I recently discovered a Python IDE called PIDA that provides support for embedded Vim. I'm not a big fan of Python IDEs, but being able to use Vim as the editing component is definitely a killer feature. I haven't seen Vim embedded in a GNOME program since the unfortunate demise of the GVim Bonobo component and I had no idea it was even possible with current versions of Vim. A quick inspection of PIDA's source code revealed that it launches a new instance of GVim and then uses XEmbed to incorporate it into the user interface.
I had never heard of XEmbed before, but seeing it used in PIDA compelled me to pursue further investigation. Apparently, XEmbed makes it possible to embed virtually any X11 window inside of another application. GTK provides support for this with the Plug and Socket widgets, which are available in GTK's Python and Ruby bindings. XEmbed client support is also available in Qt with the QtXEmbedContainer object.
For the sake of experimentation, I wrote a simple Ruby script that uses my window management library and the Gtk::Socket object to facilitate window embedding. It provides two combo boxes which each contain a complete list of windows. The user selects a window in each one, and then clicks the Grab button to grab those windows and reparent them in Socket objects separated by a resizeable splitter. The source code for this experiment is relatively simple:
#!/usr/bin/env ruby
require "wmlib"
require "gtk2"
win = Gtk::Window.new("Embed Test")
win.signal_connect("destroy") {|w| Gtk.main_quit}
win.add vb = Gtk::VBox.new
vb.pack_start(hb = Gtk::HBox.new, false, false)
lst = Gtk::ListStore.new String, Integer
hb.pack_start(combo1 = Gtk::ComboBox.new)
hb.pack_start(combo2 = Gtk::ComboBox.new)
hb.pack_start(btnGrab = Gtk::Button.new("Grab"), false)
combo1.model = lst
combo2.model = lst
for w in WM::Window
i = lst.append
i[0], i[1] = w.title[0..30], w.id if w.title
end
vb.pack_start(pane = Gtk::HPaned.new)
pane.pack1 socket = Gtk::Socket.new, true, false
pane.pack2 socket2 = Gtk::Socket.new, true, false
win.show_all()
btnGrab.signal_connect("clicked") {|*a|
socket.add_id combo1.active_iter[1]
socket2.add_id combo2.active_iter[1]
}
Gtk.main
While experimenting with panes and XEmbed, one is reminded of tiling window managers like Ion. The ability to place arbitrary windows into tabs or panes is very compelling, and I'm certain I'll find some uses for the feature in the future.
Tags: programming, gnome, ruby
Posted on 2007-01-06
I've been trying to get my hands on a Nintendo Wii since the release last month. I haven't had much luck, and I'm not willing to pay a higher price to get one from e-bay or from the numerous retailers that offer overpriced bundles. To help me keep an eye on Wii availability, I made a simple notification utility that uses xpBargain's Wii Locator RSS feed. My utility checks the RSS feed every five minutes and displays the results in a compact notification window.
I was initially just going to keep a browser window open at the Wii Locator page and use the Firefox ReloadEvery extension to refresh it every five minutes, but that would waste a lot of screen space and be harder to read at a glance. I decided to use Ruby and GTK just to keep things simple. Coding simple data lists with a GTK treeview is more trouble than it is worth, so I decided to generate simple html and dump it into a GtkMozEmbed widget.
Making this sort of utility is relatively trivial in GTK, and I managed to write most of it without having to refer to the API documentation. I used REXML to parse the RSS feed and then I generate the HTML by hand. I could have converted the RSS to HTML with XSL, but that probably would have been overkill for such a simple throw-away app.
Everything seemed to work, but I ran into a small problem. Ruby's simple threading system doesn't work with GTK's main loop. To make a Ruby script perform an action every 300 seconds without stalling the rest of the program, I would typically do something like this:
Thread.new do
loop do
process_data
sleep 300
end
end
Unfortunately, that particular approach makes the program hang. After a little bit of research, I figured out how to perform timed events on a loop with GTK. The timeout_add method will evaluate a block at intervals when the program is otherwise idle. The block will continue to repeat at roughly the specified interval as long as the block returns the boolean true value when it is evaluated. In my simple Wii availability tracker, I used the following code:
Gtk.timeout_add(300 * 1000) do
process_data
true
end
It seems like the only places that have Wiis available right now are selling overpriced bundles. I probably wont be able to get one at a reasonable price until January, but at least my new utility will make it easier.
Tags: programming, ruby, gnome, nintendo
Posted on 2006-12-23
My blog recently got swamped with comment spam. I had hoped that a simple "click this to prove you are not a spammer" checkbox would provide adequate protection against automated comment bots, but that didn't accomplish very much. I managed to find a relatively good Ruby captcha library built with GD that will hopefully get the job done. Captcha systems are simplistic turing tests that automatically generate images with random garbled text. A user proves that they are not a bot by typing the text from the image into a text box. Integrating Captcha support was relatively easy:
captcha = CAPTCHA::Web.from_configuration($CAPTCHA_CONFIG)
captcha.clean; captcha.image
$cgi.div("id" => "commentForm") {
$cgi.img(captcha.image_uri + captcha.file_name) +
$cgi.hidden("digest", captcha.digest) +
$cgi.p {
"Text in image:" + $cgi.br + $cgi.text_field("key", "")
} +
$cgi.submit("Send Comment")
}
I also spent some time fixing up the recent activity links on my home page. I added support for recent Trac actions by parsing items from the Trac Timeline RSS feed. Right now, it only shows items that relate to Mage, but I may expand it in the future if I start working with other Cixar project repositories on a regular basis. I was having trouble with Trac's RSS feed at first, because it wasn't including an author tag in any of the items. I eventually discovered that the author tag only gets included if the associated user specifes an e-mail address on the Trac settings page. I still haven't found a way to use URL parameters to filter for a specific user, so I do it with an XPath expression in my Ruby script.
Tags: programming, ruby, blog, cgi
Posted on 2006-08-08
I finally managed to set aside enough time to fix my blog. The frustrating limitations of PyBlosxom compelled me to search for better solutions, but I was unable to find blog software that meets all of my needs. Rather than attempting to integrate new features into an existing code base, I decided to write my own from scratch. Although I generally try to avoid reinventing the wheel, starting from scratch with Ruby ended up being less labor intensive than trying to fix PyBlosxom, and I will thankfully be able to maintain and extend my new system with much greater ease.
Although I still use nested categories under the hood, my new system emphasizes tags. Categories are poorly suited for blog entry classification, and tags provide a much higher level of flexibility. Consider my previous entry about the Vim Ruby bindings, for instance. Does that belong in the Software/Vim category or in the Programming/Ruby category? With tags, I can associate the entry with Programming, Ruby, Software, and Vim. Tags also facilitate some very intriguing and useful navigation paradigms. I'm particularly pleased with my new tag cloud feature, which displays tags in various sizes and colors based on frequency of tag use. For those of you that are not familiar with tag clouds, a brief description can be found alongside the clouds on my new Tags page.
I have also integrated Simpy functionality. The Recent Bookmarks section on the left is populated entirely from Simpy using my Ruby API. Despite my initial skepticism regarding social bookmarking, I have become an avid user, and I am now convinced that the concept is brilliant. My Tags page displays an additional cloud with tags from Simpy links. Unfortunately, the Simpy REST API does not provide temporal details for tags, so my script has to do some very inefficient processing that places excessive load on the Simpy servers. In order to limit the impact of that load, I have configured my script to cache the data and generate new tag clouds only when I add or update a blog entry. I plan to discuss this deficiency with Otis, one of the Simpy developers. Otis has been very receptive to my constructive criticism in the past, and I think that he will implement the features I need to make an efficient implementation of my Simpy tag cloud generator.
In addition to tags, I also implemented support for custom syndication exports. Now that I have a working blog, I would like to be able to add it to Planet ArsLinux and eventually Planet GNOME. The syndication export feature will enable me to control which entries get exported to various planets. I would also eventually like to create a Planet Cixar which will include the blog entries of all developers working on Cixar projects.
At present, my entire blog system (including the cloud feature) is about 300 lines of code. It is still too messy for public exhibition, but I plan to clean it up for official release at some point in the next few months. For those that are interested and impatient, private requests for access to the source code will be granted in the interim. The tag cloud code may be released sooner, because I think it would make a fun topic for an article. During the development process, I experimented with several different libraries and frameworks. I wanted to use Ruby's CGI library, but unfortunately, it doesn't support XHTML. I eventually found a nice patch that adds reliable support for XHTML 1.0, and I applied it to a copy of the Ruby 1.8 CGI library for deployment with my blog script. The CGI library has some particularly useful features for generating output and parsing parameters. I also started experimenting with WEBrick, a light-weight Ruby server that was immensely useful during the debugging process.
Tags: blog, programming, ruby, cgi, tagging, simpy
Posted on 2006-07-17
I have recently developed an interest in web services that enable users to store and share links on the Internet. Social bookmarking services are increasingly popular amongst bloggers, and could completely replace conventional bookmarks as ubiquitous broadband becomes reality. After comparing a number of available services, I finally settled on Simpy, which has several unique features and provides numerous advantages over competing services like del.icio.us. I'm particularly impressed with Simpy's support for third party development. Using the Simpy REST API, I can manipulate my bookmarks with virtually any programming language.
Although I initially had some problems with the authentication mechanism, I eventually managed to create a complete Ruby library for interacting with Simpy. Now officially released, the source code is available online through the Simpy Tools Sourceforge version control system. Documentation for the latest CVS version can be found here.
At the present time, simpyapi-ruby features support for adding, removing, modifying, and acquiring links as well as removing, renaming, and listing tags. Support for notes and watchlists will be implemented in a future version. Unsupported operations can still be performed with the generic Simpy::ClientBase.command method. Support is also provided for interfacing with Simpy RSS feeds.
The simpyapi-ruby library allows developers to leverage Ruby's syntactic sugar and interact with Simpy without having to parse, process, or manipulate XML data. Login and connection handling is all automatic, all you have to do is provide simpyapi-ruby with a Simpy username and password.
The simpyapi-ruby library is very easy to install. Simply run the setup.rb script included with the library. The library was designed with application development in mind (I plan to create a proof-of-concept GNOME utility for Simpy management), but it is also entirely suitable for simple scripting and web integration. For optimal exposure and licensing compatibility with other Simpy tools, my library is dual-licensed under the terms of the ASL and BSD licenses.
Tags: programming, ruby, tagging, simpy
Posted on 2006-06-21
I'm currently expanding the functionality of my AOL Instant Messenger interceptor for a project I'm working on with a researcher from IBM. For this particular project, the script will need to be able to intercept timestamps and screen names in addition to the data the program is already equipped to capture. Timestamps are easy, because they are handled entirely by the client application and aren't really part of the protocol. It's really as easy as adding a #{Time.now} to the relevant output string.
Screen names are a lot more challenging. The OSCAR protocol only embeds the destination screen name in outgoing messages and the sender screen name in incoming messages. As a result, it is very difficult to identify the screen name of the other party associated with each message. The packet sniffer will have to intercept the screen names during the sign-on process and associate those screen names with a local network IP address. I'm not sure yet, but the fact that the OSCAR protocol uses encryption for login strings might make this a lot more interesting.
The script has to be able to intercept data from two separate network interfaces simultaneously. In other programming languages this might be a problem, but thanks to Ruby's excellent support for simple threading, I can do it with relative ease. The following is a trivial example that demonstrates how to monitor packets from multiple interfaces at the same time using Ruby:
pc1 = Pcaplet.new("-s 1500 -i eth1")
pc2 = Pcaplet.new("-s 1500 -i eth3")
f1 = Pcap::Filter.new("tcp", pc1.capture)
f2 = Pcap::Filter.new("tcp", pc2.capture)
def display dev, pck
puts "#{dev}: #{pck.tcp_data}"
end
fork {
pc1.each_packet {|pk| display "eth1", pk}
}
pc2.each_packet {|pk| display "eth3", pk}
The power and elegance of Ruby never cease to impress me.
Tags: ruby, programming, pcap, oscar
Posted on 2006-06-14
GNOME's built-in screenshot utility really sucks. It must be invoked every time a single screenshot is taken, the interface doesn't facilitate capture of specific windows or screen regions, there is no easy way to save the same capture to more than one location, and the tiny preview makes it difficult to evaluate the captured image before I choose to save it. When I need to make screenshots for my articles, I typically use the import command provided by ImageMagick. I finally got tired of putting up with suboptimal screen capture tools and decided to write my own.
My new screenshot utility features a full-size preview, the ability to capture individual windows or specific screen regions, an integrated file selection component, support for saving the captured image directly to a remote location, and support for saving the same capture to multiple locations. The entire utility took me about an hour to build, including time spent researching the various APIs. The current version is approximately 25 lines code, not including comments or blank lines. I designed the interface with Glade and wrote the code with Ruby. The network transparency features are all implemented with GNOME's VFS library and the screen capture functionality is provided by Ruby ImageMagick bindings.
The effectiveness of my utility, the relative ease with which I developed it, and the brevity of the code are all a testement to the power of Ruby and GNOME. I'm convinced that Ruby and Glade provide the best solution available for rapid application development on Linux. The GNOME bindings for Ruby are excellent and relatively complete, but there are still problems in a few places. The GnomeVFS bindings are currently undocumented, and the API does not behave the way one would expect it to. GnomeVFS::File diverges from the traditional File class in several respects. The most frustrating difference relates to file creation. It should be possible to create a new remote file thusly:
GnomeVFS::File.new("sftp://cixar.com/home/segphault/test.txt", 2)
Unfortunately, the above yields a "File not found" error. I queried the folks in the relevant IRC channel, but didn't get much help. After a great deal of experimentation, I filed a bug report. I then proceeded to examine the relevant source code to see if I could find out why file creation was raising a "File not found" error. Careful examination of the file_initialize function revealed that file creation only transpires when the File.new method is passed three arguments. The missing argument indicates whether or not the program should refuse to overwrite existing files. The following successfully performs the creation operation:
GnomeVFS::File.new("sftp://cixar.com/home/segphault/test.txt", 2, false)
In any event, my new utility works quite well, and you can benefit from it too, because I am distributing it under the GPL. You can read the code or download the utility. Keep in mind that it has quite a few dependencies, so you might not be able to run it if you can't get the necessary libraries.
Tags: ruby, programming, imagemagick, gnome, glade
Posted on 2005-08-24
After reading an article about network monitoring with ngrep at Newsforge, I developed an interest in libpcap, a portable network packet sniffing library originally designed by LBL.
Packet sniffing is useful for a wide variety of tasks, particularly reverse engineering network protocols and spying on local network users. I found some Ruby bindings and started messing around. My first goal was to write a small network sniffer that would display http GET requests transmitted by any system on the network. I monitored packets transmitted by Firefox Get requests and managed to build a filter capable of extracting them. The filter also appears to work with Safari, and to a very limited extent, with IE, which appears to use a slightly different format for the requests.
Once I had basic GET filtering figured out, I decided to construct a system that would simplify filter extension. My PacketSniffer class allows me to add filters using pcap filtering strings and anonymous functions. The end result is a psuedo-language for network sniffing. Filter definitions look like this:
ps.filter 'tcp and dst port 80', proc {|pk|
if pk.tcp_data =~ /GET(.*)HTTP.*Host:([^\r\n]*)/xm
puts "(->) <#{pk.src}> http://#{$2.strip}#{$1.strip}"
end
}
The first argument of the filter method is the pcap filter string, 'tcp and dst port 80', which, in this case, tells libpcap to filter all tcp packets sent to port 80 of a remote server. The second argument of the filter method is an anonymous function that will be called every time libpcap receives a packet that meets the constraints of the filter string.
Next, I wanted to make an AIM message sniffer. Intercepting AIM packets is really not that difficult, it's just a matter of figuring out which port AIM uses (5190) and building pcap filter strings to grab packets sent to or recevied from that port. Parsing the packets OTOH is a pain in the ass. The OSCAR protocol is an insane mess and there are a lot of inconsistencies between various implementations. My aim_msg_parse function is really kludgy, but it is now capable of extracting the AIM SN of the user sending the message as well as the message itself.
The current version of my script works relatively well. In tests on my own network, it was capable of intercepting AIM and GET packets from OS X, Windows and Linux systems. If you want to try it out yourself, you will need my script, libpcap, and the ruby bindings. On Linux systems, you must run the script as root. I have not run it on a Windows system, but with a Windows port of libpcap, it might work.
Tags: ruby, programming, pcap, oscar
Posted on 2005-07-23
In my
article
on innovations in window management, I mention the
wmctrl
utility, which allows users to manage X11 windows from the command line. It is compatible with most netwm/gnome compliant window managers, and it supports the vast majority of the
EWMH specification.
I'm fascinated by the capabilities of this helpful little utility, but I'm frustrated with some of its limitations.
Using the source code for wmctrl as a guide, I have constructed a window management
library
for Ruby in C. This library provides users with an intuitive and concise syntax for window management and control. Here are some simple examples:
Window.each do |w|
w.close if
w.winclass == "gaim" and
w.title != "Buddy List"
end
Window.filter {|w| w != /Mozilla/}
Window.current.desktop = (Window//terminal/)[0].desktop
Window.each do |w|
puts "#{w.title} - #{w.winclass} - #{w.desktop}"
end
Eventually, I hope it will be comprehensive enough to provide any window manager with all the Sawfish features i've become accustomed to using. At that point I plan to construct a daemon that will allow me to associate keyboard shortcuts with individual Ruby functions. I'll be able to utilize my keyboard-fu with a more featureful window manager. Right now i'm thinking about switching to Flux, or something else with window tab support.
Tags: ruby, programming, libwm
Posted on 2005-02-26
There are a number of frustrating bugs in the Ruby interface for Vim that make it
difficult to utilize effectively. Deleted buffers are still accessible to the Ruby
interface, and are still counted by the Buffer count class method. Additionally,
changes to displayed but inactive buffers are not refreshed appropriately.
I have managed to fix the deleted buffer problem in the buffer access method and
in the buffer count method. I have also managed to fix the problems with screen
refresh for append and delete operations. There are probably other situations in
which the the refresh is not done correctly, but I havent found them yet.
In addition to my various bug fixes, I have written an extension to the interface
in native ruby that can be loaded at run-time in a vimrc file. Using my new Ruby
interface extensions, I've managed to port my PyRun tools to Ruby, and make some
nice improvements to them.
I plan on documenting my new Vim extensions at the moot eventually, but for now,
if you are interested you can get the code from the cixar web site. To add my
bugfixes to Ruby interface, get
my if_ruby.c file
and replace the one in your code base with it, and then recompile Vim. Then,
you can download
my native ruby interface extension
and put it somewhere in your .vim directory. Then you just have to add a line
in your vimrc file to include it.
Tags: ruby, programming, vim
Posted on 2005-01-11