Sunday, March 22, 2009

Scanning for wireless networks

Two methods to scan for wireless networks. One requires sudo/root, the other requires Network Manager.

#! /usr/bin/env python
"""This python 2.5 script uses iwlist to scan for nearby wireless networks. It must be run as sudo/root to work."""
import subprocess as SU
command = ['iwlist', 'eth1', 'scan']
output = SU.Popen(command, stdout=SU.PIPE).stdout.readlines()
data = []
for item in output:
    print item.strip()
    if item.strip().startswith('ESSID:'): 
        data.append(item.lstrip(' ESSID:"').rstrip('"\n'))
    if item.strip().startswith('Quality'): 
        data.append(int(item.split()[0].lstrip(' Quality=').rstrip('/100 ')))
    if item.strip().startswith('Encryption key:off'): data.append('OPEN')
    if item.strip().startswith('Encryption key:on'): data.append('encrypted')        
print data


#! /usr/bin/env python
"""This python 2.5 script uses dbus to query Network Manager, which scans regularly for wireless networks. It does NOT require root/sudo."""
import dbus
item = 'org.freedesktop.NetworkManager'
path = '/org/freedesktop/NetworkManager/Devices/eth1'
interface = item + '.Device'

bus = dbus.SystemBus()
data = []
wireless = dbus.Interface(bus.get_object(item, path), interface)
for network_path in wireless.getNetworks():
    network = dbus.Interface(bus.get_object(item, network_path), interface)
    data.append(network.getName())                        # also network.getProperties[1]
    data.append(network.getStrength())                    # also network.getProperties[3]
    if network.getEncrypted(): data.append('encrypted')
    else: data.append('OPEN')
print data

Wednesday, March 11, 2009

Replacing os.popen() with subprocess.Popen() in Python

In Python, you can execute shell commands using the os.popen method...but it's been deprecated in favor of a whole new command.

# The old way, which worked great!
import os
shell_command = 'date'
event = os.popen(shell_command)
stdout = event.readlines()
print stdout

# The new way, which is more powerful, but also more cumbersome.
from subprocess import Popen, PIPE, STDOUT
shell_command = 'date'
event = Popen(shell_command, shell=True, stdin=PIPE, stdout=PIPE, 
    stderr=STDOUT, close_fds=True)
output = event.stdout.read()
print output

# The new way, all in one line (a bit uglier), works in Python3!
import subprocess
output = subprocess.Popen('date', stdout=subprocess.PIPE).stdout.read()
print output

Sunday, March 8, 2009

Using DBus on Xubuntu 8.04

This post is obsolete has been superseded by my 2012 dbus tutorial series.

DBus is a system that permits different applications to exchange information. Tutorial Reference Other Reference.
Sometimes, DBus crashes upon restart from a suspend or hibernation. These bash commands will help you figure out if it has crashed, and how to restart it.

$ps -e | grep `cat /var/run/dbus/pid` # Confirm if DBus is running by checking for the PID number in the list of live processes.
                                      # If DBus is running, this will return the process number. 
                                      # If not, it will return nothing.
$sudo rm /var/run/dbus/pid            # Remove the stale pid file so DBus can be restarted.
$sudo dbus-daemon                     # Start DBus again.


A python script uses DBus to see if the network connection is available by asking Network Manager:

#! /usr/bin/env python
import dbus
bus = dbus.SystemBus()
item = 'org.freedesktop.NetworkManager'
eth0_path = '/org/freedesktop/NetworkManager/Devices/eth0'
eth1_path = '/org/freedesktop/NetworkManager/Devices/eth1'
interface = 'org.freedesktop.NetworkManager.Devices'

# There are two possible network interfaces: eth0 (wired) and eth1 (wireless).
eth0 = dbus.Interface(bus.get_object(item, eth0_path), interface)
if eth0.getLinkActive(): print('The wired network is up') # getLinkActive() is a boolean, TRUE if the network link is active
eth1 = dbus.Interface(bus.get_object(item, eth1_path), interface)
if eth1.getLinkActive(): print('The wireless network is up')

This shell script does exactly the same thing, using the same DBus call:
# This shell script checks Network Manager if the network is up, using dbus as the communications medium.
# There are two possible network interfaces: eth0 (wired) and eth1 (wireless). Of course, you may need to alter these to meet your own circumstances.
# The basic format of dbus-send is: dbus-send --system --dest=org.freedesktop.NetworkManager /org/freedesktop/NetworkManager/Devices/eth0 --print-reply org.freedesktop.NetworkManager.Devices.eth0.getLinkActive
DEST='org.freedesktop.NetworkManager'
PPATH='/org/freedesktop/NetworkManager/Devices'
DEVICE='org.freedesktop.NetworkManager.Devices'

result_eth0=`dbus-send --system --dest=$DEST $PPATH'/eth0' --print-reply $DEVICE'.eth0.getLinkActive'`
shaved_eth0=`echo $result_eth0 | cut -d ' ' -f8`
if [ $shaved_eth0 = 'true' ]; then echo 'The wired network is up'; fi

