Posts Tagged 'Python'
2016-09-03

A few days ago, the urge to create an internetted thingy was upon me. Recently, I had been reading documentation regarding machine to machine communication using MQTT as well as BLE, and then I took a quick look at my tinker pile. The amount of neat things that I purchased for use in a project was far too high.... and then it hit me: LESS HOARDING, MORE MAKING!

What I was craving to make, was a network accessible pixel LED, inside an orb light fixture, that could be controlled using MQTT.

For this build, I used:

  • an orb light fixture from the ReStore
  • an Unwired One single board computer running OpenWRT
  • a single ws2801 pixel LED that was left over from the Glimmer build.

The Build

The Fixture

The orb light fixture appears to be a run of the mill wall or ceiling CFL fixture. If I remember correctly, the orb cost around $5. The cost of computer cases these days is getting out of hand. :)

Take it Apart!

For some reason, the fixture manufacturer decided to put a bunch of unnecessary crap in my new computer case. Fortunately, it only took a few minutes to get the orb in a usable state.

Test the Circuit

ws2801 pixel LEDs are controlled using SPI. On the Unwired One device, there are no GPIO pins that are dedicated to SPI. Instead, the SPI bus is created by loading a kernel module with arguments to determine which GPIO pins will be used for SPI.

Solder Leads to the LED

Once the circuit was working, leads were soldered to the LED so that it could be connected directly to the GPIO headers.

After the soldering was complete, the leads were wrapped in electrical tape.

Make a Notch for the Power Cable

Since the orb is intended to rest on a table, it was necessary to make a notch in the base for the power cord to fit through. Thus ensuring that the orb will be flush on a flat surface. After a bunch of waffling around trying to find my files and metal snips, a grab and twist with some needle nosed pliers did the trick.

Put it Together

After more waffling while trying to determine the best way to make some fancy standoffs for the computer board, I opted to simply hot glue the board to a piece of cardboard, and then hot glue the cardboard to the underside of the orb base.

Fast
Simple
Effective
... and I'm lazy

The LED was also hot glued to the base, pointing upward into the glass orb. /

Now me has ORB!

Have Some Coffee

Technically, this picture is for the morning after the Orb Device had been created, but it does a damn fine job of showing what the orb thingy looks like.

The Code

Enable SPI

As previously stated, the SPI on the board needs to be configured by added a kernel module. In order for the kernel module to be loaded each time the device is restarted, the /etc/rc.local file was edited to launch the following script:

#!/bin/sh

#see http://www.unwireddevices.com/wiki/index.php/Working_with_SPI_(C/C%2B%2B)
#BUS : SPI bus number, can be bus0, bus1, bus2, bus3
#ID : SPI device ID (integer number)
#SCK, MOSI, MISO : GPIO numbers for the corresponding SPI signals
#MODE : SPI mode (0, 1, 2 or 3)
#FREQ : max SPI frequency (Hz)
#CS : GPIO number for the CS signal (optional)
# insmod spi-gpio-custom BUS=ID,SCK,MOSI,MISO,MODE,FREQ,CS

bus="bus1"
id=1
sck=20
mosi=23
miso=18
freq=25000000

cmd="insmod spi-gpio-custom $bus=$id,$sck,$mosi,$miso,0,$freq"
echo $cmd
`$cmd`

Honestly, the entire script can be replaced with one line of code: the final command. However, the script was written while reading the documentation of the process and if I ever need to edit the script, there are hella useful comments in there.

Control the Orb

The code for controlling the orb is written in Python and requires the Paho MQTT library.

code is available at https://gitlab.com/jezra/orb

oh... It would be really nice if I could copy the payload parsing documentation from the README.md in the code repository. Sadly, the lazy dev hasn't yet created that file. Well then, I know what I'm doing later!

Now quit reading, and go make an orb thingy.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 5
Answer:
required
2015-03-15

The Magic starts at 3:30AM... because at 3:30AM every day, a cron task runs on the chicken coop computer to determine when the coop door should open and close later that day. For most of my weather needs I had been using the http://wunderground.com API, but for some reason, they don't offer access to twilight data through the API and I didn't want to write a webpage scraper to harvest the data from their website.

Fortunately for me, hamweather.com has a wonderful API accessible weather service with great documentation, all at a very reasonable price (free, if making less than 750 API calls per day).

For this piece of the project, I decided to use civil twilight as the opening and closing times. Then it was just a matter of reading the hamweather API documentation for sun and moon data and then hacking together some code. Since the majority of the code is written in Python, I opted to use python for this script.

Enter the Python

#!/usr/bin/env python2
import urllib
import json
import os.path
import subprocess
import time

client_id "MY_CLIENT_ID"
client_secret "MY_CLIENT_SECRET"
url="http://api.aerisapi.com/sunmoon/MY_CLOSEST_TOWN?client_id=%s&client_secret=%s" % (client_idclient_secret)

print url

script_dir os.path.dirnameos.path.realpath(__file__) )
data_file os.path.join(script_dir'sunmoon.json')
open_script os.path.join(script_dir,"open_door.sh")
close_script os.path.join(script_dir,"close_door.sh")

try:
  #read the data from the url and write it to a file
  response urllib.urlopen(url)
  data response.read()
  open(data_file"w")
  f.write(data)
  f.close()
except:
  #something failed, open the old data file
  open(data_file,'r')
  data f.read()

#load the json into an object
json_object json.loads(data)

#get the twilight from the json object
twilight json_object['response'][0]['sun']['twilight']

#get the twilight start and end
civilbegin twilight['civilBegin']
civilend twilight['civilEnd']

#format the start and end times in Hour:Minute format
twilight_start time.strftime("%H:%M"time.localtime(civilbegin))
twilight_end time.strftime("%H:%M",time.localtime(civilend))

#create commands based on the times
open_cmd =  "at -f "+open_script +' '+twilight_start
close_cmd =  "at -f "+close_script +' 'twilight_end

#run the commands as a subprocess
subprocess.call(open_cmdshell=True)
subprocess.call(close_cmdshell=True)

During the event loop of my door controller, a file is checked to see if the door should open or close, and the door behaves accordingly. This twilight fetching script will determine when to open and close the door, and submits a command to at, which then handles the timing for running the open or close script.

The chickens will be going into their new home next weekend, and the close time will probably need to be adjusted, but that just means I need to hang out in the hammock at sunset/twilight and see if all the chickens go in the coop before the door closes.

Now quit reading, and go make something that can only be tested by relaxing in a hammock at sunset. :)

Comments
2015-06-28 Tim Diskataneous:
Nice work. I'm looking for my first project and it's cool to see what you made. I bet the chickens wonder who opens and closes the door every day!
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
3 minus 3
Answer:
required
  • Tags:
  • Python
2014-12-25

Not too long about I built a tubular music thingy, and while it is nice and fun to play, I really wanted to automate the playing of some tunes as well as giving myself the ability to play certain tunes at certain times.

To be fair, this was the plan all along and I'm calling this thing "Spiel", which I guess is short for glockenspiel.

Before the tubular music thingy was built, I came up with a fairly plain-text music notation format that I call SMN: the Spiel Music Notation format. In a nutshell, SMN files start with "tNUMBER" where NUMBER is the number of beats per second, and then there will be a series of notes: G a b c d e f g, and rests: r. Both notes are rests will be considered whole notes unless followed by a number.

For example, Sudo Modprobe in SMN would be:

t30   
a4 G8 a4 G8 a8   
b8 c2 e4 d2 r8  
e4 d8 c8 d8 c8  
d8 c8 b8 a4 G8  
a4 G8 a8 b8  
c2-8 b8 G1-2  

On To the Build

For the automation, I used the following:

Stand Offs

What are these things called? I don't know.

What I do know, is that once the spiky bits are bent down, these things make amazing stand offs for just about any computer build.

Mount the computer and relay board

After the stand offs were put on the Beaglebone Black and the 8 channel relay, the stand offs were hot glued to the frame of the Tubular Music Thingy.

Place the solenoids

More hot glue was used to place the solenoid bell hammers.

When the solenoids are energized, they produce heat, and if they stay energized long enough, they get hot enough to melt the glue.

This led me to write a script called 'heat' that will heat up a solenoid so that I can realign the hammer for a better sound.

Wire it up!

The Beaglebone was wired to the relay (I later changed the beaglebone to relay wiring).

The relay is wired to the solenoids, and the 12V power adapter was spliced into the relay and common ground of the solenoids.

Plug it in and run some code

Here is the finished product, ready to be played, or automated over the network.

What about some code?

The code for controlling Spiel is available at https://gitorious.org/spiel/spiel The documentation is non-existent, but there are a few .smn files in the SMN directory. If you have any questions about the code, send me an email and I'll help as I can.

My last Linux Outlaws related script

The final episode of the Linux Outlaws is nearly upon us, and I needed a script that will check if the last episode has been released, and if so, trigger the Spiel device to play the Sudo Modprobe melody.

Basically, this is a Final Episode Alarm

#!/usr/bin/env python2
import urllibtime
from xml.dom.minidom import parseString
url "http://feeds.feedburner.com/linuxoutlaws-ogg?format=xml"

#we need to loop for a while
looping True

while looping:
  #read the URL
  urllib.urlopen(url)
  xml f.read()
  #parse the XML
  obj parseString(xml)
  #get the first item
  item obj.getElementsByTagName("item")[0]
  #get the title
  title item.getElementsByTagName("title")[0]
  ttext title.firstChild.nodeValue.encode('utf8')
  #does the title contain 370?
  if "370" in ttext:
    open('play_smn','w')
    f.write("sudo_modprobe.smn")
    f.close()
    looping False

  print time.strftime("%Y-%m-%d %H:%M:%S"time.localtime())
  #sleep for 5 minutes
  time.sleep(300)

So now, within 5 minutes of the final episode being released, my Spiel device will play:

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
5 minus 4
Answer:
required
2012-12-24

After setting up my BeagleBone with a phone so that I can run scripts when certain numbers are dialed, it became apparent that I should have other ways of running scripts on the BeagleBone, and most importantly, it should be possible to run scripts at a given time. Time for a new project!

A bit of Background

For a while, I had been using SSH to connect to the BeagleBone and using the at command to schedule the running of a script at a specific time. For example to run my "maiden_fade" script at 8:10 AM, I would use the following:

echo "morning_maiden" | at 8:10

While that is fine and dandy, I really really did not want to open a terminal, ssh to the BeagleBone, and then type a command in order to do what I want. To make everything nice and easy, I started the Ruby Web Alarm Project. Originally, I had wanted to hack this thing together during a "code sprint" at the NBLUG, but there wasn't enough time. Oh well.

What is Ruby Web Alarm?

Ruby Web Alarm is a small Ruby based webserver with an HTML and JavaScript based UI that allows one to schedule scripts to be run within 24 hours. (This should really be changed to allow a user to set a script to run farther in the future)

Screen Shots!

Some Blather About the Code

As stated previously, the server for Ruby Web Alarm is written in Ruby (hey, fancy that!), and does not use any 3rd party gems. All of the included libraries are part of the Ruby Standard Library.

For the UI, jQuery handles async calls to the server and I'm using a slightly modified stylesheet from the MuttonChop Mobile UI.

What it Needs

While the project mosty does what I need it to do, it still needs some feature improvements such as:

  • Better time input (I'd like to use the HTML5 Range element, but none of the modern webkit based mobile browsers have a working Range element)
  • Daily, weekly, monthly repeating

Maiden Fade?

In case you were wondering what the "maiden_fade" script looks like...

#!/bin/sh
curl http://wind:2876/player/volume/0
/opt/mc_random_iron_maiden.py
for i in {1..35}; do
    curl http://wind:2876/player/volume/$i
    sleep 1
done

It sets the volume to zero, starts playing a random Iron Maiden track, and then, each second of the next 35 seconds it increases the volume by 1. Is there a better way to wake up than to fade into some Maiden?

And if you were wondering about the Random Iron Maiden script...

#!/usr/bin/env python2
import urllib
import json
import random
f = urllib.urlopen("http://wind:2876/audio/search/iron%20maiden")
fjson = f.read()
search_result = json.loads( fjson )
files = search_result['files']
random_file = random.choice( files )
url = "http://wind:2876/audio/play/%d" % (random_file['id'])
urllib.urlopen(url)

UP THE IRONS!

Comments
2013-04-10 Alice:
My coder is trying to convince me to move to .
net from PHP. I have always disliked the idea because of the expenses.
But he's tryiong none the less. I've been using WordPress on a variety of websites for about a year and am worried about switching to another platform.
I have heard very good things about blogengine.net.
Is there a way I can transfer all my wordpress content into it?
Any help would be really appreciated!
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 7
Answer:
required
2012-09-23

Earlier this month, I received a 3V Voltmeter that I purchased on Amazon. The reason for the purchase was so that I could write some code to control the voltmeter from my beaglebone. Well hey! Guess what I finally got around to doing?

Before I get to the code, let me explain how this works, and I'd like to give a big shout out to gigamegablog for being such a great resource.

Controlling the voltmeter with the beaglebone requires a bit of understanding of Pulse-Width Modulation (PWM). The pins on the beaglebone are capable of outputting 3.3V of power. By using PWM we can send the 3.3V of power in pulses, and when these pulses are spaced apart properly, we give the illusion of a lower voltage.

The Pin Layout

According to the beaglebone documentation, pins 14 and 16 of pin header P9 on the beaglebone are PWM pins. It should be possible to use other pins as PWM pins by "muxing" the pins, but for my needs, pin 14 will do just fine.

The positive probe of the voltmeter was connected to pin 14 on P9 and the ground was connected to pin 2 on P9.

The First Test

Since I am runnin Arch Linux on my beaglebone, PWM is not only supported in the kernel, it is also enabled. Rad, that is one less step to take. Thank you Arch Linux!

The test went like this :

  1. su to become root
  2. mux pin 14, just to be sure:
    echo 6 > /sys/kernel/debug/omap_mux/gpmc_a2
  3. set the duty_percent to 0 (this is the percent of the 3.3V that we want to emit):
    echo 0 > /sys/class/pwm/ehrpwm.1:0/duty_percent
  4. set the period frequency to 100 (this is the 'pulse' ins Hz):
    echo 100 > /sys/class/pwm/ehrpwm.1:0/period_freq
  5. start the PWM pin running:
    echo 1 > /sys/class/pwm/ehrpwm.1:0/run
  6. start increasing the duty_percent until the voltmeter is at 3V. For me, this was when the duty_percent was 92

Sweet!

Getting Data To Process

Since I didn't want to do any web server configuration in order to get data, I decided to write a very small WSGI app in python and serve it up using the wsgiref.simple_server class.

Enter the Python

#!/usr/bin/env python2
from wsgiref.simple_server import make_server

#define some stuff
max_val 92
pin_path "/sys/class/pwm/ehrpwm.1:0"
run pin_path+"/run"
duty_percent pin_path+"/duty_percent"
period_freq pin_path+"/period_freq"

#mux the pin
try:
  open("/sys/kernel/debug/omap_mux/gpmc_a2","w")
  f.write("6")
  f.close()
except Exceptione:
  print e.message

def set_runval ):
  try:
    open(run,"w")
    f.writestr(val) )
    f.close()
  except Exceptione:
    print e.message

def set_duty_percent(val):
  try:
    open(duty_percent,"w")
    f.writestr(val) )
    f.close()
  except Exceptione:
    print e.message

def set_period_frequency(val):
  try:
    open(period_freq,"w")
    f.writestr(val) )
    f.close()
  except Exceptione:
      print e.message

#set some PWM values
set_run(0)
set_duty_percent(0)
set_period_frequency(100)
set_run(1)

def got_value(val):
  val val.strip("/")
  val int(val,10)
  percent = (max_val val/100)
  set_duty_percent(percent)

class my_wsgi_app:
  def process_request(self,text):
    got_value(text)
    status '200 OK' # HTTP Status
    headers = [('Content-type''text/html')] # HTTP Headers
    return statusheaderstext
      
  def run(selfenvironstart_response):
    path environ['PATH_INFO']
    statusheaderscontent self.process_requestpath )
    #begin the response
    start_response(statusheaders)
    #return the content
    return content

app my_wsgi_app()
httpd make_server(''8080app.run)
print "Serving on port 8080..."

# Serve until process is killed
httpd.serve_forever()

For ease of copy, the code is also available at http://hoof.jezra.net/snip/o3

The code starts serving on port 8080 and sets the percent of the PWM based on the URL used to access the server. For example, to set the voltmeter to 25%, one would need to point a browser at http://NAME_BEAGLEBONE:8080/25. My beaglebone is mounted on the wall and is named "wallbone", yea original. Anyway, if I want to set the voltmeter to 77%, I would access http://wallbone:8080/77

Face to Face

It's a shame that the voltmeter as a big "V" on it, as well as 0, 1, 2, 3, and some useless text.

One would think that I could just take apart the voltmeter, make a compass from a pin and a twist-tie, draw a new face, and re-assemble the meter. Oh, don't mind if I do!

How about some video?

Well... it was far too dark and the pin isn't really visible. What a shame.
http://www.youtube.com/watch?v=ldcwMYj5AIc
http://www.youtube.com/watch?v=SdbBgiZn1UM

Now What?

There are plenty of uses for this meter.

  • analog progress or volume meter for muttonchop
  • add a few more meters and create a neat clock

With the pulse-width modulation, I could connect and control a fan from the beaglebone.

The possibilities are only limited by my imagination.

Now quit reading, and go send me some ideas.

Comments
2012-10-12 Alison Chaiken:
Just curious . . . which kernel version is running? I know, such a boring question, but I'm curious whether it would be a newer one with devicetree, whose pinctrl with PWM is somewhat complex, but prolly implemented by TI for beaglebone.
2012-10-12 jezra:
Kernel 3.2.21
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 4
Answer:
required
2011-08-12

In the movies, when it is time to detonate the explosives, the person setting off the KABOOM has a sweet remote control with either a two part switch or with two separate switches. Damn, I love a good movie explosion.

The two part switch allows the user to:

  1. activate the explosive
  2. blow shit up

Because the switch has two components, activate and detonate, it is uncommon for there to be accidental explosions. This is a damn fine safety measure and when adding the ability to block an identi.ca user with heybuddy, I wanted a similar safety precaution; blocking someone by accident should not be possible. So that is what I did.

To accomplish this, it was necessary to do the following

  • create a disabled button that brings the hammer down!
  • create a checkbox that will activate the button

In GTK parlance, to disable a button, one needs to set the button sensitivity to false. Since I am using Python, this is accomplished thusly

widget.set_sensitive(False)

To show this idea as a small working bit of code, I wrote a quick Python-GTK app that almost does the bare minimum.

Enter the Python

#!/usr/bin/env python

import gtk
class application:
  def __init__(self):
    #I want to keep track of click counts
    self.click_count=0
    '''build the UI'''
    #make the window
    winder gtk.Window(gtk.WINDOW_TOPLEVEL)
    winder.connect("destroy",self.quit)
    #winder.set_default_size(300,300)
    winder.set_title("Super Trigger")
    #make some boxes
    vbox gtk.VBox()
    hbox gtk.HBox()
    #make some buttons
    self.button gtk.Button("Click Me")
    self.checkbutton gtk.CheckButton(label="Enable Button")
    #add the buttons to the hbox
    hbox.pack_start(self.button,False,False)
    hbox.pack_start(self.checkbutton,False,False)
    #add the hbox to the vbox
    vbox.pack_start(hbox,False,False)
    #create a Label
    self.label gtk.Label()
    #add the label to the vbox
    vbox.pack_start(self.label)
    #add the vbox to the window
    winder.add(vbox)
    
    #disable the button
    self.button.set_sensitive(False)
    
    #connect the buttons
    self.checkbutton.connect('toggled'self.toggle_clicked )
    self.button.connect('clicked'self.button_clicked)
    #show it all!
    winder.show_all()
    
  def toggle_clicked(selfwidget):
    #is the checkbutton active?
    active widget.get_active()
    self.button.set_sensitive(active)     
  
  def button_clicked(self,widget):
    #increment the click count
    self.click_count+=1
    #make a string 
    string "clicks: %d" %(self.click_count)
    #set the label to the string 
    self.label.set_text(string)
    
  def run(self):
    gtk.main()
    
  def quit(self,widget=None):
    gtk.main_quit()
  
if __name__=="__main__":
  application()
  a.run()

It may not be the best solution, but it is a solution and it works.

Well that's it for now. Quit reading, and go write some code.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 4
Answer:
required
2011-06-20

My N810 died

When I went to the MeeGo conference I discovered that my N810 would no longer boot. The device would simply blink the Nokia logo when I tried to turn it on, and all of my attempts to flash a new image to the device failed. So long buddy. [sadface]

How Am I Supposed to Test Software?

Since I need an Maemo device for testing Heybuddy I wrote a postcard to Nokia and requested a new device. Quite surprisingly, I have not yet received a response.

Really? Didn't they see the awesome picture of a coffee cup that drew on the postcard? Maybe they were just waiting until they announced the N9 before they send one my way. Yeaaaaa, that's the ticket!

Fortunately for me (and for !Heybuddy ), there are some kick ass users and testers out there that help me with Maemo problems. However......

Terrible Timing

On June 16th, there was a bug report for heybuddy regarding a security vulnerability due to the use of Python's urllib2 module for HTTPS communication. A nice fix for the problem was found at stackoverflow, and it utilizes the SSL module. However.....

There is No Python SSL Module on Maemo!

One more time, in case you missed it: There is no Python SSL Module on Maemo! Maemo uses Python 2.5 and the 2.5 release did not include an SSL module. Fortunately, there is an SSL module available for Python 2.5 that can be downloaded from http://pypi.python.org/pypi/ssl. Unfortunately, I can't properly test this SSL module with Heybuddy on a Maemo device because my device is broken. Oh, now we've come full circle.....

If you are using Heybuddy, I highly suggest that you update to the latest version, and if you are on an Maemo device, update to the latest Heybuddy release and install the SSL module.

Now quit reading, update your software, and send someone a postcard.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
5 minus 5
Answer:
required
2011-03-18

The Need

I really wanted a web accessible interface to control streaming music on my media playing computer that is connected to my home stereo. With a mix of Last.fm, shell-fm, pyjamas, web.py, and a few hundred lines of python code, I succeeded adequately and thought I'd share my experience.

The Why

When I wanted to stream music on my media machine, I would to get on the command line, ssh to the machine with X11 forwarding, and run the last.fm linux client on the machine. This would display a sluggish last.fm client on whichever computer I was using to connect to the media machine.

But what if I'm switching computers or I want to use my Nokia N810 or Neo Freerunner to control the music? A web interface solves these issues.

NOTE: this "how to" assumes that you are familiar with compiling software from source on a Linux system.

Step 1

Sign up for a Last.fm account. It is a fairly straight forward process.

Step 2

Install shell-fm (follow the directions on the shell-fm webpage). Shell-fm may be in your repos, but the version may not be new enough for this to work.

edit your ~/.shell-fm/shell-fm.rc file to look like the following:

username = YOUR_LASTFM_USERNAME
password = YOUR_LASTFM_PASSWORD
default-radio = lastfm://user/YOUR_LASTFM_USERNAME
unix = /home/YOUR_USER/.shell-fm/unix_file
daemon = true

What the configuration does:

  • username and password should be self explanatory
  • default-radio is used to set what starts streaming when shell-fm is started
  • unix creates a unix file socket that allows for communication with shell-fm. My file is /home/jezra/.shell-fm/unix_file. we need to use this later
  • daemon runs shell-fm as a daemon in the background

OK, that takes care of the player. Now onto the web server

Step 3

In order to get a web interface, I need some sort of webserver. There are many many different ways to create a webserver and for this example I chose to use web.py which is a damn simple web app framework for Python. If web.py isn't in your repos, you should switch distributions (or download the source).

My Layout of Files and directories
/testing_directory
--index.py #the webserver
--interface.py # the pyjamas interface for the web app
--templates/ #a directory containing one template that the webserver will use
--static/ #a directory of static content served by the web app
----styles/ #a directory of css styles used by the web app
----pyjamas/ #a directory containing the compiled output of the interface.py file

Lets move on to the files

index.py

#import the web module
import web
import socket

#define some variable
#define our url mapping
urls = (
  '/''index',
  '/track_info','get_track_info',
  '/skip','skip',
  '/love','love',
  '/pause','pause',
  '/station/(.*)/(.*)','station',
  '/volume/(.*)','volume',
  '/ban','ban'
)
#create a renderer
template_dir 'templates'
render web.template.render(template_dir)

#create the app
app web.application(urlsglobals(),False)

def process_socket(command,get_return=False):
  sock socket.socket(socket.AF_UNIXsocket.SOCK_STREAM)
  sock.settimeout(3)
  #connect to the socket
  try:  
    sock.connect('/home/YOUR_NAME/.shell-fm/unix_file')  
    #format and send data
    sock.sendallcommand )
  except:
    #probably no socket, behave accordingly
    return "could not connect to socket"
    
  #get some data back
  if get_return:
    try:
      (stringaddress)=sock.recvfrom(512)
      sock.close()
      return string
    except:
      return "error"

#we need to send commands to 
class index:
  def GET(self):
    return render.index()

class get_track_info:
  def GET(self):
    command ="info %a::%t::%l::%d::%R::%s::%v::%r\n"
    data process_socket(command,True)
    return data
    
class skip:
    def GET(self):
      process_socket("skip\n")
      return "OK"

class love:
    def GET(self):
      process_socket("love\n")
      
class ban:
    def GET(self):
      process_socket("ban\n")
      
class pause:
  def GET(self):
    process_socket("pause\n")
    
class volume:
  def GET(self,direction):
    for in range(3):
      process_socket("volume-%s\n" % (direction) )

class station:
  def GET(self,type,text):
    command "play lastfm://%s/%s\n" % (type,text)
    data process_socket(command)
    return data
      
if __name__ == "__main__":
  app.run()
else:
  application app.wsgifunc()

Go to line 28 and change sock.connect('/home/YOUR_NAME/.shell-fm/unix_file') to point to your unix file that you set up in the shell-fm.rc file.

Almost there, now we just need to create an AJAXy interface.....

Step 4

Creating a web app with pyjamas is quite a bit like creating a Python application with any graphical toolkit, except the code needs to be compiled by Pyjamas and the finished product is HTML and JavaScript.

So go get the pyjamas source. You don't really need to install pyjamas, but you will need to "bootstrap" it. cd into the pyjamas directory and run "python bootstrap.py" which will create the pyjamas compilers in the bin directory of the pyjamas source directory.

interface.py

from pyjamas.ui.HTML import HTML
from pyjamas.ui.RootPanel import RootPanel
from pyjamas.ui.SimplePanel import SimplePanel
from pyjamas.ui.HorizontalPanel import HorizontalPanel
from pyjamas.ui.Label import Label
from pyjamas.ui.Button import Button
from pyjamas.ui.VerticalPanel import VerticalPanel
from pyjamas.HTTPRequest import HTTPRequest
from pyjamas.Timer import Timer
from pyjamas.ui.ListBox import ListBox
from pyjamas.ui.TextBox import TextBox
from pyjamas.ui.FlexTable import FlexTable
from pyjamas import DOM
from pyjamas import Window

class InfoHandler:
  def __init__(selfpanel):
    self.panel panel 
  
  def onError(selftextcode):
    self.panel.onError(textcode)

  def onTimeout(selftext):
    self.panel.onTimeout(text)
    
class NullInfoHandler(InfoHandler):
  def __init__(selfpanel):
    InfoHandler.__init__(panel)
    self.panel panel

  def onCompletion(selftext):
    pass  

class ButtonInfoHandler(InfoHandler):
  def __init__(selfpanel,button):
    InfoHandler.__init__(panel)
    self.panel panel
    self.button button
    
  def onCompletion(selftext):
    self.button.setEnabled(True)

class TrackInfoHandler(InfoHandler):
  def __init__(selfpanel):
    InfoHandler.__init__(panel)
    self.panel panel

  def onCompletion(selftext):
    #return the text to the application for processing
    self.panel.process_track_info(text)