result_eth1=`dbus-send --system --dest=$DEST $PPATH'/eth1' --print-reply $DEVICE'.eth1.getLinkActive'`
shaved_eth1=`echo $result_eth1 | cut -d ' ' -f8`
if [ $shaved_eth1 = 'true' ]; then echo 'The wireless network is up'; fi


A Python script that queries Network Manager to get the list of wireless networks.

import dbus
item = 'org.freedesktop.NetworkManager'
path = '/org/freedesktop/NetworkManager'
interface = item + '.Device'

network_list = []
bus = dbus.SystemBus()

# Create a Network Manager interface and get the list of network devices
event = dbus.Interface(bus.get_object(item, path), interface)

# Create an interface for each device
# Query each interface to see if it's wireless
# Query each wireless interface for the networks it sees
for device in event.getDevices():
    device_interface = dbus.Interface(bus.get_object(item, device), interface)
    if device_interface.getType() == 2:  # 0 unknown, 1 wired, 2 wireless
        network_list.extend(device_interface.getNetworks())

# Reformat the network names in the list to be more readable
if network_list:
    for entry in network_list:
        #print entry    # String of path/to/network_name
        entry_list = entry.split('/')
        print entry_list[-1]  # String of network_name



A Python listener that catches the changes in wireless signal strength using both available methods.

import dbus, gobject
from dbus.mainloop.glib import DBusGMainLoop

def print_device_strength1(*args):  #Use the received signals
    signal_strength = args[1]
    print ('Signal Strength (Method 1): ' + str(signal_strength) + '%')

def print_device_strength2(*args, **kwargs):  #Use the received signals
    signal_strength = args[1]
    print ('Signal Strength (Method 2): ' + str(signal_strength) + '%')

DBusGMainLoop(set_as_default=True)    # Set up the event loop before connecting to the bus
bus_object = dbus.SystemBus()

# The variables you need. I used the shell command 'dbus-monitor --system' to find this information
sender = 'org.freedesktop.NetworkManager'
path = '/org/freedesktop/NetworkManager'
interface = sender
member = 'DeviceStrengthChanged'

# Method 1 - bus_object.proxy_object.connect_to_signal(method, action, filter, message_parts)
proxy_object = bus_object.get_object(sender, path)
proxy_object.connect_to_signal(member, print_device_strength1, dbus_interface = interface)

# Method 2 - bus_object.add_signal_receiver(action, [filters])
bus_object.add_signal_receiver(print_device_strength2, dbus_interface = interface, member_keyword = member)

# Start the loop
loop = gobject.MainLoop()
loop.run()




Thunar responds beautifully to D-Bus. Introspection is fully set up, so it's easy to use with the d-feet application. Useful for launching programs, opening folders and windows, and manipulating the trash. Launching a program by this method means that the the window manager launches the program, not the script or terminal, so the program can remain open after the script or terminal terminates.

#!/usr/bin/env python
import dbus
item = ('org.xfce.Thunar')
path = ('/org/xfce/FileManager')
interface = ('org.xfce.FileManager')
event = dbus.Interface(dbus.SessionBus().get_object(item, path), interface)

# These three lines at the end of the script open the file's 'properties' window
display = (':0')         # The current session screen
uri = ('/home/me/dbus_test.py')
event.DisplayFileProperties(uri, display)

# These three lines at the end of the script launch a new application
display = (':0')         # The current session screen
uri = ('/usr/bin/gftp-gtk')
event.Launch(uri, display)

# These four lines at the end of the script open a folder window and optionally select a file
display = (':0')         # The current session screen
uri = ('/home/me/.cron')
filename = ('anacrontab.daily')
event.DisplayFolderAndSelect(uri, filename, display)


A sample hal script.

#!/usr/bin/python
"""This python 2.5 script uses dbus to check if the lid switch is open.
Based on an original python script at http://schurger.org/wordpress/?p=49"""
import dbus

dest = 'org.freedesktop.Hal'
hal_path = '/org/freedesktop/Hal/Manager'
hal_interface = 'org.freedesktop.Hal.Manager'
udi_interface = 'org.freedesktop.Hal.Device'

# Get the list of possible input switches. The return is a list of paths.
bus = dbus.SystemBus()
hal = dbus.Interface(bus.get_object(dest, hal_path), hal_interface)
list_of_udi_paths = hal.FindDeviceByCapability('input.switch')

# Filter the list for the word 'lid'. Print the status for each one.
for udi_path in list_of_udi_paths:
    udi = dbus.Interface(bus.get_object(dest, udi_path), udi_interface)
    if udi.GetProperty('button.type') == "lid":
        # The button.state.value is FALSE if the lid is open.
        if udi.GetProperty('button.state.value'): print ('Lid is closed')
        else: print ('Lid is open') 
    else: print ('Problem: I could not find the lid switch. Sorry.')