class Application:
  def __init__(self):
    #set some vars
    self.title "PyWebShellFM"
    #this is where we build the ui
    self.statusPanel VerticalPanel()
    self.statusPanel.setID('status_panel')
    #make a few Labels to hold artist, track, album info
    self.artistLabel Label()
    self.trackLabel Label()
    self.albumLabel Label()
    self.timeLabel Label()
    self.infoTable FlexTable()
    i=0
    self.infoTable.setWidget(i,0,Label("Artist:") )
    self.infoTable.setWidget(i,1,self.artistLabel)
    i+=1
    self.infoTable.setWidget(i,0,Label("Track:") )
    self.infoTable.setWidget(i,1,self.trackLabel)
    i+=1
    self.infoTable.setWidget(i,0,Label("Album:") )
    self.infoTable.setWidget(i,1,self.albumLabel)
    
    self.statusPanel.add(self.infoTable)
    self.statusPanel.add(self.timeLabel)
    #make the time bar
    timebarWrapperPanel SimplePanel()
    timebarWrapperPanel.setID("timebar_wrapper")
    #timebarWrapperPanel.setStyleName('timebar_wrapper')
    self.timebarPanel SimplePanel()
    self.timebarPanel.setID("timebar")
    #self.timebarPanel.setStyleName('timebar')
    timebarWrapperPanel.add(self.timebarPanel)
    self.statusPanel.add(timebarWrapperPanel)
    #make some shit for buttons
    self.buttonHPanel HorizontalPanel()
    self.skipButton Button("Skip"self.clicked_skip )
    self.buttonHPanel.add(self.skipButton)
    loveButton Button("Love"self.clicked_love )
    self.buttonHPanel.add(loveButton)
    pauseButton Button("Pause"self.clicked_pause )
    self.buttonHPanel.add(pauseButton)
    banButton Button("Ban"self.clicked_ban )
    self.buttonHPanel.add(banButton)

    #control the volume
    self.volumePanel VerticalPanel()
    self.volumeLabel Label("Volume:")
    self.volumePanel.add(self.volumeLabel)
    volupButton Button("volume +"self.clicked_volume_up5)
    self.volumePanel.add(volupButton)
    voldownButton Button("volume -"self.clicked_volume_down5)
    self.volumePanel.add(voldownButton)
    
    #make some stuff for station Identification
    self.stationInfoHPanel HorizontalPanel()
    stationText Label("Station: ")
    self.stationLabel Label()
    self.stationInfoHPanel.add(stationText)
    self.stationInfoHPanel.add(self.stationLabel)
    
    #make buttons and shit to create a new station
    self.setStationHPanel HorizontalPanel()
    self.setStationTypeListBox ListBox()
    self.setStationTypeListBox.setVisibleItemCount(0)
    self.setStationTypeListBox.addItem("Artist","artist")
    self.setStationTypeListBox.addItem("Tags","globaltags")
    self.setStationHPanel.add(self.setStationTypeListBox)
    self.setStationTextBox TextBox()
    self.setStationTextBox.setVisibleLength(10)
    self.setStationTextBox.setMaxLength(50)
    self.setStationHPanel.add(self.setStationTextBox)
    self.setStationButton Button("Play"self.clicked_set_station)
    self.setStationHPanel.add(self.setStationButton)
    
    #make an error place to display data
    self.infoHTML HTML()
    RootPanel().add(self.statusPanel)
    RootPanel().add(self.buttonHPanel)
    RootPanel().add(self.volumePanel)
    RootPanel().add(self.stationInfoHPanel)
    RootPanel().add(self.setStationHPanel)
    RootPanel().add(self.infoHTML)
    
  def run(self):
    self.get_track_info()

  def get_track_info(self):
    HTTPRequest().asyncGet("/track_info"TrackInfoHandler(self))
    Timer(5000,self.get_track_info)

  def clicked_skip(self):
    self.skipButton.setEnabled(False
    HTTPRequest().asyncGet("/skip",ButtonInfoHandler(self,self.skipButton) )

  def clicked_volume_down(self):
    HTTPRequest().asyncGet("/volume/down",NullInfoHandler(self) )

  def clicked_volume_up(self):
    HTTPRequest().asyncGet("/volume/up",NullInfoHandler(self) )

  def clicked_love(self):
    HTTPRequest().asyncGet("/love",NullInfoHandler(self) )

  def clicked_ban(self):
    result Window.confirm("Really ban this song?")
    if result:
      HTTPRequest().asyncGet("/ban",NullInfoHandler(self) )

  def clicked_pause(self):
    HTTPRequest().asyncGet("/pause",NullInfoHandler(self) )


  def clicked_set_station(self):
    type self.setStationTypeListBox.getSelectedValues()[0]
    text self.setStationTextBox.getText().strip()
    if len(text) > :
      #clear the text
      self.setStationTextBox.setText("")
      HTTPRequest().asyncGet("/station/%s/%s"% (type,text),NullInfoHandler(self) )

  def set_error(selfcontent):
    self.infoHTML.setHTML("<pre>%s</pre>" content)
    
  def onTimeout(self,text):
    self.infoHTML.setHTML("timeout: "+text)
    
  def onError(selftextcode):
    self.infoHTML.setHTML(text "<br />" str(code))
    
  def process_track_info(self,text):
    #explode the text at :: 
    #%a::%t::%l::%d::%R::%s::%v::%r
    data text.split("::")
    artist data[0]
    track data[1]
    album data[2]
    duration data[3]
    played int(duration)-int(data[4])
    percent intplayed/int(duration)*100 )
    self.artistLabel.setTextartist )
    self.trackLabel.setTexttrack )
    self.albumLabel.setTextalbum )
    #format time
    "%s : %d  %d" % (duration,played,percent)
    self.timeLabel.setText(data[7])
    #update the timebarwidth
    self.timebarPanel.setWidth("%d%" % (percent) )
    #set the station
    self.stationLabel.setText(data[5])
    #display the volume
    self.volumeLabel.setText("Volume: "+data[6])
    Window.setTitle(self.title+": "+artist+" - "+track)
    
if __name__ == '__main__':
  app Application()
  app.run()

Compile this code using "/PATH/TO/PYJAMAS/SOURCE/bin/pyjsbuild interface.py". Compiling will create a directory named "output". Copy the contents of "output" into "static/pyjamas"

Now create a style sheet for the progress bar in our interface
static/style/style.css

#timebar_wrapper {
  border2px solid #ddd;
  width:200px;
  height:12px;
}
#timebar{
  background-color:black;
  height:12px;
}

finally, create the index template
templates/index.html

<html>
<head>
  <meta name="pygwt:module" content="/static/pyjamas/interface">
  <link REL=STYLESHEET type='text/css' href="/static/styles/style.css">
  <title>PyWebShellFM</title>
</head>
<body bgcolor="white">
<script language="javascript" src="/static/pyjamas/bootstrap.js"></script>
<iframe id='__pygwt_historyFrame' style='width:0;height:0;border:0'></iframe>
</body>
</html>

Now if everything went according to plan.....
run "shell-fm" to start the shell-fm player
run "python index.py" to start the server
Point your web browser to http://localhost:8080 and enjoy the ugly web interface that is nowhere near "feature complete", but it is a start.

Now quit reading, and go listen to Basil Poledouris

All of these files can be downloaded from http://www.jezra.net/downloads/blogcode/pywebshellfm.tgz

EDIT: What does it look like?

How about a screenshot of the UI, and a picture of the UI in the N810 and the Freerunner.

Comments
2011-03-19 Egil:
Some screenshots of the interface would be awesome.
2011-03-19 jezra:
Yea, that's a good idea.
2011-03-20 jrobb:
100% awesome
2011-03-22 PeteV:
total awesomeness, makes me want to tinker more too.
2011-03-22 jezra:
always tinker. ;)
2011-03-27 jrobb:
I use the recommended radio sometimes, so I added in another line for personal/playlist/recommended radio stuffs.

http://jrobb.org/xfer/code/pywebshellfm/

I'm no jez and I also know zip about python, and wasn't sure of how to get the username in there, so i just made a variable up at the top.
;-)

works alright tho
2011-03-27 jezra:
jrobb, the link to the file isn't working because your server is trying to run the python script.
2011-09-24 chris:
Love it! Please add a license and put it in a public git repo, and I'll try to get it integrated into OpenWRT.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 minus 1
Answer:
required
  • Tags:
  • Python
2010-11-11

Recently, I've been playing around with web.py, a simple yet very powerful web framework for writing web applications using python, and I thought I would share my first test of web.py that goes beyond "hello world". I discussed this briefly on Episode 13 of Frostcast

what it do

In a nutshell, the code will produce a browseable list of directories and audio files.

When run, the code will start a web server on port 8080 that dynamically displays the list of directories and files. Clicking on a link to an audio file will cause the audio file to be played on the server machine using sap. One could easily use any command line audio player of their choice in place of sap; such as mplayer.

Enter the Python

#import some stuff
import web
import subprocess
import os
import urllib
#show the errors as a lot of debug info
web.internalerror web.debugerror
#what URL do we need to handle?
urls = ("/","list",
    "/list/(.*)","list",
    "/play/(.*)","play",
    "/stop","stop",
    )
#what extensions do my audio files have?
good_extensions = (".mp3",".ogg")
#create the app
app web.application(urlsglobals(),True)
#where is the music?
music_dir "/storage/music/"

class stop:
  def GET(self):
    #kill sap
    subprocess.call("killall sap",shell=True)
    #redirect to list
    raise web.seeother("/")

class play:
  def GET(self,asset):
    #kill sap
    subprocess.call("killall sap",shell=True)
    #launch the sap
    music_dir+asset
    #return f
    command "sap \"%s\"" % (f)
    print command
    subprocess.Popen(command,shell=True )
    #recdirect to list
    raise web.seeother("/")
class list:
  def GET(self,dir=None):
    body=""
    dir_list=""
    file_list=""
    short_path_prefix=""
    #determine which directory we should look in
    if dir!=None:
      short_path_prefix=dir+"/"
    parse_dir music_dir+short_path_prefix
    #if this directory exists, we need to show some shit  
    if os.path.exists(parse_dir):
      body+="<b>%s</b> <a href='/stop'>stop</a><br>" % (parse_dir)
      #get the dir contents
      dirlist os.listdirparse_dir )
      #I like things in alphabetical order
      dirlist.sort()
      for asset in dirlist:
        asset_full_path parse_dir+"/"+asset
        asset_short_pathshort_path_prefix+asset
        if os.path.isdirasset_full_path ):
          encoded_path urllib.pathname2url(asset_short_path);
          dir_list+="<a href='/list/%s'>%s</a><br>" % (encoded_path,asset)
        else:
          #check the extension
          (f,e) = os.path.splitext(asset)
          #TODO: make this case insensitive
          if in good_extensions:
            encoded_path urllib.pathname2url(asset_short_path);
            file_list+="<a href='/play/%s'>%s</a><br>" % (encoded_path,asset)
    else:
      body+="%s doesn't exist" % (parse_dir)
    #add the links to directories and files
    body+=dir_list
    body+=file_list
    return "<html><title>Music Browser</title><body>%s</body></html>" body
if __name__ == "__main__"
  app.run() 

Give it a go

  1. save the code http://www.jezra.net/downloads/blogcode/audio_browser.tgz
  2. edit line 19 to point to a directory of audio files
  3. run the code with python
  4. point your browser to http://localhost:8080
  5. email me to let me know what went wrong
  6. ?
  7. Did you remember to install web.py?

Moving Forward

Welcome to "Shot of Code", this is only the start of the code. So where can this go? AKA, what would I like this to do. Well I can think of a few things.

  • a web interface for playing videos on my media machine. This will require some some way to send commands to Mplayer for play, pause, fast-forward, etc.
  • a web front end to a command line audcatcher (sorry, I couldn't resist).
  • recreate shnerkel with a web interface.

The reason that all of my code ideas are audio or video related is because I want to be able to control my media machine with a web interface and not have to SSH into the machine to control media playback.

Now quit reading, and go sweeten that sauce!
jezra

Comments
2010-11-12 kabniel:
Cool, i've yet to play with python web apps.

I played a bit with mplayer control from bash scripts a few weeks ago and a combination of mplayer's 'slave=yes' and 'input=file=/path/to/fifo' options makes it pretty easy to control mplayer by sending commands like 'echo "play" > /path/to/fifo'
2010-11-12 jezra:
excellent! I can use the python socket module to communicate through the named piped. Blend this with some pyjamas ajaxy-ness and I'll be a happy camper.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
6 minus 5
Answer:
required
  • Tags:
  • Python
2010-08-16

Starting with version 2.18, the GTK library allowed for adding links to Labels, and this is exactly how I handled adding clickable URLs and names in heybuddy. Unfortunately, the version of GTK on Maemo devices is too old to use links in Labels.

Another unfortunate problem with links in Labels is that no GTK theme seems to have set a custom link color and all themes are stuck with the default bright blue link color. Egads! It sure would be nice if the user could set the color of links in heybuddy.

After searching and searching, I couldn't find an example of how to set a custom color for links in a GTK Label so I first needed to write some code to experiment with. The end result was a very simple example of exactly what I needed: a custom link color in a GTK Label.

Enter the Python

#!/usr/bin/env python
import gtk

gtk.Window()
gtk.Label()
"go to <a href='http://www.jezra.net'>jezra.net</a>"
l.set_markup(m)

style='''
style "label" {
  GtkLabel::link-color="#ff6600"
}
widget_class "*GtkLabel" style "label"
'''

gtk.rc_parse_string(style)

w.add(l)
w.show_all()
gtk.main()

shazaam! Custom color, baby!

Now quit reading, and go make things colorful.

Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 4
Answer:
required
2010-06-30

Recently, while writing code for heybuddy, I needed to:

  • display a tray icon
  • hide the window when "close window" is selected
  • show the window when the tray icon is clicked

Although getting this done was easy enough to do, finding out how to do it was a bit of a pain, so I thought it would be best if I shared my Python/GTK test code and save future developers from some pain.

Enter the Python

#!/usr/bin/env python
import gtk
class Win (gtk.Window):
    def __init__(self):
        gtk.Window.__init__(self)
        self.set_title("Close to tray")
        self.connect("delete-event"self.delete_event)
        #create a virtical box to hold some widgets
        vbox gtk.VBox(False)
        #make a quit button
        quitbutton gtk.Button("Quit")
        quitbutton.connect("clicked",self.quit)
        #add the quitbutton to the vbox
        vbox.pack_start(quitbutton)
        #create a label to say *something* and add to the vbox
        label=gtk.Label("This is a test of 'close to tray' functionality")
        vbox.pack_start(label)
        #add the vbox to the Window
        self.add(vbox)
        #show all of the stuff
        self.show_all()
        #make a status icon
        self.statusicon gtk.status_icon_new_from_stock(gtk.STOCK_GOTO_TOP)
        self.statusicon.connect('activate'self.status_clicked )
        self.statusicon.set_tooltip("the window is visible")
        #start the gtk main loop
        gtk.main()
    
    def quit(self,button):
        #quit the gtk main loop
        gtk.main_quit()
    
    def delete_event(self,window,event):
        #don't delete; hide instead
        self.hide_on_delete()
        self.statusicon.set_tooltip("the window is hidden")
        return True
        
    def status_clicked(self,status):
        #unhide the window
        self.show_all()
        self.statusicon.set_tooltip("the window is visible")
        
if __name__=="__main__":
    win Win()

The basics of what needs to be done:

  1. connect the Window's "delete-event" signal to a function
  2. in the function, have the window "hide_on_delete()"
  3. return True ( if True is not returned, the Window's children will be destroyed and everything will go to crap )

So there you have it, an easy way to "close to tray" or whatever you want to call it.

Now quit reading, and go ease a developer's pain.

Comments
2010-08-17 cs.ati:
Thank U! This is exactly what I need. U saved me a lot of time. Thanks again!
2010-08-17 jezra:
You are very welcome.
2010-12-09 Guilherme:
THANKS!!! That's just awesome! I thought I would spent a lot of time doing this, but then I found you and now 'm really relieved.
2016-01-25 nurul irfan:
it works, even in PyGObject. the return true is the most important. thanks.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 3
Answer:
required
2010-06-24

The Need


Having recently implemented parsing of Markdown code in my blog, and deciding to write most, if not all, of my future documentation in Markdown, what I really needed, was a way to do the following:

  1. write markdown code in a spellchecking text editor
  2. convert the markdown code to HTML
  3. preview the HTML as it would appear in a browser

Simple enough, but unfortunately, all of the solutions that I found were online forms. I wanted something that I could run on my own machine.

The Solution


As is often the case, the solution was to write a quick little application. For this application, I chose to use Python due to the readily available Python Markdown module as well as my "quick/dirty" need and recent Python usage on various projects.

The application uses GTK for the graphical interface, and webkit for HTML rendering.

Without further ado ...
Enter the Python

#!/usr/bin/env python 
''' 
Hey, this is GPLv3 and copyright 2010 Jezra 
''' 
import gtk 
import webkit 
import markdown #http://www.freewisdom.org/projects/python-markdown/ 
global default_file 
default_file = "markdown.txt" 
try: 
    import gtkspell 
    has_gtkspell=True 
except: 
    has_gtkspell=False 
     
class application: 
    def `__init__`(self): 
        #build the UI 
        winder = gtk.Window(gtk.WINDOW_TOPLEVEL) 
        winder.connect("destroy",self.quit) 
        winder.maximize() 
        #add some accellerators 
        #create an accellerator group for this window 
        accel = gtk.AccelGroup() 
        #add the ctrl+q to quit 
        accel.connect_group(gtk.keysyms.q, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.quit_accel ) 
        #add ctrl+s to save 
        accel.connect_group(gtk.keysyms.s, gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE, self.save_accel ) 
        #lock the group 
        accel.lock() 
        #add the group to the window 
        winder.add_accel_group(accel) 
        #we need a paned 
        pane = gtk.HPaned() 
        #and a vbox 
        box = gtk.VBox(False) 
        #add the pane to the box 
        box.pack_start(pane) 
        #add the box to the window 
        winder.add(box) 
        #do the text crap for the first pane 
        self.tb = gtk.TextBuffer() 
        tv = gtk.TextView(self.tb) 
        tv.set_wrap_mode(gtk.WRAP_WORD) 
        #try and add spell checking to the textview 
        if has_gtkspell: 
            #what if there is no aspell library? 
            try: 
                self.spell = gtkspell.Spell(tv) 
                self.has_spell_library=True 
            except Exception: 
                #bummer 
                self.has_spell_library=False 
                print Exception.message 
        #add the text view to a scrollable window 
        input_scroll = gtk.ScrolledWindow() 
        input_scroll.add_with_viewport(tv) 
        input_scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) 
        #add to the pane 
        pane.pack1(input_scroll,True) 
        #make the HTML viewable area 
        self.wv = webkit.WebView() 
        #disable the plugins for the webview 
        ws = self.wv.get_settings() 
        ws.set_property('enable-plugins',False) 
        self.wv.set_settings(ws) 
        out_scroll = gtk.ScrolledWindow() 
        out_scroll.add(self.wv) 
        out_scroll.set_policy(gtk.POLICY_AUTOMATIC,gtk.POLICY_AUTOMATIC) 
        pane.add2(out_scroll) 
        #we will add buttons at the bottom 
        bbox = gtk.HButtonBox() 
        bbox.set_layout(gtk.BUTTONBOX_START) 
        #we need buttons! 
        savebutton = gtk.Button("Save") 
        savebutton.connect("clicked", self.save) 
        markdownbutton = gtk.Button("_Markdown",use_underline=True) 
        markdownbutton.connect("clicked", self.markdown) 
        #add the buttons to the bbox 
        bbox.pack_start(savebutton,False,False,0) 
        bbox.pack_start(markdownbutton,False,False,0) 
        #add the bbox to the box 
        box.pack_start(bbox,False,False,0) 
        winder.show_all() 
        self.read_default_file() 
    def run(self): 
        gtk.main() 
    def quit(self,widget=None): 
        self.save() 
        gtk.main_quit() 
    def markdown(self,widget): 
        text = self.get_buffer_text() 
        mdtext = markdown.markdown(text) 
        self.wv.load_html_string(mdtext,"file:///") 
    def read_default_file(self): 
        try: 
            f = open(default_file,"r") 
            text = f.read() 
            self.tb.set_text(text) 
            f.close() 
        except: 
            pass 
    def quit_accel(self,accel_group, acceleratable, keyval, modifier): 
        self.quit() 
    def save_accel(self,accel_group, acceleratable, keyval, modifier): 
        self.save() 
    def save(self,widget=None): 
        #get the text 
        text = self.get_buffer_text() 
        f = open(default_file,"w") 
        f.write(text) 
        f.close() 
    def get_buffer_text(self): 
        start_iter = self.tb.get_start_iter() 
        end_iter = self.tb.get_end_iter() 
        text=self.tb.get_text(start_iter,end_iter) 
        return text 
if `__name__`=="`__main__`": 
    a = application() 
    a.run() 

Usage

  • Ctrl+q quits the application
  • Ctrl+s saves the current document
  • Alt+m marks down the text and displays the HTML in the right pane

Note: the 'save' location is hard coded to be "markdown.txt" in the directory where the code is run from.

EDIT:the code for markdowner is now available on launchpad

Now quit reading, and go markdown some text.

Comments
2010-07-03 Jon Kulp:
Jezra, this is brilliant!! I use Markdown for all my course syllabi and some other stuff and this is a very cool preview mechanism for it. I normally use vim and then just run a shell script that converts it to html and displays in browser. I'll definitely be using this thing, man! Feature request: syntax highlighting! I have this in vim thanks to some guy who put together a syntax file and shared it on the web.
2010-07-03 jezra:
I only *start* the conversation... I mean code. It is up to others to add features. Well, this may turn into a project, and if so, you will be able to add your feature request to whichever source control system get used.
2010-09-18 yadon:
This looks quite intriguing, in the vein of what I'm looking for! Did you write this with Python 2.7 in mind, or will it only work on 2.6? I've never really used pyGTK or pywebkitgtk before, so I find these dependencies a bit confusing. Could you clarify a bit exactly what versions would be optimal? Thanks.
2010-09-18 Jezra:
yadon, there is no reason why the code shouldn't run with *almost* any of the 2..X python releases. It should also work with python 3, although I haven't tested it.

GTK is used for creating the graphical interface of the application, and Webkit is used for rendering the HTML that gets produced by the markdown module. Any version of these two libraries released in the last 2 years should be sufficient.
2010-09-18 yadon:
Thank you for clarifying. I'm quite new to Python, hence the confusion.

I'd appreciate if you could sort one more thing out for me. The libraries which you mention, GTK and Webkit respectively, are in actuality pyGTK and pywebkitgtk, or are there other modules for Python to use? Did you write this code with these specific modules in mind?
2010-09-18 jezra:
Python uses the pyGTK module to access the GTK library. It is the same with webkit. In a sense pyGTK is a bridge that allows python and GTK to interact.

There are various libraries that I could have used to create the interface and display the HTML but I prefer GTK and I use GTK for almost all of my interface needs.

If you have further questions, it would probably be more efficient to email them to me.
2010-11-30 Mark Stosberg:
How do open a pre-existing Markdown file?jj
2010-11-30 Mark Stosberg:
I figured it out. Nice tool!

###

4a5
> import sys
9c10,11
< default_file = "markdown.txt"
---
> # default to loading a file on the command line: ./markdowner.py file.mkd
> default_file = sys.argv[1]
2011-05-24 Anonymous:
sed 's/input_scroll.add_with_viewport/input_scroll.add/g'

If I read the documentation correctly, you add a text view to a scrolled window with add() since it has it's own native scrolling
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 5
Answer:
required
  • Tags:
  • Python
2010-05-27
Reflecting on the past
Working on heybuddy, I have learned a few things.
  • I probably should have used Vala
  • using threads in python isn't hard
  • using threads in a python GTK app isn't hard
  • Documentation for pygtk is not the greatest

Why Vala Had I written the program using Vala, Heybuddy would have been a compiled native application and would use less system resources. However, using Python certainly has its advantages; the most notable being that it is quite easy to move Heybuddy to a different architecture ( as long as the operating system on the new architecture has Python and GTK).
Heybuddy on x86, x86-64, ARM, PPC? No problem. Just copy the code and run it.

Threads For a few days, I was tearing out my hair trying to figure out how to properly use threads to fix a major bug in heybuddy.
The problem was this: When heybuddy would request data from the identi.ca server, the user interface would freeze until heybuddy was finished reading the data from the server.
The solution: use seperate threads to show the UI and read data from the server

Unfortunately using threads didn't work. Then I learned some stuff.
When using python threading with a GTK application, threads will not work without first calling the function gobject.threads_init() or gtk.gdk.threads_init().
One more time:
When using python threading with a GTK application, threads will not work without first calling the function gobject.threads_init() or gtk.gdk.threads_init().

This however, turned up a new problem. The server data reading thread was emitting signals for the main thread to process and this would sometimes happen when the main thread was updating the GUI and there would be a resulting crash of the application. The work around for this was to use gobject.idle_add( handle_the_signal_stuff ) which will allow the main thread to do the handling of the signal stuff when the main thread isn't busy and thus segfaults are averted. YEA!

links in labels Although it isn't in the pygtk pango reference it is possible to put links in a labels markup. For example:
label=gtk.Label() markup="Visit <a href='http://www.jezra.net'>Jezra's Website</a>" label.set_markup(markup)

This will put a clickable link in the text of a GTK label. Sweet!

OK, that's enough for me. I have some bugs to fix.

Now quit reading, and go write something.
Comments
2010-05-28 senshikaze:
theards aren't hard per se, just a pain in the ass, and you are right. no where, and I mean NO WHERE in documentation(that i could find) was the whole threads.init() mentioned, and only a few stackoverflow posts mentioned it.
question: why do they have two? is one going to be deprecated in later versions? I know I got a depreciated warning, not on the init.threads, but when trying to force the UI into a separate thread (not the way to go).
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
6 plus 7
Answer:
required
  • Tags:
  • Python
  • Heybuddy
2010-05-16
It's a new project!

For the past few months, I've been using indenti.ca to micro-blog and while there are plenty of applications available to access the identi.ca data from my laptop or desktop, I couldn't find anything that would run well on my Nokia N810 pocket computer.

Originally, I had planned to write a client in Vala, but since there is already a wonderful Vala denting client named Pino and since I hadn't written anything in Python for a while, I decided to use Python and develop a client in such a way that it might be easy to port to other languages: specifically Vala and possibly Java.

A couple of hours here and there with Geany and I had a pretty decent denting client in need of a name. Luckily I have a buddy who, aside from needing to blog about his photos, needs a denting client named after him.

Hey Buddy! I wrote an identi.ca client and named it after you. Well, sort of.... it's called heybuddy



In the wild

Jake Hume (http://blog.fragdev.com/) took this picture of heybuddy running on his N810. Sweet!

Although I don't have any plans for making packages of heybuddy for various Linux distributions, fellow Linux Outlaw and heybuddy user timttmy has created a heybuddy AUR package for Arch Linux



What I've learned
  • simple little projects are rarely simple or little
  • I still can't get threading to work in a python app
  • there is a correct way to authenticate an identi.ca user via the urllib2 python module, the short cut I tried to take showed me the error of my ways
  • the documentation for gtk.keysyms seems to be non-existent
  • The majority of heybuddy user's live in apple growing country
  • I ♥ scrumpy

Now quit reading, and go say "hey buddy" to your buddy!
Comments
2010-05-17 senshikaze:
i got an identica account just so i can play with your program ;)
clean. a little... gray. but clean.
2010-05-17 jezra:
What's wrong with gray? It works with light and dark themes.
2010-05-18 mjjzf:
Playing with Python packaging. Maybe I can knock up a Slackbuild.
2010-05-18 Jezra:
That would be wonderful! If you need anything from me for packaging, please let me know.
2010-05-18 mjjzf:
Sorry, all that's missing is a little extra 1337-ness. I guess it should be up and running at the release of Slackware 13.1.
2011-02-05 Anonymous:
i have time lol
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
6 minus 5
Answer:
required
2010-03-15
What I wanted was a simple way to generate a very basic sitemap file that would conform to the Sitemap protocol. Fortunately, I couldn't find something to do it for me so I had to write a python application to do it for me. But wait! Why stop there?

Since the application is basically a site crawler, I might as well add some feature bloat by checking for link errors and gathering some fairly useless information about the crawled site. To make this possible I utilized the OptionParser class from the optparse library so that I could pass in various command-line arguments to affect the way the app outputs data and to choose which URL to parse.

Without further ado... enter the Python:
#!/usr/bin/env python import sys,urllib,re,os from optparse import OptionParser from HTMLParser import HTMLParser class Parser(HTMLParser):   def __init__(self,verbose=False):     HTMLParser.__init__(self)     self.clear()     self.verbose verbose   def handle_starttag(selftagattrs):     # we only care about 'a' tags     if tag=="a":       href self.get_value_from_tuple_list("href",attrs)       if href!=None:         if self.verbose:           print "found href: "+href         if not href in self.hrefs:           self.hrefs.appendhref )              elif tag=="base":       href self.get_value_from_tuple_list("href",attrs)       if self.verbose:           print "found base href: "+href       if href!=None:         self.base_href=href            def get_hrefs(self):     return self.hrefs        def get_base_href(self):     return self.base_href   def get_value_from_tuple_list(self,target_key,list):     for (key,valuein list:       if key==target_key:         return value     return None      def clear(self):       self.hrefs=[]       self.base_href="" class JayWalker():   browsable_files =[]   valid_links = {}   errors = {}   processed_urls =[]   depth 0   file_links = {}   current_file_identifier=""   webpage_extensions = ["htm","html","php","asp","jsp","py",""]   def __init__(self,options):     self.options=options        def get_extension(self,str):     #remove the start_url from the string     s_string str.replace(self.options.start_url,"")     splits s_string.split(".")     if len(splits)>1:       return splits[-1]     else:       return ""          def walk(self):     #process the start_url     self.process_url(self.options.start_url)          '''we are done, what sort of output should there be?'''     #does the user want sitmap data?     if self.options.sitemap:       sm_text="<?xml version=\"1.0\" encoding=\"UTF-8\"?>"       sm_text+="<urlset xmlns=\"http://www.sitemaps.org/schemas/sitemap/0.9\">\n"       if len(self.browsable_files)>0:         for in self.browsable_files:           sm_text+="\t<url>\n"           sm_text+="\t\t<loc>%s</loc>\n" % (f           sm_text+="\t</url>\n"         sm_text+="</urlset>\n"     if self.options.outdir !=None:       dir self.options.outdir       if not os.path.exists(dir):         os.makedirs(dir)       #make the errors       errors_path os.path.join(dir,"errors.txt")       file open(errors_path,"w")       if len(self.errors)>0:         for key in self.errors.keys():           file.writekey +"\n")           for page in self.errors[key]:             file.write"\t%s\n" % ( page ) )       else:         file.write("no errors")       file.close()       #make the errors       links_path os.path.join(dir,"links.txt")       file open(links_path,"w")       if len(self.valid_links)>0:         for key in self.valid_links.keys():           file.writekey +"\n")           for page in self.valid_links[key]:             file.write"\t%s\n" % ( page ) )       else:         file.write("no links")       file.close()       #make the browsable files       browsables_path os.path.join(dir,"browsables.txt")       file open(browsables_path,"w")       if len(self.browsable_files)>0:         for in self.browsable_files:           file.write("\t%s\n" % (f) )       else:         file.write("no browsables")       file.close()       #are we making a sitemap?       if self.options.sitemap:         sitemap_path os.path.join(dir,"sitemap.xml")         file open(sitemap_path,"w")         file.write(sm_text)         file.close()     else:       print "--ERRORS--"       if len(self.errors)>0:         for key in self.errors.keys():           print key           for page in self.errors[key]:             print "    %s" % ( page )       else:         print "no errors"              print "--VALID LINKS--"       if len(self.valid_links)>0:         for key in self.valid_links.keys():           print key           for page in self.valid_links[key]:             print "    %s" % ( page )       else:         print "no valid links"              print "--BROWSABLE FILES--"        for in self.browsable_files:         print "    "+f              if self.options.sitemap:         print "--SITEMAP--"         print sm_text        def process_url(self,url,parent="root",depth=0):          #add the url to our processed list     self.processed_urls.append(url)          self.file_links[url]=[]     if self.options.be_verbose:       print "processing "+url          #is this a mailto?     if url.startswith("mailto:"or url.startswith("MAILTO:"):       if self.options.be_verbose:         print "found mailto "+url       #todo: record the mailto       return          #is this a link to an anchor?     if url.startswith("#"):       if self.options.be_verbose:         print "found anchor link "+url       #todo: record the mailto       return          #get a handle for the opened url file     try:       fh urllib.urlopen(url)       #what code did we get?       code intfh.getcode() )       if code>400:         #record this error         if not self.errors.has_keyparent ):           self.errors[parent] = []         self.errors[parent].append(url)                  if self.options.be_verbose:           print "error %d" % ( code )         return       else:         #record this good link         if not self.valid_links.has_keyparent ):           self.valid_links[parent] = []         self.valid_links[parent].append(url)         if self.options.be_verbose:           print code              #check the file extension       ext self.get_extension(url)       if not ext in self.webpage_extensions and ext!="":         if self.options.be_verbose:           print "based on extension, we won't parse this file"         return              #check the content type, we only want html       file_info fh.info()       content_type file_info.getheader("Content-type")       if content_type.endswith("html"):         #this is good         pass       else:         if self.options.be_verbose:           print "bad content type: "+content_type         return       #this is a browsable file       self.browsable_files.append(url)         #read the text from the file       file_text ""       readfile True       while(readfile):         temptext fh.read(1028)         file_text+=temptext         if temptext==None or temptext =="":           readfile False       fh.close()       if self.options.be_verbose:         print "reading "+url              Parser(self.options.be_verbose)       p.feed(file_text)              self.file_links[url] = p.get_hrefs()       base_href=p.get_base_href()            except Exception as inst:       print type(inst)     # the exception instance       print inst.args      # arguments stored in .args       print inst           # __str__ allows args to printed directly       if self.options.be_verbose:         print url+" is not a valid file"       if not self.errors.has_key(parent):         self.errors[parent] = []       self.errors[parent].append(url)       return          if self.options.be_verbose:       if len(self.file_links[url])>0:         print "--unique links--"         for link in self.file_links[url]:           print "  "+link       print "processed "+url          #loop through the links     for link in self.file_links[url]:       parse_link=None;       # if the url isn't absolute       #TODO: find a better way to process anchored links       if re.match"^[A-Za-z]+:",link)==None and not link.startswith("#"):         # is there a base href?         if base_href!=None and base_href!="":           parse_link self.get_absolute_url(link,base_href,True)         else:           parse_link self.get_absolute_url(link,url)                else:         #todo: check if this is an Absolute path on the site         pass                if parse_link!=None:         if not parse_link in self.processed_urls:           print parse_link           self.process_url(parse_link,url)        def get_absolute_url(self,url,parent,parent_is_base_href=False):     #determine the parent's directory     bits parent.split("/")     if not parent_is_base_href and parent.count("/")>2:       del(bits[-1])     #how may ../ are in the url?     up_count url.count("../")     #remove that many dirs from the parent bits     for in range(up_count):       print x       sys.exit()       del(bits[-1])     #remove the ../ from the url     url.replace("../","")     parent_root "/".join(bits)          if url.startswith("/") :       return_url parent_root+url     else:       return_url parent_root+"/"+url     return return_url           class Application:   def __init__(self):     #we need an option parser     self.opt_parser OptionParser()     #what do we need from the user?     self.opt_parser.add_option("-u""--url"dest="start_url",       help="The URL to start processing"metavar="URL")     self.opt_parser.add_option("-o""--out"dest="outdir",       help="save output to a specific directory"metavar="dir")     self.opt_parser.add_option("-s""--sitemap",                   action="store_true"dest="sitemap"default=False,                   help="include generic sitemap data in the output")     self.opt_parser.add_option("-v""--verbose",                   action="store_true"dest="be_verbose"default=False,                   help="display what is going on")        def run(self):     #parse the options     (optionsargs) = self.opt_parser.parse_args()     #if there is no url,we quit     if options.start_url==None:       print "ERROR: You did not specify a URL to process"       print ""       sys.exit()     #we need a JayWalker     jw JayWalker(options)     jw.walk() if __name__=="__main__":   app Application()   app.run()

On my computer this file is named "walk.py", and to make a sitemap for my website I run
./walk.py -u http://www.jezra.net -s -o output
The command makes a directory named "output" and creates files with information about my site as well as a sitemap file named "sitemap.xml", which I then upload to my website.

Well there you have it. Copy, edit, learn, or ignore.

Now quit reading, and go crawl with Python.
Comments
2010-03-15 James:
Nice idea. I'm going to take this and build a little bit on it. Interested in feedback?
2010-03-16 jezra:
James, since the code does what I need it to do, I'm very interested in how you change the code to do what you need it to do.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
0 plus 1
Answer:
required
  • Tags:
  • Python
2009-12-25
Recently, I've been getting a lot of hits from people searching for "pyclutter examples". Unfortunately, the code that most people find is my hello world example that was written for Clutter 0.6 and since the API for Clutter has change quite a bit since I wrote that code, the code doesn't work with the latest stable release of Clutter.

I've considered updating the code to work with the more modern Clutter, but that wouldn't really be fun would it? Besides, someone has already updated the code. Thanks Ryan. Why don't I write another example? Don't mind if I do.

There is nothing new Clutter-wise in this example to differentiate it from the hello world example except that this code will run with the latest Clutter.


What we have here, is John Conway's Game of Life where each cell is a Clutter Rectangle.
#!/usr/bin/env python # copyright 2009 jezra lickter #this software is licensed under the GPLv3  http://www.gnu.org/licenses/gpl.txt import clutter import random class Game:     def __init__(self,cell_size):         self.cells = []         #create a clutter stage         self.stage clutter.Stage()         #set the stage size in x,y pixels         self.stage.set_size(500,500)         self.stage.set_title("The Game of Life")         self.stage_widthself.stage_height self.stage.get_size()         #define some clutter colors in rgbo (red,green,blue,opacity)         self.color_black =clutter.Color(0,0,0,255         self.color_green =clutter.Color(0,255,0,255)         self.color_blue =clutter.Color(0,0,255,255)         #set the clutter stages bg color to our black         self.stage.set_color(self.color_black)         #we will need to check on the key presses from the user         self.stage.connect('key-press-event'self.parseKeyPress)         self.stage.connect("destroy"self.quit)         '''fill the screen with rectangles'''         num_cols int(self.stage_width/cell_size)         num_rows int(self.stage_height/cell_size)         cells_to_create num_cols*num_rows         0         for row in range(num_rows):             0             self.cells.append( [] )             print "%i cells to create" % (cells_to_create)             for column in range(num_cols):                 #set the column index to a rect that is cell_size by cell_size                 self.cells[row].appendclutter.Rectangle() )                 self.cells[row][column].set_size(cell_size,cell_size)                 self.cells[row][column].set_position(x,y)                 #add this cell to the stage                 self.stage.add(self.cells[row][column])                 self.cells[row][column].set_color(self.color_green)                 self.cells[row][column].set_opacity(0)                 x+=cell_size             cells_to_create-=num_cols                          y+=cell_size         print "cells created"         #get some data about the rows         self.row_count  len(self.cells)         self.col_count len(self.cells[0])              self.cell_count self.row_count*self.col_count         #make a timer to do stuff for us         self.timeline clutter.Timeline(duration=500)         #when the timer finishes running, we want to determine the life         self.timeline.connect("completed",self.compute_life)         #increase linearly         alpha clutter.Alpha(self.timelineclutter.LINEAR)         #make some opacity behaviours that we will apply to the cells         self.dieBehaviour clutter.BehaviourOpacity(255,0,alpha)         self.aliveBehaviour clutter.BehaviourOpacity(0,255,alpha)              def parseKeyPress(self,actor,event):         if event.keyval == clutter.keysyms.q:             self.quit()         elif event.keyval == clutter.keysyms.c:             self.compute_life()         elif event.keyval == clutter.keysyms.r:             self.reseed()          def quit(self,widget=None):         clutter.main_quit()          def run(self):         self.stage.show_all()         self.seed_life()         self.compute_life()         clutter.main()              def reseed(self):         for in range(self.row_count):             for in range(self.col_count):                 if self.cells[r][c].get_opacity()>0:                     self.cells[r][c].set_opacity(0)         self.seed_life()              def seed_life(self):         print "adding random life"         #loop through the rows         for in range(self.row_count):         #loop through the columns             for in range(self.col_count):                 #generate a number                 random.randint(0,4)                 if i==0:                     # x,y is alive                     self.cells[r][c].set_opacity(255)              def compute_life(self,asset=None):         #detach all objects from the behaviors         self.dieBehaviour.remove_all()         self.aliveBehaviour.remove_all()         for in range(self.row_count):             for in range(self.col_count):                 if self.cells[r][c].get_opacity()>0:                     alive=True                 else:                     alive=False                 ln self.living_neighbors(r,c)                 if alive and ln<or ln>):                     #this cell will die; boohoo                     self.dieBehaviour.apply(self.cells[r][c] )                 elif not alive and ln==3:                     #this cell is now alive; damn zombies                     self.aliveBehaviour.apply(self.cells[r][c] )                                      #start the timeline again         self.timeline.start()                      def living_neighbors(self,r,c):         alive 0         r_min r-1         r_max r+1         c_min c-1         c_max c+1         if r==:             r_min=r         elif == self.row_count-1:             r_max=r         if c==:             c_min=c         elif == self.col_count-1:             c_max=c                      tr r_min         tc c_min         cells_computed=0         while not tr r_max:             tc c_min             while not tc c_max:                 if tr!=or tc!=):                     cells_computed+=1                     if self.cells[tr][tc].get_opacity()>0:                         alive+=1                 tc+=1             tr+=1         return alive          if __name__=="__main__":     game Game(5)     game.run()

When running the code: press "q" to quit, press "r" to reset.

One issue I have with this code is that when creating the rectangles for the cells, creation slows down significantly after creating about 3000 rectangles. The app runs fine when all of the cells are created, but the slow down during creation really irks me. Perhaps I need more RAM or I should write the code in Vala using SDL?

Now stop reading, and go play with colored rectangles.
Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
6 minus 4
Answer:
required
2009-12-18
Damn, I burnt my oatmeal again.
The concept of time is not something I have a great abundance of (anyone that has heard me play music can probably attest to this) and, quite often, I fail to be mindful of the time whilst cooking some delicious steel cut oats for breakfast.

If only there was some way to be reminded to check the stove after a certain amount of time; without having to purchase a Talkie Toaster. Yes, I know all about wind-up egg timers, but mine broke a week or four ago.

The Software Solution
In a nutshell, I needed a timer that will alert me when a set amount of time has expired. The easiest way to do this is to ssh into my (almost) always on media player machine and run
sleep 240; espeak "Hey Jezra, it is time to check the oatmeal"
Yea, it works, but what if I don't want to maintain an ssh session? It would be so much nicer if ,on my local machine, I could just run a script to send the time and text to the media player machine. Espeak, by the way, is a nice text to speech application.

Enter the Python
Using Python socket programming I wrote two pieces of software; one to listen and parse commands, and one to send commands from a user.

First is the code for the listener, which is launched at system startup
#!/usr/bin/env python import socket import subprocess import sys HOST '' #we don't care about the specific hostname               PORT 5000 #pick a port       '''define the Server class'''         class Server:     def __init__(self,hostport):         #create and bind to a socket         self.sock socket.socket(socket.AF_INETsocket.SOCK_STREAM)         self.sock.bind((HOSTPORT))         self.sock.listen(1)              def run(self):         waiting True         data=""         #get a connection created when the socket accecpts input         connaddr self.sock.accept()         print "connected"         while waiting:             # read a 4K of data from the connection              data conn.recv(4096)             if not data:                 # if there is no data, then we end the loop                 waiting False             else:                 print "received %s" % (data)                 #parse the data we have received                 result self.parse_data(data)                 #send a response to the sending client                 conn.send(result)         #close the connection         conn.close()         #run again to create a new connection         self.run()              def parse_data(self,data):         try:             #split the data into the seconds to delay, and the string to speak             seconds,string data.split(":")             #what command do we need to perform?             command "sleep %s; espeak \"%s\"" % (seconds,string)             #run the command             subprocess.Popen(command,shell=True)             return_string "OK"         except:             return_string "Error with data: %s" % (data)         print return_string         return return_string                   if __name__ == "__main__":     server Server(HOST,PORT)     server.run()


On to the sender
#!/usr/bin/env python import socket import sys #what happens where there is an error? def quitmessage ):     print message     sys.exit() #do we have enough args? if len(sys.argv)==2:     string sys.argv[1]     seconds "0"      elif len(sys.argv)==3:     string sys.argv[2]     seconds sys.argv[1]      else:     quit("sender requires 1 or 2 arguments, you passed %i" % (len(sys.argv)-1) )      #if we haven't quit, connect and send the data HOST 'player'  #what is the name/IP of the server we need to connect to PORT 5000  # what port are we connecting on? #create a socket sock socket.socket(socket.AF_INETsocket.SOCK_STREAM) #connect to the host:port sock.connect((HOSTPORT)) #format and send data sock.send("%s:%s" % (seconds,string) ) #get some data back data=sock.recv(4096) sock.close() print data
The sender script was named ttsend and it resides in my ~/bin directory. Now when I'm cooking my oatmeal, I enter the command
ttsend 240 "It is time to check the oatmeal"
Since I will probably be more interested in timing things by the minute than by the second, an update to take minutes as input seems to be in order.

Now stop reading, and CHECK THE OATMEAL!
Comments
2009-12-23 loopo:
Hi there Jezra. Nice work! Just getting into python myself. Enjoyed your Gimp post on Linux Outlaws.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
3 plus 8
Answer:
required
  • Tags:
  • Python
2009-11-28
Recently, after completing a web-based project, it was necessary to take multiple screenshot of the various states of the project's interface for a "take the tour" feature. Under normal circumstances, I would use the import command from imagemagick to take a screenshot but for some reason, import wouldn't capture the text on some form elements in firefox.

Off I went to find a screen capture app that does what I need, and after a minute of searching my distribution's software repository, and I had found, installed, and tested scrot (screen shot).

The icing on the cake would be to have a simple way to created incremented screenshots, so that I could end up with a set of images named
screenshot.png, screenshot001.png, screenshot002.png, screenshot003.png, etc.

Enter the python...
#!/usr/bin/env python import os.path import subprocess '''start defining some variables''' file_name "screenshot" # the base file name file_extension "png" # the image type save_path os.path.expanduser("~/"#where are we saving the screen shots increment #we may need to increment the file names save_file "%s.%s" % (file_name,file_extension# the default screen shot name #does the save file exist? while os.path.isfilesave_file ):     #the file exists, add and increment to the file name     increment+=1     if(increment10):         increment_string "00%i" % (increment)     elif(increment<100):         increment_string "0%i" % (increment)     else:         increment_string "%i" % (increment)     save_file "%s%s.%s" % (file_name,increment_string,file_extension) path_to_save_file os.path.join(save_path,save_file)    #what command do we need to run? command "scrot -q 100 %s" % ( path_to_save_file ) #run the command subprocess.call(command,shell=True)

Good and good. The script is named "screenshot", is in my ~/bin folder, and is keybound in my window manager to the "Print" key.

What about screen shots of opened drop-down lists? good question.
Bound to the keyboard combo of "Alt+Print" is the command
sleep 2; screenshot
which will delay for 2 seconds, which is enough time to open the dropdown, and then run screenshot.

As an added feature, scrot will beep when it captures the screen, so it is easy to tell when the delay has ended.
Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 8
Answer:
required
  • Tags:
  • Python
2009-10-28
No one likes a quitter; especially when that one is me, and the quitter is the network connection of my media playing computer.

The media computer is connected to both my television and stereo, and runs MPD, gpodder, mplayer, and sap. For the most part access to the machine is via SSH.

Every now and then, for reasons I have yet to determine, the wireless network connection of my media player drops, and it is damn frustrating when I can't ssh to my media player.

If only there was a way to test the machines network connection and restart the connection if the network is down....
Time for some Python
#!/usr/bin/env python import subprocess '''declare some variables''' test_ip "192.168.1.1" ping_query "ping -c 1 %s >/dev/null" % ( test_ip ) netup_command "netcfg -r HomeNetworkConfig" #try to ping the test ip address ping_result subprocess.call(ping_queryshell=True) #a result of 0 is good, a result of 1 means no network if ping_result != 0:     #try to bring up the network     subprocess.call(netup_command,shell=True)     subprocess.call("beep",shell=True)

This python script runs every 2 minutes and pings my router. If the network is up, the result of the ping command will return 0. If something other than zero is returned, a command is run that will bring up the network connect.

Now stop reading, and go do whatever is that you do.
Comments
2009-10-29 Anonymous:
Nasty work around...
2009-10-29 jezra:
If you have a better solution, please feel free to share.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
3 plus 4
Answer:
required
  • Tags:
  • Python
2009-09-05
While programming the rotary phone input device, I needed a way to play the latest version of one of my favorite audcasts(yea, I said audcast because it is an audio broadcast and I don't have an ipod). OK enough of that, let's get back to the problem at hand. The audcast uses RSS to publish new episodes of their show, and since an RSS feed is a standarized XML file, I should be able to get all the info I need fairly easily.

The basic functionality that I needed was:
  • get the XML text from the URL of the RSS feed
  • process the XML text and get the URL of the first audio file in the XML
  • play the audio file over the internet; fortunately, I know of a great command-line audio player that can do that. Mplayer will do that as well.

There are some people with powerful command-line foo who could probably write a five line bash script with wget and awk to do this, but I'm not one of those people so I decided to use the Python programming language and the code is as follows:
#!/usr/bin/env python import sys #we will need to parse xml from xml.dom import minidom #we need to get files from the interpipesnetweb import urllib #we want to run a subprocess import subprocess #define the parser class class RSSParser():     def __init__(self,url):         self.rssUrl url     def getLatest(self):         try:             print "checking "+self.rssUrl+"..."             #read the text from the URL             rsstext urllib.urlopen(self.rssUrl)             print "parsing..."             #parse the xml from the rss text             xmlDocElement minidom.parse(rsstext).documentElement             #make an array of the enclosure elements             enclosures xmlDocElement.getElementsByTagName("enclosure")             #get the first enclosure             enclosure enclosures[0]             #get the url from the first enclosure             audio_url enclosure.attributes['url'].value             return audio_url         except:             return ""      if __name__=="__main__":     #set a default rss url     rss_url "http://feeds.feedburner.com/linuxoutlaws-ogg"     #what shall we use to play the audio?     audio_player "sap"     # has the user requested a different url?     if len(sys.argv) > 1:         rss_url sys.argv[1]     #make a parser     parser RSSParser(rss_url)     #get the file we want to play     file_to_play parser.getLatest()     #did a file get returned?     if len(file_to_play) > 0:         #launch the player with the file to play         subprocess.call([audio_player,file_to_play])

Well there you have it. Read it, change it, use it.
Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 4
Answer:
required
  • Tags:
  • Python
2009-06-28
Pyjamas, in the most basic of terms, is a port of the Google Web Toolkit (which uses Java), to Python. What does this really mean? according to the Pyjamas website: pyjamas is a stand-alone python to javascript compiler, an AJAX framework / library and a Widget set API.

With Pyjamas, a developer can write a Python application that contains buttons, labels, and various other common widgets; and then compile the application to a web ready pile of javascipt code. Since the javascript code handles all of the cross-browser crap, the developer can focus on writing a decent app and not have to worry about all of the various browser idiosyncrasies.

As an example, I have created a very basic Pyjamas app that will do the following:

The python code is
from pyjamas.ui.RootPanel import RootPanel from pyjamas.ui.Panel import Panel from pyjamas.ui.VerticalPanel import VerticalPanel from pyjamas.ui.HorizontalPanel import HorizontalPanel from pyjamas.ui.HTML import HTML from pyjamas.HTTPRequest import HTTPRequest from pyjamas.ui.Label import Label from pyjamas.ui.ListBox import ListBox #what do we need to process returned XML? #make a XMLHTTPRequest handler to process the dictionary XML class processDictionaryXMLHandler:     def __init__(self,caller):         #who called this class? (AKA parent)         self.caller caller     def onCompletion(selfxml):         self.caller.set_message("processing data...")         '''process the text'''         elements xml.getElementsByTagName("element")         for in range(elements.length ):             element elements.item(i)             word self.getChildNodeValue(element,"word")             desc self.getChildNodeValue(element,"desc")             self.caller.add_dictionary_item(word,desc)             self.caller.set_message("Adding "+word)         self.caller.update_list()         self.caller.set_message("")     def onError(selftextcode):         self.caller.set_message("error %s %s"  % (code,text))     def onTimeout(selftext):         self.caller.set_message("timeout %s"  text)     #get the value of a nodes child     def getChildNodeValue(self,node,childname):         childNode=node.getElementsByTagName(childname).item(0)         if childNode.firstChild.nodeValue != null:             value childNode.firstChild.nodeValue         else:             value=""         return value     #get the value of a node     def getNodeValue(self,node):         value node.firstChild.nodeValue         return value #define devils dictionary application class class ddApplication:     def __init__(self):         '''make some global variables'''         #we need a dict type to hold key/value pairs for words         self.words_dict = {}         #create a vertical panel to hold most of our infoself.vPan = verticalPanel()         self.vPan VerticalPanel()         #make a label for "devils dictionary         title Label("The Devil's Dictionary (L through Z)")         #give credit to the author         by Label("by Ambrose Bierce")         #give titles to the labels so we can style them with a CSS         title.setStyleName("dd_title")         by.setStyleName("dd_by")         #add the labels to the vertical panel         self.vPan.add(title)         self.vPan.add(by)         #create a listbox to display the words         self.wordsListBox ListBox()         #make the wordslistbox a dropdownlist         self.wordsListBox.setVisibleItemCount(20)         #hide the wordslistbox         self.wordsListBox.setVisible(False)         #what should happen when the wordslistbox changes?         self.wordsListBox.addChangeListener(getattr(self,"word_list_changed") )         #add the wordsListBox to the vertical panel         #self.vPan.add(self.wordsListBox)         #make an HTML text thingy to hold the definition of the word         self.definitionHTML HTML("")         #hide the definition         self.definitionHTML.setVisible(False)         #add the definition to the vert panel         #self.vPan.add(self.definitionHTML)         hPan HorizontalPanel()         #add some spacing for the hPan elements         hPan.setSpacing(10)         hPan.add(self.wordsListBox)         vPan2 VerticalPanel()         self.wordLabel Label("")         vPan2.add(self.wordLabel)         vPan2.add(self.definitionHTML)         hPan.add(vPan2)         self.vPan.add(hPan)         #make a label to pass messages to the user in case of a problem         self.messageLabel Label()         #set the style for the messageLabel         self.messageLabel.setID("info_label")         #add the messageLabel to the vert panel         self.vPan.add(self.messageLabel)         #add the vertical panel to a specific div of the template         RootPanel("content").add(self.vPan)     def word_list_changed(self):         #get the current index of the wordslistbox         selected_index self.wordsListBox.getSelectedIndex()         word self.wordsListBox.getValue(selected_index)         self.display_definition(word)              def set_message(self,message):         self.messageLabel.setText(message)              def display_definition(self,word):         self.wordLabel.setText(word)         definition self.words_dict[word].strip()         self.definitionHTML.setHTML("<pre>"+definition+"</pre>")         self.definitionHTML.setVisible(True)     def add_dictionary_item(self,word,description):         self.words_dict[word]=description              def update_list(self):         for key in self.words_dict.keys():             self.wordsListBox.addItem(key)         #display the wordListBox         self.wordsListBox.setVisible(True)     def run(self):         self.get_dictionary_xml()     def get_dictionary_xml(self):         self.set_message("retrieving XML data...")         HTTPRequest().asyncGet(None,None,"dictionary.xml",processDictionaryXMLHandler(self),1); if __name__ == '__main__':     app ddApplication()     app.run()

Looks like a typical Python app to me!

The compiled web application is viewable at www.jezra.net/code_examples/pyjamas_devil/index.htm

As with most things, there were a few gotcha's that I ran into and I had a devil of a time finding the solutions. The first, and in my mind the most important gotcha is that when making an HTTPRequest().asyncPost(), one needs to add "1" as the last argument if the returned data is XML. If the "1" is omitted, the returned data is treated as plain text and any attempt to process the data as XML will fail.

The second biggest gotcha also deals with XML. Since there is no discernable documentation for processing data from returned XML, one might need to write custom functions, based on the few available Pyjamas examples that deal with HTTPRequests, for retrieving data from a chunk of XML. Although this isn't really that major of an issue, but it would be more efficient to use native XML processing and not have to reinvent the wheel.

All things considered, I certainly plan to use Pyjamas in the future whenever I need to write an asyncronous web-app.
Comments
2010-08-21 Rick:
I'm not sure what you mean by "retrieving data from a chunk of XML", but this sounds like some sort of parsing to me and in my opinion this doesn't belong on the client side, javascript is a quirky weird langauge for doing "real programming stuff" as I have found at times in the past, you should use the server side python for any parsing like this and have it send the data to the client side in as controlled a manner as possible, just trying to make javascript (its still converted to javascript at the end from your python code) parse random XML, etc sounds like a recipe for disaster to me
2010-08-21 jezra:
Rick, I was indeed referring to XML processing, which I've never really like in any language. However, by transferring data as XML, the data is being formatted and transferred in a very controlled manner and this allows me to programmatically retrieve the data that I need.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
7 minus 6
Answer:
required
  • Tags:
  • Python
2009-05-10
Quite a while ago, I began playing with the clutter library as a way to move and display graphics on a fullscreen application interface. With the 0.8 release of the clutter library, the fullscreen function didn't seem to work properly anymore and I was rather perturbed. Instead of taking up the entire screen, a "fullscreened" app would behave as though one had maximized a window. However, earlier today I revisited some python code that uses pyclutter to access the clutter library and I came up with a hack that will solve the fullscreen problem and actually redefines the problem in my eyes.
First, let me show some code that does not work as expected:
#!/usr/bin/env python import clutter class test:     def __init__(self):         #create a stage         self.stage clutter.Stage()         #set the stage to be fullscreen         self.stage.fullscreen()         #show the screen         self.stage.show_all()         #get the key presses         self.stage.connect('key-press-event'self.quit)         #start the clutter main loop         clutter.main()     def quit(self,object,event):         #quit the main loop         clutter.main_quit() if __name__=="__main__":     #make an instance of the test     test()

After trying different various possible fixes, it appeared that the bug is actually
"the stage fullscreen function behaves like maximize if used before the clutter main loop".
What does that mean? It means that the fullscreen function will work if the function is called after the clutter main() function. But how does one call a function after a mainloop has been initialized? I'm glad you asked.

Check out this code with the fix in place:
#!/usr/bin/env python import clutter import gobject class test:     def __init__(self):         #create a stage         self.stage clutter.Stage()         #show the screen         self.stage.show_all()         #get the key presses         self.stage.connect('key-press-event'self.quit)         #add a fullscreen function to the gobject timeout         gobject.timeout_add(10,self.go_fullscreen)         #start the clutter main loop         clutter.main()     def go_fullscreen(self):         self.stage.fullscreen()     def quit(self,object,event):         #quit the main loop         clutter.main_quit() if __name__=="__main__":     #make an instance of the test     test()

Since the clutter library uses Gobject objects as base classes for most clutter classes, including the mainloop, one can use gobject.timeout_add() to call a function after a set amount of time. In the fixed code, a function that calls fullscreen was set to run a few milliseconds after the clutter.main() was called.

Now I need to port the code to Vala and make sure the fix works with the Vala Clutter bindings as well.
Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
8 minus 3
Answer:
required
2009-02-07
A few years ago, I converted a Nintendo NES gamepad to a USB gamepad, using a hardware chip from Raphnet, in order to control an MPD frontend that I wrote using Pygame. Fast forward to the present...... While working on my new media player application, I needed a way to receive joystick events but I didn't want to include the Pygame library just for joystick support. Thus was born the need for a python gobject joystick class.
Where is it? I looked all over the inter-tubes and couldn't find what I needed.

A bit of the old "clickity click click" on the keyboard ( a quite a bit on documentation reading ) and I came up with:
''' Copyright 2009 Jezra Lickter This software is distributed AS IS. Use at your own risk. If it borks your system, you have been forewarned. This software is licensed under the LGPL Version 3 http://www.gnu.org/licenses/lgpl-3.0.txt for documentation on Linux Joystick programming please see http://www.mjmwired.net/kernel/Documentation/input/joystick-api.txt ''' import gobject #needed for sending signals import struct #needed for holding chunks of data class Joystick(gobject.GObject): '''The Joystick class is a GObject that sends signals that represent Joystick events''' EVENT_BUTTON = 0x01 #button pressed/released EVENT_AXIS = 0x02 #axis moved EVENT_INIT = 0x80 #button/axis initialized #see http://docs.python.org/library/struct.html for the format determination EVENT_FORMAT = "IhBB" EVENT_SIZE = struct.calcsize(EVENT_FORMAT) # we need a few signals to send data to the main '''signals will return 4 variables as follows: 1. a string representing if the signal is from an axis or a button 2. an integer representation of a particular button/axis 3. an integer representing axis direction or button press/release 4. an integer representing the "init" of the button/axis ''' __gsignals__ = { 'axis' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,gobject.TYPE_INT,gobject.TYPE_INT)), 'button' : (gobject.SIGNAL_RUN_LAST, gobject.TYPE_NONE, (gobject.TYPE_INT,gobject.TYPE_INT,gobject.TYPE_INT)) } def __init__(self,dev_num): gobject.GObject.__init__(self) #define the device device = '/dev/input/js%s' % dev_num #error check that this can be read try: #open the joystick device self.device = open(device) #keep an eye on the device, when there is data to read, execute the read function gobject.io_add_watch(self.device,gobject.IO_IN,self.read_buttons) except Exception,ex: #raise an exception raise Exception( ex ) def read_buttons(self, arg0='', arg1=''): ''' read the button and axis press event from the joystick device and emit a signal containing the event data ''' #read self.EVENT_SIZE bytes from the joystick read_event = self.device.read(self.EVENT_SIZE) #get the event structure values from the read event time, value, type, number = struct.unpack(self.EVENT_FORMAT, read_event) #get just the button/axis press event from the event type event = type & ~self.EVENT_INIT #get just the INIT event from the event type init = type & ~event if event == self.EVENT_AXIS: signal = "axis" elif event == self.EVENT_BUTTON: signal = "button" if signal: print("%s %s %s %s" % (signal,number,value,init) ) self.emit(signal,number,value,init) return True if __name__ == "__main__": try: j = Joystick(0) loop = gobject.MainLoop() loop.run() except Exception,e: print(e)

It still needs a bit of work, not all of the button/axis init signals are emitted when the class is started. The remaining init signals are sent on the first button press or axis movement. All in all, it fits my needs wonderfully.
Comments
2010-11-13 Anonymous:
Awesome and thank you. Not only useful for joysticks, but has opened my eyes to signals and the power of the gobject class.
2011-01-09 Anonymous:
Awesame, many thanks.
2011-02-11 John Crisp:
Like this but I am trying to do it on a time base so rather than waiting for the event, I'd like to be able to check the status at predefined intervals.

Any suggestions on how I might achieve this as I cannot for the life of me see how !
2011-02-11 jezra:
John, you would need to use something like gobject.timeout_add to run the "read_buttons" function at a set period of time

http://www.pygtk.org/pygtk2reference/gobject-functions.html#function-gobject--timeout-add
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
5 minus 4
Answer:
required
  • Tags:
  • Python
2008-12-29
Back Story
On the Linux Outlaws Podcast, there is a feature on the show called the "crap alert"; which is an alarm sound that plays whenever Fabian, one of the show hosts, states that something is crap. There were a few discussions on the show's forums regarding playing the alarm on one's own computer.

"Hey, I have a switch and I want to use it to play a command on my computer!"

But how do I do it?
A bit of research found www.swen.uwaterloo.ca/~drayside/altinput/, which has information regarding making a switch to connect to the serial port of a computer. After a bit of soldering, my serial port switch was complete. All it needed was some sort of driver. Having previously worked with Python to access data from a serial port, I decided to use Python to poll the state of the switch and run a command when the switch is depressed.

Here is the "driver"
#!/usr/bin/env python
import serial
import gobject
import os
import sys
import subprocess

class serial_checker (gobject.GObject):
def __init__(self):
self.config = {"port":0,"carrier_detect_pressed":"echo carrier\ detect\ pressed\n"}
#read the config file
cfile = os.path.expanduser("~/.serial_switch" )
file_lines = open(cfile)
for line in file_lines:
#trim the white spaces
line = line.strip()
#if the line has length and the first char isn't a hash
if len(line) and line[0]!="#":
#this is a parsible line
(key,value) = line.split(":",1)
self.config[key.strip()] = value.strip()

self.cd_switch_down = False
self.cd_switch_action_running = False
gobject.GObject.__init__(self)
self.switch_count = 0
#define our serial
self.ser = serial.Serial(self.config["port"])
self.mainloop = gobject.MainLoop()
gobject.timeout_add(150, self.check_switches )

def cd_switch_action(self):
#what is the user requested command?
command = self.config["carrier_detect_pressed"]
#print command
#run the command, this should be in a thread or asynchronous
subprocess.Popen(command,shell=True)

def run(self):
#run the main loop
self.mainloop.run()

#check the switches
def check_switches(self):
cdval = self.ser.getCD()
if (cdval):
if(self.cd_switch_down==False):
self.cd_switch_down = True
#try to run the cd switch action
self.cd_switch_action()
else:
self.cd_switch_down = False

return True

#when the class is closed, clean up
def __del__(self):
self.ser.close()
self.mainloop.quit()


if (__name__=="__main__"):
ser_check = serial_checker()
ser_check.run()


When the python script is first run, it reads a configuration file from my home directory named ".serial_switch", which contains the command that I want to run when the switch is depressed. The config looks like this:
# this is a config file for serial_switch
# all configurations will be in key:value format

# serial port (default /dev/ttyS0 )
#port:/dev/ttyS0

#what happens when the carrier_detect switch is pressed
carrier_detect_pressed: gmrun


Although I played around with various commands that I thought would be fun to run when the switch was depressed, I found that gmrun was more useful than playing an audio file.

TODO
Add more switches to the serial connector.
Physically, the serial connector should be able to hand 3 or 4 switches so I should add the switches and update the software accordingly.

What it looks like
Here is the video I uploaded to youtube.
Comments
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
7 plus 8
Answer:
required
MCM_468x60_static
2008-05-23
When creating audio file playing software, it is nice to be able to access the tags of the audio files for display to the user. Since I am planning to use gstreamer to handle the playing of audio in my Luma project, I thought it would be best to make a very simple test app to handle the retrieval of tags from an audio file. Actually, I still need to test this with the various filetypes that gstreamer supports; specifically vorbis files. Anyway, on with the show.

#!/usr/bin/env python
import os
import sys
import gst
import gobject

class tag_getter:
def __init__(self):
#make a dictionary to hold our tag info
self.file_tags = {}
#make a playbin to parse the audio file
self.pbin = gst.element_factory_make("playbin")
#we need to receive signals from the playbin's bus
self.bus = self.pbin.get_bus()
#make sure we are watching the signals on the bus
self.bus.add_signal_watch()
#what do we do when a tag is part of the bus signal?
self.bus.connect("message::tag", self.bus_message_tag)
#create a loop to control our app
self.mainloop = gobject.MainLoop()

def bus_message_tag (self, bus, message):
#we received a tag message
taglist = message.parse_tag()
#put the keys in the dictionary
for key in taglist.keys():
self.file_tags[key] = taglist[key]
#for this test, if we have the artist tag, we can quit
if self.file_tags['artist']:
print self.file_tags
sys.exit()

def set_file(self,file):
#set the uri of the playbin to our audio file
self.pbin.set_property("uri","file://"+file)
#pause the playbin, we don't really need to play
self.pbin.set_state(gst.STATE_PAUSED)

def run(self):
#start the main loop
self.mainloop.run()

if __name__=="__main__":
if len(sys.argv)>1:
file = sys.argv[1]
pwd = os.getcwd()
filepath = os.path.join(pwd,file)
getter = tag_getter()
getter.set_file(file)
getter.run()

else:
print "select an audio file"


Useful? Not really, but it certainly is a good building block for a more advanced application.
Comments
2010-10-10 Ryan:
THANK YOU SO MUCH! This made it so much easier to figure out how to get tags through gstreamer. I've searched for forever trying to figure this out and this helped me figure it out in 5 minutes. Thanks again. Keep up the good work.
2010-10-10 jezra:
You are very welcome.
2011-03-25 Sam Brookfield:
Again, thanks, really helpful
2011-03-25 Sam Brookfield:
For anyone interested, the image tag is just a binary image - you can write it to a file -
if key == 'image':
img = open('temp.png', 'w')
img.write(taglist[key])
img.close()
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
1 plus 5
Answer:
required
  • Tags:
  • Python
  • Gstreamer
2008-05-22
In preparation for my Luma media player, I wanted to create a simple audio player with visualization. Based upon what I have read, this would require a "tee" when using gstreamer. The tee in gstreamers is much like a tee in piped shell commands; data gets copied at the tee and travels in multiple directions. In gstreamer, one needs to create a queue for each new direction that the data will flow down in the pipeline.

It took me a while to wrangle with the tee requirements for handling queues. I could see how, but I couldn't understand why. So anyway, this is what I came up with:
1. a tee in the pipeline gets a name
2. the end of a queue gets declared as part of the tee, and is given the name of the tee followed by a period
3. add a queue to the gstreamer pipeline
4. the end of the queue thingy gets placed at the end of the queues ( this doesn't seem to be required for the last queue)

My gstreamer pipeline looks like this:
#!/bin/sh
gst-launch
filesrc location=/path/to/audio/file
! decodebin ! audioconvert
! tee name=myT myT.
! queue ! autoaudiosink myT.
! queue ! goom ! ffmpegcolorspace ! autovideosink


sweet! Now on to a my pythonic version using pygst
#!/usr/bin/env python

import sys
import gst
import time
class myPlayer ():
def __init__(self):
self.pipeline = gst.Pipeline()
self.src = gst.element_factory_make("filesrc", "src")
self.decoder = gst.element_factory_make("decodebin", "decoder")
self.decoder.connect("new-decoded-pad", self.onNewDecodedPad)
self.goom = gst.element_factory_make("goom")
self.colorspace = gst.element_factory_make("ffmpegcolorspace","color")
self.conv = gst.element_factory_make("audioconvert", "conv")
self.vidsink = gst.element_factory_make("autovideosink","videosink")
self.asink = gst.element_factory_make("autoaudiosink", "aoutput")
self.tee = gst.element_factory_make('tee', "tee")
self.queuea = gst.element_factory_make("queue", "queuea")
self.queuev = gst.element_factory_make("queue", "queuev")
self.pipeline.add(self.src,self.decoder,self.conv,self.tee,self.queuea)
self.pipeline.add(self.asink,self.queuev,self.goom, self.colorspace, self.vidsink)
gst.element_link_many(self.src,self.decoder)
gst.element_link_many(self.conv,self.tee)
self.tee.link(self.queuea)
self.queuea.link(self.asink)
self.tee.link(self.queuev)
gst.element_link_many(self.queuev, self.goom,self.colorspace, self.vidsink)
def onNewDecodedPad(self,decodebin, pad, islast):
#link the pad to the converter
decodebin.link(self.conv)

def playfile(self,file):
self.src.set_property('location', file)
self.pipeline.set_state(gst.STATE_PLAYING)
pipelinestate = self.pipeline.get_state()

while pipelinestate[1] == gst.STATE_PLAYING:
time.sleep(1)
pipelinestate = self.pipeline.get_state()
sys.exit()

if __name__ == '__main__':
if (len(sys.argv) > 1):
file = sys.argv[1]
player = myPlayer()
player.playfile(file)
else:
print "you must select a tune"


The big difference here, at least to me, is that the decodebin isn't really a bin, but it represents a series of possible bins. So if one where to select a vorbis file to play, the decodebin will determine the correct type of bin needed to handle the file and would create an instance of that type of bin, the same is true for wav,flac,aac,mp3, etc; all of which have a specific decoder that I don't want to have to figure out, so I let the decodebin do it for me. This line: self.decoder.connect("new-decoded-pad", self.onNewDecodedPad), will call a function whenever a new bin is created by the decoder bin and it is in the onNewDecodedPad function that the decodebin links to the rest of the pipeline. Does that make sense?
Comments
2009-10-07 Chaz6:
Thanks for the example. There is one minor typo...

"self.popeline.add" should be "self.pipeline.add"
2009-10-07 jezra:
Thanks for the catch! The code has been updated.
2009-11-18 tom:
Thanks for the great example - I spent an hour trying to fit a pipeline for goom... You saved me :-)
Now this works on my...drumroll...Nokia N900! Maemo forever :-)
2009-11-18 jezra:
That is awesome Tom! I've always wanted to how well gstreamer runs on ARM based systems.
2010-09-09 Dennis:
It's more intuitive to think of the pipeline after the tee more like this:

#!/bin/sh
gst-launch
filesrc location=/path/to/audio/file
! decodebin ! audioconvert
! tee name=myT
myT. ! queue ! autoaudiosink
myT. ! queue ! goom ! ffmpegcolorspace ! autovideosink
2010-09-09 jezra:
Dennis, that was *exactly* what I needed to explain the tee!
2016-03-02 Anes P A:
I am working in a pygtk project with Gtk2. As part of modification I need to include Gstreamer 1.0 instead of 0.1. In my old system can use

import sys, os, os.path, time
import pygst
pygst.require("0.1")
import gst

But I don't know how to add new version of GStreamer ... I try above one with change in version as

import gi
gi.require_version('Gst', '1.0')
from gi.repository import GObject, Gst, Gtk

got error as :
import gi
File "/usr/lib/python2.7/dist-packages/gi/__init__.py", line 39, in <module>
raise ImportError(_static_binding_error)
ImportError: When using gi.repository you must not import static modules like "gobject". Please change all occurrences of "import gobject" to "from gi.repository import GObject". See: https://bugzilla.gnome.org/show_bug.cgi?id=709183

Please advise the right way..

thanks
Anes
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
2 plus 7
Answer:
required
  • Tags:
  • Python
  • Gstreamer
2008-05-12
This is not a tutorial. This is an example of code and some ramblings of my experience writing the code. What does the code do? What we have here is a basic image viewer using pyclutter textures as the output of the image. The image viewer will run in fullscreen mode and will animate images on and off of the screen when the user pressed either the left or right arrow keys. Here we go.

#!/usr/bin/env python
import sys
import os, os.path
import clutter
import gtk

class imageList:
def __init__(self,dir):
# we need an array and a default index of the array
self.imagelist =[]
self.imagelist_index = 0
#go find the images in the directory
self.find_images(dir)

def get_index_image(self):
return self.imagelist[self.imagelist_index]

def get_left_image(self):
return self.imagelist[ self.get_next_left_index() ]

def get_right_image(self):
return self.imagelist[ self.get_next_right_index() ]

def get_index(self):
return self.imagelist_index

def get_next_left_index(self):
left = self.imagelist_index-1
if left<0:
left = self.get_image_count()-1
return left

def get_next_right_index(self):
right = self.imagelist_index+1
if right>=self.get_image_count():
right = 0
return right

def decrement_index(self):
self.imagelist_index = self.get_next_left_index()

def increment_index(self):
self.imagelist_index = self.get_next_right_index()

def get_image_count(self):
return len(self.imagelist)

def find_images(self,dir):
#loop through the files in the folder
print "searching "+dir+" for images...."
assets = os.listdir(dir)
for i in range(len(assets) ):
asset_path = os.path.join(dir,assets[i] )
if os.path.isdir(asset_path):
#if the asset is a directory, recurse
self.find_images(asset_path)
pass
else:
#how do we know if this is a usable image?
#try to get a pixbuf from the image
try:
"""TODO: assume the majority of files are images and delete
the bad files from the array when they are displayed to the
user, this should vastly improve start up time"""
pixbuf = gtk.gdk.pixbuf_new_from_file(asset_path)
#add this asset to the imagelist
print "Found: "+asset_path
self.imagelist.append(asset_path)
except:
#fail this isn't a usable image
pass

class imageViewer:
def __init__(self):
#is there a sys.argv?
if len(sys.argv)>1:
self.create_image_list(sys.argv[1])
else:
self.print_help("No image directory selected")
#if we made it this far, we can start making the clutter interface
self.stage = clutter.Stage()
#make the stage full screen
self.stage.fullscreen()
#hide the mousey
self.stage.hide_cursor()
#we need to record the stage size
#NOTE: I should try to use the CLUTTER_STAGE_WIDTH, but it may only be part of the c library
(self.stage_w,self.stage_h) = self.stage.get_size()
#define a clutter color in rgbo (red,green,blue,opacity)
color_black =clutter.Color(0,0,0,255)
#set the clutter stages bg color to our black
self.stage.set_color(color_black)
#we will need to check on the key presses from the user
self.stage.connect('key-press-event', self.parseKeyPress)
#make three textures: left,right,and center
self.l_image=clutter.Texture()
self.r_image=clutter.Texture()
self.c_image=clutter.Texture()
#add the textures to the stage
self.stage.add(self.l_image)
self.stage.add(self.r_image)
self.stage.add(self.c_image)
#hide all of the images
self.l_image.set_opacity(0)
self.c_image.set_opacity(0)
self.r_image.set_opacity(0)
#creae a timeline for the texture animations
self.texture_timeline = clutter.Timeline(fps=25,duration=1000)
#create an alpha to describe the movement for behaviours
self.texture_alpha = clutter.Alpha(self.texture_timeline, clutter.ramp_inc_func)
'''we will need a behaviour to handle animation between the left and center(lc),
and between the center and right(cr) '''
self.lc_behaviour = clutter.BehaviourPath(self.texture_alpha)
self.cr_behaviour = clutter.BehaviourPath(self.texture_alpha)
#show all stage items and enter the clutter main loop
self.stage.show_all()
self.set_center_image()
clutter.main()

#we will need to scale the textures to fill the screen
def scale_texture(self,texture):
#NOTE: texture.get_size is not working properly, use the size of the pixbuf
#(twidth,theight) = texture.get_size()
buf = texture.get_pixbuf()
twidth = buf.get_width()
theight = buf.get_height()
if twidth>theight:
scale = ((self.stage_w+0.0)/twidth)
else:
scale = ((self.stage_h+0.0)/theight)
#perform the scaling
print scale
texture.set_property("width",twidth*scale)
texture.set_property("height",theight*scale)

def set_center_image(self):
#hide the texture
self.c_image.set_opacity(0)
#center is the current index
buf = gtk.gdk.pixbuf_new_from_file( self.imagelist.get_index_image() )
self.c_image.set_pixbuf( buf )
#scale the images
self.scale_texture(self.c_image)
#center the center image
(tex_width,tex_height) = self.c_image.get_size()
xloc = self.stage_w/2 - tex_width/2
yloc = self.stage_h/2 - tex_height/2
self.c_image.set_position(xloc,yloc)
#make the center image visible
self.c_image.set_opacity(255)

def set_right_image(self):
#right is right image
buf=gtk.gdk.pixbuf_new_from_file( self.imagelist.get_right_image() )
self.r_image.set_pixbuf( buf )
#scale the image
self.scale_texture(self.r_image)
#move the right image off of the screen and center vertically
(tex_width,tex_height) = self.r_image.get_size()
yloc = self.stage_h/2 - tex_height/2
self.r_image.set_position(self.stage_w,yloc )
self.r_image.set_opacity(255)
def set_left_image(self):
#l_image is left image
buf = gtk.gdk.pixbuf_new_from_file( self.imagelist.get_left_image() )
self.l_image.set_pixbuf( buf )
#scale the texture
self.scale_texture(self.l_image)
#move the left image off of the screen and center vertically
(tex_width,tex_height) = self.l_image.get_size()
yloc = self.stage_h/2 - tex_height/2
self.l_image.set_position(0-tex_width,yloc )
#make the center image visible
self.l_image.set_opacity(255)

def parseKeyPress(self,actor, event):
#do stuff when the user presses a key
#it would be awesome if I could find some documentation regarding clutter.keysyms
if event.keyval == clutter.keysyms.q:
#if the user pressed "q" quit the test
clutter.main_quit()
elif event.keyval == clutter.keysyms.Right:
self.next_image(0)
elif event.keyval == clutter.keysyms.Left:
self.next_image(1)

#direction of 0 means move right, direction 1 means move from right to left
def next_image(self,direction=0):
#clear the behaviour paths
self.lc_behaviour.clear()
self.cr_behaviour.clear()
self.lc_behaviour.remove_all()
self.cr_behaviour.remove_all()
#update the center texture
self.set_center_image()
if direction:
#we need to set the right image
self.set_right_image()
#hide the left image
self.l_image.set_opacity(0)
#adjust the image list index
self.imagelist.increment_index()
#center image moves to the left
(x,y) = self.c_image.get_position()
lc_knot0 = clutter.Knot(x,y)
#what is the destination knot?
lc_knot1 = clutter.Knot(-self.c_image.get_width(),y )
#which texture is moving in the lc?
lc_texture = self.c_image
#right image moves to the left
(x,y) = self.r_image.get_position()
cr_knot0 = clutter.Knot(x,y)
xcenter = self.stage_w/2-self.r_image.get_width()/2
cr_knot1 = clutter.Knot(xcenter,y )
#which texture is moving in the cr?
cr_texture = self.r_image

else:
#we need to set the left image
self.set_left_image()
#hide the right image
self.r_image.set_opacity(0)
#adjust the image list index
self.imagelist.decrement_index()
#left image moves to the center
(x,y) = self.l_image.get_position()
lc_knot0 = clutter.Knot(x,y)
xcenter = self.stage_w/2-self.l_image.get_width()/2
lc_knot1 = clutter.Knot(xcenter,y )
#which texture is moving in the lc?
lc_texture = self.l_image
#center image moves to the right
(x,y) = self.c_image.get_position()
cr_knot0 = clutter.Knot(x,y)
endx = self.stage_w
cr_knot1 = clutter.Knot(endx,y )
#which texture is moving in the cr?
cr_texture = self.c_image

#add the knots to the lc behaviour
self.lc_behaviour.insert_knot(0,lc_knot0)
self.lc_behaviour.insert_knot(1,lc_knot1)
self.lc_behaviour.apply(lc_texture)
#add the knots to the cr behaviour
self.cr_behaviour.insert_knot(0,cr_knot0)
self.cr_behaviour.insert_knot(1,cr_knot1)
self.cr_behaviour.apply(cr_texture)
#start the timeline associated with the behaviours
self.texture_timeline.start()

def create_image_list(self,image_folder):
cwd = os.getcwd()
path_to_image_folder = os.path.join(cwd,image_folder)
#does the folder exist?
if os.path.isdir(path_to_image_folder):
print "image folder: "+path_to_image_folder
self.imagelist = imageList(path_to_image_folder)
#did we find images?
img_count = self.imagelist.get_image_count()
print "Found "+str(img_count)+" images"
if img_count ==0:
self.print_help(image_folder+" does not contain usable images")
else:
self.print_help(image_folder+" is not a directory")

def print_help(self,errormessage=""):
if(errormessage):
print "Error: "+errormessage
print "Usage: "+sys.argv[0]+" /path/to/directory/containing/images"
print "Navigation: use the left and right arrows to view the images"
print ""
sys.exit()
if __name__=="__main__":
#make an imageViewer
imageViewer = imageViewer()


Problems:
1. I was hoping to automatically change a clutter textures height and width by making sure that the texture's "sync-size" property was true and then changing the textures pixbuf. Unfortunately, this didn't work, so I grabbed the height and width the pixbuf itself and adjusted the size of the texture accordingly.
2. Althought it was a minor problem, I spend too much time trying to figure out how to use gstreamer to get the data from my images files and pass the data to a gstvideotexture. As you can see, I ended up using gtk.gdk for image handling.
3. segfaulting on behaviourPath.append_knots(knot1,knot2)

Nifty Clutter:
behaviourPath knots - a knot is an X,Y coordinate that make a path for an object to follow when the object animates. For example, suppose I have a texture at 0,0 and I want to move the texture to 200,100 and then to 50,400. I would make a knot for each coordinate and add the knot to a behaviourPath using append_knots(list of knots) or insert_knot(knot index, knot).
myBehaviourPath.append_knots(knot1,knot2,knot3)
then apply the behavor to the texture and start the timeline associated with the behaviours alpha
myBehaviourPath.append(myTexture)
myTimeline.start()
Booyah! The texture will move from knot1 to knot2 to knot3

Awesome:
Pydoc. I can't believe it took me so long to learn about pydoc: a python documentation tool. Which I found indispensable for figuring out what methods are available in various python modules.
Comments
2008-12-23 Anonymous:
for clutter-0.8 change:

buf = gtk.gdk.pixbuf_new_from_file( self.imagelist.get_index_image() )
self.c_image.set_pixbuf( buf )

to:

self.c_image.set_from_file( self.imagelist.get_index_image() )

===================================
But self.stage.fullscreen() can only maximize the window.
Do you know how to setup a real fullscreen (borderless) window?
2008-12-23 jezra:
Since the fullscreen() function does fill my entire screen when using pyclutter 0.6.x, this may be a bug in the latest branch of clutter.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 1
Answer:
required
2008-05-02
What is pyclutter? Pyclutter is the python binding to the clutter library: a high level OpenGL library for creating animated user interfaces.

OK, so this isn't really a tutorial because I'm not going to go through each line of code and explain what it does. However, there are certain parts of the code that I will give a bit more info about, because I had some problems figuring it out and I'm hoping my experience will help other.

This is not a static "hello world". I know, I broke the rules; but I think this is a good thing. Upon running the script, there will be a "hello" on the screen. Pressing "s" on the keyboard will initiate a switching toggle between "hello" and "world" by animating a change in their opacities. Pressing "q" will quit the app. Check it out
 #!/usr/bin/env python
print "Hello World!"

Ha ha, just kidding.....
#!/usr/bin/env python

import clutter

class clutterTest:
def __init__(self):
#create a clutter stage
self.stage = clutter.Stage()
#set the stage size in x,y pixels
self.stage.set_size(500,200)
#define some clutter colors in rgbo (red,green,blue,opacity)
color_black =clutter.Color(0,0,0,255)
color_green =clutter.Color(0,255,0,255)
color_blue =clutter.Color(0,0,255,255)
#set the clutter stages bg color to our black
self.stage.set_color(color_black)
#we will need to check on the key presses from the user
self.stage.connect('key-press-event', self.parseKeyPress)
#create a clutter label, is there documentation for creating a clutterlabel?
self.label = clutter.Label()
#set the labels font
self.label.set_font_name('Mono 32')
#add some text to the label
self.label.set_text("Hello")
#make the label green
self.label.set_color(color_green )
#put the label in the center of the stage
(label_width, label_height) = self.label.get_size()
label_x = (self.stage.get_width()/2) - label_width/2
label_y = (self.stage.get_height()/2) - label_height/2
self.label.set_position(label_x, label_y)
#make a second label similar to the first label
self.label2 = clutter.Label()
self.label2.set_font_name('Mono 32')
self.label2.set_text("World!")
self.label2.set_color(color_blue )
(label2_width, label2_height) = self.label2.get_size()
label2_x = (self.stage.get_width()/2) - label2_width/2
label2_y = (self.stage.get_height()/2) - label2_height/2
self.label2.set_position(label2_x, label2_y)
#hide the label2
self.label2.set_opacity(0)
#create a timeline for the animations that are going to happen
self.timeline = clutter.Timeline(fps=20,duration=500)
#how will the animation flow? ease in? ease out? or steady?
#ramp_inc_func will make the animation steady
labelalpha = clutter.Alpha(self.timeline, clutter.ramp_inc_func)
#make some opacity behaviours that we will apply to the labels
self.hideBehaviour = clutter.BehaviourOpacity(255,0x00,labelalpha)
self.showBehaviour = clutter.BehaviourOpacity(0x00,255,labelalpha)
#add the items to the stage
self.stage.add(self.label2)
self.stage.add(self.label)
#show all stage items and enter the clutter main loop
self.stage.show_all()
clutter.main()

def parseKeyPress(self,actor, event):
#do stuff when the user presses a key
#it would be awesome if I could find some documentation regarding clutter.keysyms
if event.keyval == clutter.keysyms.q:
#if the user pressed "q" quit the test
clutter.main_quit()
elif event.keyval == clutter.keysyms.s:
#if the user pressed "s" swap the labels
self.swapLabels()


def swapLabels(self):
#which label is at full opacity?, like the highlander, there can be only one
if(self.label.get_opacity()>1 ):
showing = self.label
hidden = self.label2
else:
showing = self.label2
hidden = self.label
#detach all objects from the behaviors
self.hideBehaviour.remove_all()
self.showBehaviour.remove_all()
#apply the behaviors to the labels
self.hideBehaviour.apply(showing)
self.showBehaviour.apply(hidden)
#behaviours do nothing if their timelines are not running
self.timeline.start()



if __name__=="__main__":
test = clutterTest()


Copy the code and drop it into your favorite text editor.
things I got hung up on....
1. Labels:the pyclutter documentation doesn't reference labels. Fortunately, the pyclutter examples that are included with the pyclutter source code, have a sample of a label
2. Alpha: clutter.alpha is a representation of a value based on a clutter.timeline and a function to determine the value at a given time on the time line. The first function that I used was a sine function that would peak at the midway point and then return to the starting point. So my opacity behavior would go from 0 to 255 and then back to 0, when what I really wanted was to go from 0 to 255.
3. behaviourOpacity: When the behavior transitions the opacity from 255 to 0, the ending opacity is 1, or at least is was the last time I checked. Odd.
4. Determining which key is pressed: I don't know enough about python to be able to print out all of the data in the event object that is sent when there is a "key-press-event". I was trying to print the keys from the event objects dict but nothing was working. Luckily, I stumbles upon the clutter.keysyms doohicky but I haven't found all of the documentation for the keysyms yet. For now, I just use clutter.keysyms.[what key am I looking for] to determine if a specific key is pressed.

That's it for now...
Comments
2009-09-23 kelvan:
doesn't work with python-clutter1.0.0
2009-09-23 jezra:
This example was written for clutter 0.6. Fortunately, the python backtraces make it fairly easy to determine what has changed from 0.6 to 1.0. In this case, it is probably the change of the "Label" object to a "Text" object.
Name:
not required
Email:
not required (will not be displayed)
Website:
not required (will link your name to your site)
Comment:
required
Please do not post HTML code or bbcode unless you want it to show up as code in your post. (or if you are a blog spammer, in which case, you probably aren't reading this anyway).
Prove you are human by solving a math problem! I'm sorry, but due to an increase of blog spam, I've had to implement a CAPTCHA.
Problem:
9 minus 7
Answer:
required
subscribe
 
2019
2016
2015
2014
2013
2012
2011
2010
December
November
October
September
August
July
June
May
April
March
February
January
2009
December
November
October
September
August
July
June
May
April
March
February
January
2008