Notes:

  • The D-feet application is very handy for exploring DBus, and figuring out how to communicate with it. It's available in the Ubuntu repositories.
  • More information on the destination/path/interface settings is available from each application's XML config files, found in the /etc/dbus-1/system.d and /session.d directories.
  • The system-tools-backends DBus interfaces look promising, with methods for network interfaces, time, users and groups, and more. But I couldn't get any of it to work. One hint suggested that the DBus message must be sent by root instead of user.
  • xfce4-terminal has a Launch method, seemingly for launching items in the terminal (source code). I can see how that would be handy, but I couldn't get it to work.

Wednesday, March 4, 2009

Bash and Python scripts to unzip and modify an OpenOffice .odt document

.odt files are actually containers (you can see one using unzip -l document.odt). Within the container, content is in the content.xml file. Script info source


Here's what I've figured out about opening, modifying, and saving the content of an .odt file:

  • To open the container for editing:
    # bash
    $unzip path/container.odt content.xml
    $unzip path/container.odt content.xml -d working/dir/path/
        # -d places content.xml in a different directory.
        # Creates a content.xml file where you want it.
    
    # python
    >>>import zipfile
    >>>odt_file = zipfile.ZipFile('path/to/file.odt','a')  
    >>>    # Options are 'r'ead only, 'w'rite only, and 'a'ppend to existing
    >>>raw_xml = odt_file.read('content.xml') 
    >>>    # Reads content.xml in as a string, doesn't place a file.
    

  • Modify the content.xml file by hand, or using a script, or using Python.
    >>> # Tip for using python: ElementTree is good at XML, and it can parse a file, but it cannot parse a string!
    >>> # So here's how to make a string look like a file using python's StringIO module.
    
    >>>import StringIO, xml.etree.ElementTree as ET
    >>>fakefile = StringIO.StringIO(raw_xml)   # Pretend raw_xml string is a file called fakefile
    >>>tree = ET.parse(fakefile).getroot()     # Parse the fakefile
    >>>fakefile.close()                        # close() is desired by StringIO to free the buffer
    >>> # Make changes to your tree here.
    

  • To restore the container with modified content:
    # bash
    zip -j path/container.odt working/dir/path/content.xml
        # The -j flag adds the file 'content.xml' instead of the useless 'path/content.xml'. You need this!
    rm working/dir/path/content.xml    # Clean up
    
    # python
    >>>new_xml = ET.tostring(tree) # If you're exporting from an ElementTree
    >>>odt_file.writestr('content.xml', new_xml)
    >>>odt_file.close()
    
  • Putting it all together in bash:
    cd path/to/working/directory
    cp path/to/template_filename.odt working_file.odt
    unzip working_file.odt content.xml
    
    # Change the xml...somehow
    
    zip -j working_file.odt content.xml
    rm content.xml
    
  • Putting it all together in python:
    def edit_the_odt_content(template_filename):
        """Exposes the content of an .odt file so you can modify it."""
        import os, shutil, StringIO, zipfile, xml.etree.ElementTree as ET
    
        shutil.copyfile(template_filename, 'working_file.odt') # Copy the template into a working file
        odt_file = zipfile.ZipFile('working_file.odt','a')
        xml_string = odt_file.read('content.xml')              # Read the zipped content.xml within the .odt as a string
        raw_xml = StringIO.StringIO(xml_string)                # Pretend the read string is a file so ElementTree will parse it
        tree = ET.parse(raw_xml).getroot()                     # Convert raw string to ElementTree
        raw_xml.close()
    
        office_namespace = '{urn:oasis:names:tc:opendocument:xmlns:office:1.0}'
        body = tree.find(office_namespace + 'body')            # Search the tree to find the elements you want to change
        text = body.find(office_namespace + 'text')
        new_text = your_function_to_modify_the_xml(text)       # You can now change the XML any way you wish
        body.remove(text)                                      # Replace the old XML with the new
        body.append(new_text)
    
        new_xml = ET.tostring(tree)                            # Convert the modified ElementTree back into an XML string
        odt_file.writestr('content.xml', new_xml)              # Write the string into the zipped content.xml
        odt_file.close()                                       # Close the zip archive (important!)
        return
    
  • Bug: Don't use the zip -m flag! It looks handy, claiming to delete the content.xml file from your file system after adding it to the archive...but instead it will unpredictably delete without adding to the archive.

  • You can avoid the whole "containers" muddle by saving an OpenOffice document as a flat file (.fodt). There's no zipping or unzipping, just open the file in an editor - it's xml already. Open the modified .fodt with OpenOffice, and your document is right there. Er, be sure your version of OO supports .fodt before using it. My Mac doesn't, for example.

  • OpenOffice also has it's own script classes for Python and C, called UNO. However, I haven't taken time to dig around through it.

Tuesday, March 3, 2009

Python Script on Windows XP

Trying to adapt the Linux-built rental invoice script onto the Windows XP bookkeeping computer

cmd - run this command at the run prompt to get a windows command line.
C:\Python26\python  - run python from the windows command line.
C:\Python26\python "C:\Documents and Settings\All Users\Documents\" - run the script from the command line