Kirsle.net logo Kirsle.net

Tagged as: Tk

Go UI Toolkit and Other Libraries
May 1, 2020 by Noah

As I've been working on my videogame (codenamed Project: Doodle), I created a few Go libraries that I'm releasing as open source software that can be used to create other, unrelated applications (other games, other kinds of graphical programs, etc.)

These libraries are still in "early development" (meaning I may change their API around a bit as I refactor and add new features) but they are generally stable and I'm good about documenting changes in the code, if you wanna play around with these and aren't afraid of occasional breakages.

This is just a quick blog post letting people know about these libraries. I'll probably post again when these libraries reach a stable "1.0" state, where their API won't change and they'll have a degree of stability guaranteed to them.

I'm generally using GitHub as a mirror for these libraries and will accept issues and pull requests filed on GitHub (I wouldn't expect or want anybody to sign up an account on my local Gitea instance).

render: A Rendering Engine (SDL2, HTML Canvas)

render example screenshot

I decided early on for my game that I would be using libSDL for my game's graphics, audio, controller inputs and so on -- at least to start out with. I didn't want my game to rely on SDL though: I needed the flexibility to swap it out for OpenGL or Vulkan or Metal or any other back-end driver as needed to expand my game to future platforms.

So I created go/render as a "rendering engine library" for 2D graphics in Go. It presents an API interface for drawing pixels to the screen which can be implemented by various back-end "drivers" that do the real work.

Currently it supports SDL2 for desktop applications (Linux, macOS and Windows) as well as a WebAssembly driver that uses the HTML Canvas API. (I have a build of my game to WebAssembly, but WASM performance is not great yet.) Examples are included in the git repo for both desktop and WASM applications.

ui: A User Interface Toolkit (SDL2, HTML Canvas)

ui toolkit screenshot

My game also required a UI toolkit for easily adding buttons, panels, windows and basic user interface controls to the game.

There were a handful of options I could've gone with: desktop UI toolkits like Gtk+ or Qt could've wrapped around my SDL surface and provided menu bars and button toolbars, but I wanted to minimize my inclusion of C or C++ libraries with my Go application. I was fortunate that go-sdl2 provided clear documentation how to cross-compile my program for Windows, and I didn't wanna push my luck bringing in yet more C libraries that might've made my game harder to ship. This also ruled out some potential SDL2-based C libraries for UI controls as well.

So I created my own UI toolkit in Go, and it uses my go/render library as its graphics back-end: meaning my UI toolkit can work for SDL2 desktop applications and as WebAssembly applets.

The library's API is inspired by the Tk GUI toolkit which I had prior experience with in Perl and Python (see my Tk blog posts).

It currently supports widgets such as Labels, Buttons, Checkboxes, Tooltips and (virtual) Windows (with title bars that can be dragged around and closed). The Frame widget allows easy arrangement of child widgets using Tk-style Pack and Place controls.

Future planned widgets include: menus and menu bars, tabbed frames, text input boxes, scrollbars and sliders (in roughly that order).

audio: A Simple Audio Engine

The newest library implements a simple audio engine for playing music and sound effects. My game needed these, and doesn't have any fancy requirements yet, so this library provides the basics for loading music (.mp3 and .ogg) and sounds (.wav) and playing, pausing and stopping them.

Currently it only supports the SDL2 (Mixer) driver. This module is independent from go/render and you can mix and match (or not) that library.

Future planned features include: adding WebAssembly support (Web Audio API), maybe branch out to other back-end drivers as needed.

Tags: 0 comments | Permalink
Python/Tk Experiments
October 8, 2014 by Noah

I decided to take a look at the Tk GUI framework for Python and put together a simple mockup GUI to test various things.

I'd worked with Tk before in Perl (see my Perl CyanChat Client) and if you look at some of the Linux screenshots on that page, Tk looks ugly as hell in Perl.

Python's implementation of Tk (which they call Tkinter) is more modern than Perl's, and there's other neat helper modules like ttk which provides the Tk "Tile" theming engine which makes the standard Tk widgets look more modern, and takes a CSS-style approach to theming your widgets: instead of manually specifying things like background and foreground colors in each widget you program, you keep all that stuff in one central place and refer to it by name from the widgets.

For my Python mockup test app, I put together a rough copy of my Perl CyanChat Client GUI.

At first I was trying to use the ttk/Tile versions of the widgets (such as Button, Entry, etc.), but I ran into a rather annoying roadblock: in PCCC, my text Entry widgets have black background colors, and the insertion cursor (the little flashing I-beam in a text box) is also black by default. So when clicking in the text box, you wouldn't be able to see the insertion cursor.

In the standard Tkinter Entry widget, you can use the insertbackground option to change the color of the insertion cursor. But in ttk/Tile? There is no insertbackground option. Source - it's just not supported in ttk/Tile and their theming engine.

So I decided to not use ttk and just use the standard Tk widgets. I liked ttk's centralized styling system though, so I made a central class of dictionaries full of configuration attributes that I could easily reference when setting up my widgets. So, I eventually got my GUI put together and it looked nice enough I guess...

Tk widgets with ugly scrollbar

Except for those ugly scrollbars. The "1980s 3D" look to the scrollbar and those ugly triangle arrow widgets are from the Motif GUI which Tk was originally modeled to look like. It's ancient and it's ugly. This was also one of the main reasons why my Perl CyanChat Client looks so horrible under Linux, because this is Tk and Tk is ancient.

The Tile theming engine is supposed to fix this, but I wasn't using Tile in my code because of the aforementioned text insertion cursor problem. The best I could do with the standard Tk scrollbar is color it to make it look kind of "cool" at least, so I made it all black and grey to fit the theme of the rest of my GUI.

But then I figured out I can mix and match the widgets. I could import the Scrollbar from ttk while importing all the other widgets from Tkinter. The result?

Nice scrollbars!

That's better.

I probably won't create a full CyanChat client in Python because I really don't care about CyanChat much anymore, so this was mostly just me messing around with Tk and seeing how practical it is for certain use cases. But here's the source code anyway.

There's a few interesting things in the code, like I created my own "Scrolled" class for wrapping a widget in a scrollbar (works with Text and Listbox), so it's kinda like Python's ScrolledText module, but it's really more like Perl's Tk::Scrolled module in that it can wrap arbitrary widgets, not just Text.

Also, Tkinter's Text widget can't be made read-only. You can make a text box disabled, but that also prevents programmatic insertions/deletions as well. So I made a little function for inserting text that would first re-enable it, then insert text, then disable it again.

#!/usr/bin/env python

"""My test script for Python/Tk experimentation."""

import Tkinter as tk
from Tkinter import Tk, StringVar, Frame, Label, Text, Entry, Button, Listbox, END
from ttk import Scrollbar


class ChatClient(object):
    def __init__(self):
        # Styles
        self.style = MainWindowStyles()

        self.setup()

    def setup(self):
        self.mw = Tk()
        self.mw.title("Python CyanChat Client")
        resize_and_center(self.mw, 640, 480)

        # Variables
        self.nickname = StringVar(self.mw, "Kirsle")
        self.message = StringVar(self.mw, "--disabled--")

        # Top Frame (name entry box, buttons, conn status)
        self.login_frame = Frame(self.mw, **self.style.Frame)
        self.lower_frame = Frame(self.mw, **self.style.Frame)
        self.login_frame.pack(side="top", fill="x")
        self.lower_frame.pack(side="top", fill="both", expand=1)

        # The lower left (message entry, chat history) and lower right
        # (who lists)
        self.left_frame = Frame(self.lower_frame, **self.style.Frame)
        self.right_frame = Frame(self.lower_frame, **self.style.Frame)
        self.right_frame.pack(side="right", fill="y")
        self.left_frame.pack(side="right", fill="both", expand=1)

        # The message entry & chat history frames
        self.message_frame = Frame(self.left_frame, **self.style.Frame)
        self.dialogue_frame = Frame(self.left_frame, **self.style.Frame)
        self.message_frame.pack(side="top", fill="x")
        self.dialogue_frame.pack(side="top", fill="both", expand=1)

        ###
        # Top Frame Widgets
        ###

        self.name_label = Label(self.login_frame,
            text="Name:",
            **self.style.Label
        )
        self.name_entry = Entry(self.login_frame,
            textvariable=self.nickname,
            width=20,
            **self.style.DarkEntry
        )
        self.enter_exit_button = Button(self.login_frame,
            text="Enter chat",
            **self.style.Button
        )
        self.status_label = Label(self.login_frame,
            text="Connected to CyanChat",
            **self.style.ConnectedLabel
        )
        self.name_label.pack(side="left", padx=5, pady=5)
        self.name_entry.pack(side="left", pady=5)
        self.enter_exit_button.pack(side="left", padx=5, pady=5)
        self.status_label.pack(side="left")

        ###
        # Message Frame Widgets
        ###

        self.message_entry = Entry(self.message_frame,
            textvariable=self.message,
            state="disabled",
            **self.style.Entry
        )
        self.message_entry.pack(
            side="top",
            fill="x",
            padx=10,
            pady=10,
            expand=1,
        )

        ###
        # Who Frame Widgets
        ###

        self.who_label = Label(self.right_frame,
            text="Who is online:",
            anchor="w",
            **self.style.Label
        )
        self.who_label.pack(side="top", fill="x")

        self.who_list = Scrolled(self.right_frame, Listbox,
            attributes=self.style.Listbox,
            scrollbar=self.style.Scrollbar,
        )
        self.who_list.pack(side="top", fill="both", expand=1)

        for i in range(200):
            self.who_list.widget.insert(END, "Anonymous{}".format(i))

        ###
        # Dialogue Frame Widgets
        ###

        self.dialogue_text = Scrolled(self.dialogue_frame, Text,
            attributes=self.style.Dialogue,
            scrollbar=self.style.Scrollbar,
        )
        self.chat_styles(self.dialogue_text.widget)
        self.dialogue_text.pack(side="top", fill="both", padx=10, pady=0, expand=1)

        # Dummy junk
        messages = [
            [["[Kirsle]", "user"], [" Hello room!"]],
            [["\\\\\\\\\\", "server"], ["[Kirsle]", "user"], [" <links in from comcast.net Age>"], ["/////", "server"]],
            [["[ChatServer] ", "server"], ["Welcome to the Cyan Chat room."]],
            [["[ChatServer] ", "server"], ["There are only a few rules:"]],
            [["[ChatServer] ", "server"], ["   Be respectful and sensitive to others"]],
            [["[ChatServer] ", "server"], ["   And HAVE FUN!"]],
            [["[ChatServer] ", "server"], [""]],
            [["[ChatServer] ", "server"], ["Termination of use can happen without warning!"]],
            [["[ChatServer] ", "server"], [""]],
            [["[ChatServer] ", "server"], ["Server commands now available, type !\\? at the beginning of a line."]],
            [["[ChatServer] ", "server"], ["CyanChat Server version 2.12d"]],
        ]
        for i in range(80):
            messages.append([["[ChatClient]", "client"], [" Connecting..."]])
        messages.reverse()
        for line in messages:
            self.insert_readonly(self.dialogue_text, 0.0, "\n")
            line.reverse()
            for part in line:
                self.insert_readonly(self.dialogue_text, 0.0, *part)
        #self.insert_readonly(self.dialogue_text, END, "[Admin]", "admin")

    def chat_styles(self, widget):
        """Configure chat text styles."""
        # User colors
        widget.tag_configure("user", foreground="#FFFFFF")
        widget.tag_configure("guest", foreground="#FF9900")
        widget.tag_configure("admin", foreground="#00FFFF")
        widget.tag_configure("server", foreground="#00FF00")
        widget.tag_configure("client", foreground="#FF0000")

    def insert_readonly(self, widget, *args):
        """Insert text into a readonly (disabled) widget."""
        widget.widget.configure(state="normal")
        widget.widget.insert(*args)
        widget.widget.configure(state="disabled")

    def start(self):
        self.mw.mainloop()


class MainWindowStyles(object):
    """Simple Python class to hold style-related configurations for widgets."""
    Frame = dict(
        bg="#000000",
    )

    BaseLabel = dict(
        font="Verdana 8",
    )
    Label = dict(
        bg="#000000",
        fg="#CCCCCC",
        **BaseLabel
    )
    ConnectedLabel = dict(
        bg="#000000",
        fg="#00FF00",
        **BaseLabel
    )

    BaseFormCtrl=dict(
        highlightthickness=0, # Removes stupid border around the widget
    )

    BaseEntry = dict(
        insertwidth=1,
        selectborderwidth=0,
        selectbackground="#0099FF",
        font="Verdana 8",
        **BaseFormCtrl
    )
    Entry = dict(
        bg="#FFFFFF",
        fg="#000000",
        disabledbackground="#000000",
        disabledforeground="#666666",
        insertbackground="#000000",
        **BaseEntry
    )
    DarkEntry = dict(
        bg="#000000",
        fg="#CCCCCC",
        insertbackground="#FFFFFF", # Text insertion blinking cursor
        **BaseEntry
    )

    Listbox = dict(
        bg="#000000",
        fg="#CCCCCC",
        **BaseFormCtrl
    )

    Dialogue = dict(
        bg="#000000",
        fg="#CCCCCC",
        #disabledbackground="#000000",
        #disabledforeground="#CCCCCC",
        wrap=tk.WORD,
        state="disabled",
        **BaseEntry
    )

    Button = dict(
        bg="#000000",
        fg="#CCCCCC",
        activebackground="#000000",
        activeforeground="#0099FF",
        **BaseFormCtrl
    )

    # If using the Tkinter scrollbar, uncommon these. If using the ttk
    # scrollbar, use ttk's theming system instead.
    Scrollbar = dict(
        #relief="flat",
        #troughcolor="#000000",
        #bg="#606060",
        #activebackground="#999999",
        #borderwidth=1,
        #width=12,
        #highlightthickness=0,
    )


class Scrolled(object):
    """My own implementation for adding a scrollbar to a widget. Similar in
    principal to Python's ScrolledText module, but it works on other widgets too
    (this script uses it on Listbox too). So it's more like the Perl/Tk module
    Tk::Scrolled in that it can wrap any widget, in theory."""

    def __init__(self, master, widget_class, attributes=None, scrollbar=None):
        """
        master is the parent widget
        widget_class is the class, like Text or Listbox
        attributes are attributes for the widget
        scrollbar are attributes for the scrollbar
        """
        if attributes is None:
            attributes = []
        if scrollbar is None:
            scrollbar = []

        self.master = master

        # Parent frame to hold the widget + scrollbar
        self.frame  = Frame(master)

        # The scrollbar
        self.scrollbar = Scrollbar(self.frame, **scrollbar)

        # The widget itself
        self.widget = widget_class(self.frame,
            yscrollcommand=self.scrollbar.set,
            **attributes
        )
        self.scrollbar.configure(command=self.widget.yview)

        self.scrollbar.pack(side="right", fill="y")
        self.widget.pack(side="right", fill="both", expand=1)

    def widget(self):
        """Get at the inner widget."""
        return self.widget

    def scrollbar(self):
        """Get at the scrollbar widget."""
        return self.scrollbar

    def pack(self, **kwargs):
        """Wrapper so that pack() works as you'd expect."""
        self.frame.pack(**kwargs)


def resize_and_center(win, width, height):
    """Resize a window and center it on the screen."""
    screen_w = win.winfo_screenwidth()
    screen_h = win.winfo_screenheight()
    geometry = "{}x{}+{}+{}".format(
        width,
        height,
        screen_w / 2 - width / 2,
        screen_h / 2 - height / 2,
    )
    win.geometry(geometry)


if __name__ == "__main__":
    app = ChatClient()
    app.start()

Tags: 2 comments | Permalink
Running Tkx on Perl
June 22, 2012 by Noah

I took a fresh look at trying to wrangle Tkx into working on the standard Perl distribution.

Tkx is ActiveState's module that provides a modern, updated Tk framework for Perl. I previously wrote about how the old Tk module for Perl has been neglected for many years and is extremely outdated and has very little hope for being improved on. Tkx brings a more updated Tk interface to Perl.

The only problem is, it only really runs well on ActivePerl, and not the stock version of Perl. I've been testing Tkx over the years on various versions of Fedora Linux and Perl, and every time I'd get the same results: segmentation faults. It was impossible to run any code that uses the Tkx module, because attempting to do so much as use Tkx; would cause a segfault. I chalked it up to "it only works with ActivePerl" and left it alone. But now I decided to give it another go.

I was seeing the same symptoms this time as before. When trying to install it with cpan or cpanm, it would fail to install because its test suite was failing (giving errors like, can't find package tk). If I installed it while skipping the test suite, the tkx-ed example program would give segfaults when run. Just as before.

I found out through tinkering with it that I needed to yum install tk (it makes sense; Tkx is a wrapper around Tcl/Tk for Perl, so you need "the" Tk installed for it to work). With this, running make test would have it run through the test suite and I'd see all the graphical Tk windows pop up and disappear. But running tkx-ed would still give segfaults.

I believe I'd gotten to this point before. The test suite would work, but nothing else would. So I decided to try running the test suite "by hand", perl t/LabEntry.t. Segmentation fault. What? How can the test suite run all these scripts successfully but I can't run them myself? So, I dissected the Makefile that was used for the test suite.

Long story short, this doesn't work:

[kirsle@fireworks Tkx-1.09]$ perl tkx-ed 
Segmentation fault (core dumped)

But this does:

[kirsle@fireworks Tkx-1.09]$ PERL_DL_NONLAZY=1 perl tkx-ed

tkx-ed screenshot

Shazam. This is tkx-ed running on a stock version of Perl 5.14.2 on Fedora 17. It seems that the PERL_DL_NONLAZY environment variable is required for the Tkx module to work. This is weird.

I don't call this a victory though. Requiring this environment variable to be set for your Tkx app to run isn't very ideal. If you wrote and distributed a Tkx app for Linux users, you'd require the users to run a Bash shell script in order to launch your program instead of just running your Perl program directly like they'd expect. But, at least it works!

Note: I also needed to yum install bwidget because tkx-ed would give a "can't find package BWidget" error without it).

Update (6/24/12):

As for that environment variable issue, I found that this will work:

#!/usr/bin/perl

BEGIN {
    $ENV{PERL_DL_NONLAZY} = 1;
}

use 5.14.0;
use strict;
use warnings;
use Tkx;

...

Since your BEGIN block gets run before Perl attempts to load the modules, you can set the variable inside your script. And now a simple perl yourscript.pl will work without segfaults. :)

Tags: 5 comments | Permalink
Perl Module Neglect
March 20, 2011 by Noah
This is just a sort of review, or an organizing of thoughts, about the general state of the Perl modules available on CPAN.

I love Perl as a programming language. It's easy, fast enough for almost any application, and is often called the "swiss army chainsaw" of programming languages because it makes easy tasks easy and hard tasks possible. But, it doesn't excel very well in a couple of areas which I'll outline below, due to the state of neglect of some of its modules and ports.

Tk GUI Toolkit

The de facto module for the Tk framework for Perl is aptly named "Tk," as in:

use Tk;
This module is probably one of the most neglected modules on CPAN. It was a direct port from the Tcl/Tk that was current at the time that Perl/Tk was written. The result is that, when you run a Perl/Tk program on any platform other than Windows, it resembles an excruciatingly ugly Motif style application (see my screenshots of my Perl CyanChat Client for examples). Under Windows, though, a Perl/Tk app more or less fits in.

Because Perl/Tk was a direct port of a very old version of Tk, updating it to keep it modern has been a difficult task and so naturally nobody has done it. The only love Perl/Tk gets these days is maintenance work just to be sure it can still be compiled for modern versions of Perl.

So what can we do about this?

There are a couple other Tk implementations for Perl: Tkx by ActiveState and Tcl::Tk. These two modules are modern Tk implementations for Perl, and so they look very nice on every platform. But how usable are they?

Tkx is ActiveState's creation, and I've only been able to get it to work when using ActivePerl. This is fine for Windows, where ActivePerl is arguably the most popular Perl interpreter for Windows. But when I tried compiling Tkx for a stock Perl that ships with Fedora Linux, it gives segmentation faults and crashes. It's not usable under Linux with a stock version of Perl.

There's an ActivePerl for Linux, though, but the problem is that this Perl installation would be independent from the stock Perl that comes with your operating system. So if I needed to install another third party module to use with a ActivePerl/Tkx application, I wouldn't be able to run a simple "yum install perl-{module}" command to get it. I'd have to use ActivePerl's ppm tool, if it even had the module I want. Otherwise I need to compile the module myself for ActivePerl. Yuck. This isn't "the Linux way" of doing things. The package manager should be aware of everything that you install on your system.

ActivePerl/Tkx is out of the question for Linux then. What about Tcl::Tk? I've attempted to compile and use Tcl::Tk on a few different versions of Fedora Linux and every time they give me segmentation faults just like Tkx did. No good.

So Tk is one thing that Perl can't do very well due to lots of neglect. In contrast, the Tk ports for Python, Ruby and Tcl (of course) are much better maintained.

I know there are ports to GTK+, Wx and Qt for Perl as well, if you want to create a GUI. In my experience: Wx has a completely broken HTML widget in Perl and parts of the demo crash, GTK+ is neglected too, and I never got Qt to compile.

SDL

The Simple DirectMedia Library for Perl. This module is horribly neglected as well. Ideally, it could be used to be able to create 2D videogames using pure Perl, just like you would be able to make games in Python using the pygame library (which is the SDL port for Python).

The Perl SDL module is very "feature incomplete." The only notable thing anybody has made with Perl SDL was Frozen Bubble, and the developers of that had to hack up their code a lot to get around the limitations of the SDL module.

Perl for games? Sure, if you want to blow the dust off the SDL module and are ready to do a ton more hacking than you wanted to just to get it to work.

Most other languages have modern SDL ports. Pygame comes to mind as I mentioned before, which has a fairly active community of users actually creating games in Python.

GD Graphics Library

Ah, GD, the popular graphics library used by many PHP script kiddies the world over, for doing all sorts of image generation and modification tasks. A user uploaded their picture to your site, how do you scale it down to make thumbnails? GD. How do you stamp your own branding on the corner of their image? GD. How do you generate dynamic statistic images for users to embed in forum signatures? GD.

Perl's GD module though is in a pretty bad state of neglect. All it's good for in Perl is scaling images down (and even then it doesn't do very well; look at my photo album on kirsle.net; it can't seem to save a jpeg image with any good amount of quality. Every time it saves an image it comes out extremely grainy and it completely ignores any settings to make it not do this).

Generating an image from scratch? Maybe you can get it to work with enough effort, but good luck getting text to show up in any color besides black. Using a "template image" to generate a dynamic image off of? Good luck coming up with new colors to use that aren't in the template image. It's just a giant mess.

Image::Magick or Imager are better alternatives, at least. I started using Image::Magick on all my new web development projects, and the next iteration of kirsle.net's code will be using that to handle images instead of GD.

What is Perl good for?

Perl does well as a shell scripting language for system administrators, and it does well for web development. GD sucks for it, but it does have Image::Magick and Imager for dealing with photo manipulation for a web application.

It's also good, of course, for regular expressions and number crunching, which is what it was targeted towards in the first place.

It's not particularly strong at anything else though. Creating a graphical application? Good luck. Creating a game? Don't think about it. Use Python instead.

There are a ton of other modules on CPAN collecting dust that don't work anymore, or don't work particularly well. Net::YMSG for interacting with Yahoo Messenger? Completely broken. Net::AIM for AOL Instant Messenger? Not working (but Net::OSCAR still works as far as I last checked). Audio::Audiere? I don't know anybody who's managed to compile it.

Part of me hopes Perl 6 will be usable soon and I can start learning that (contrary to popular belief, Perl 6 is not the successor to Perl 5 but is a completely separate language), and that any new modules for Perl 6 will be modern (using modern Tk and SDL for example) and will be maintained well in the future, as the ports for Python and other languages are. But part of me just thinks I should put a lot more effort into making Python my new favorite language and using Perl only for the few tasks that Perl does well (like for shell scripting).

Tags: 0 comments | Permalink
Webcam Streaming in Perl/Tk (Linux)
September 2, 2009 by Noah

Late last week I started thinking about how to access a webcam device from within Perl. I have no direct need of such capability at the time being but I wanted to know how to do it in case I wanted to do something in the future involving webcams.

A few years ago when I used mostly Windows I found EZTwain, a DLL library for accessing a webcam in Windows using the TWAIN protocol (which as I understand is obsolete by now). The DLL was a pain in the butt to use and I couldn't get it to work how I wanted it to (it insisted on displaying its own GUI windows instead of allowing my Perl script to directly pull a frame from it without a GUI).

Besides that there's pretty much no libraries Perl has been built to use yet that can access a webcam. So, I started looking into using third-party programs such as ffmpeg and mplayer/mencoder to provide the hardware layer for me so that Perl can get just the jpeg images out and do with them what it needs.

Of these programs I wanted to use ffmpeg the most, because I know for sure there's an ffmpeg.exe for Windows, which might mean that whatever code I come up with might be reasonably portable to Windows as well.

After some searching I found some command-line sorcery for using ffmpeg over SSH to activate the camera on a remote computer and stream the video from it over SSH to the local system, and display it in mplayer:

ssh user@remoteip ffmpeg -b 100K -an -f video4linux2 -s 320x240 -r 10 -i /dev/video0 -b 100K -f ogg - | mplayer - -idle -demuxer ogg

Using the basic ffmpeg command in there, along with some hours of research and poking around, I eventually came up with a command that would activate the webcam and output a ton of jpeg images with consecutive file names, of each frame of video that the camera recorded:

ffmpeg -b 100K -an -f video4linux2 -s 640x480 -r 10 -i /dev/video0 -b 100K -f image2 -vcodec mjpeg test%d.jpg

The mjpeg codec (or "motion jpeg"), in ffmpeg, really means it's a bunch of jpeg images all combined together one after the other (the start of each jpeg image can be seen in hex by looking for the magic number, 0xFFD8). The "image2" format here means that each frame from the mjpeg stream gets written to an individual image file, in the format test%d.jpg where %d is a number that goes up for each image written.

By changing the image2 to image2pipe instead, the output (all the jpeg images in the mjpeg stream) is sent through the program's standard output, so it can be piped into another program, or read from in Perl.

So in Perl I opened a pipe that executes this command and have the script read from it, reading all the jpeg images and then displaying them in a Perl/Tk window as they come in. In effect: a live webcam stream, where Perl is entirely in control of the jpegs as they come in from ffmpeg and can do with them whatever it wants!

Tk Stream

I added a button to my GUI for taking a snapshot and saving it to disk (in actuality, as each complete image is read and displayed, it's kept around in memory until the next image is read and displayed... so this button just saves the last full image to disk).

Here's my proof of concept Perl code:

#!/usr/bin/perl -w

# Perl/Tk Webcam Streamer and Snapshot Taker
# Proof of Concept
# Author: Casey Kirsle, http://www.cuvou.com/

use Tk;
use Tk::JPEG;
use MIME::Base64 "encode_base64";

# Some things that might need to be configured.
my $device = shift(@ARGV) || "/dev/video0";
if ($device =~ /^\// && !-e $device) {
    die "Can't see video device: $device";
}

# Tk MainWindow
my $mw = MainWindow->new (
    -title => 'Tk Stream',
);
$mw->protocol (WM_DELETE_WINDOW => \&onExit);

# A label to display the photos.
my $photo = $mw->Label ()->pack();

# A button to capture a photo
my $capture = $mw->Button (
    -text => "Take Picture",
    -command => \&snapshot,
)->pack();

$mw->update();

You can download it here. It should run on any Linux distribution and it depends on having Perl/Tk and ffmpeg installed, and the video4linux2 system (any modern distro will have that).

In the ffmpeg command here you'll see I also piped the output into a quick Perl script that substitutes all the jpeg headers so that they begin with "KIRSLESEP" -- this was to make it easier to split the jpegs up while reading from the stream.

Since this uses ffmpeg and there's an ffmpeg.exe for Windows, this might work on Windows (you'll definitely need to modify the arguments sent to the ffmpeg command, though). I don't currently have access to a Windows machine with a webcam, though, so I can't work on that just yet.

Anyway, here it is: webcam access in Perl!

Tags: 10 comments | Permalink
Tk::StyleDialog
September 18, 2008 by Noah
Since there was enough interest in my two-year-old program, ErrorGen, I've created a Perl/Tk module that does basically what ErrorGen does.

Here's a screenshot:
Synopsis

It's only a module so far that can be included in other Perl/Tk applications. But it's one very large step closer to me creating a simplified tool to spawn error boxes which could be provoked from batch files or scripts. It will probably have a syntax similar to the GNOME program, Zenity.

CPAN takes a few hours to index module updates but the new module will be available at Tk::StyleDialog on CPAN.org.

UPDATE: I've thrown together a quick program called ZenMsg (a name derived from GNOME's Zenity, but since my program only does dialog boxes, it's called ZenMsg).

I've added it as a new tab to the ErrorGen page. Let me know if it can be improved. I had to use ActiveState PerlApp to compile it because PAR::Packer (which I usually prefer to use) was giving me trouble and I didn't have the time or motivation to setup a clean new compiling environment for it. PerlApp may be a bit too limiting.

Tags: 0 comments | Permalink
Tk::HyperText Uploaded
July 14, 2008 by Noah
Finally, I've shipped Tk::HyperText version 0.06 on its way to CPAN's network. It won't be up immediately but in the next few hours the following link will go to the page for version 0.06:

Tk::HyperText.

Here's a screenshot of the demo program that comes with it:

Tk-HyperText

Tags: 0 comments | Permalink
Tk::HyperText Rewrite
May 8, 2008 by Noah
Today I've begun reprogramming my Tk::HyperText widget. The old module didn't use any "real" HTML parsers, so it was reading the HTML code in as plain text and trying to figure out how to display it that way. The new module is going to use HTML::TokeParser, which will make the code cleaner and also more efficient and less buggy.

I've only been working on it for a couple of hours so far, and all it supports so far is the font tag, bold, italic, underline, and line breaks. Here's a screenshot:

Tk-HyperText Beta

The functionality of the module when it's done is going to be drastically different to what's currently on CPAN. I don't know if anyone has actually used Tk::HyperText in their programs yet, but the new module will definitely break programs that were relying on the methods provided by the old module. I'm thinking I'll have the module use similar methods and handlers to Wx::ActiveX::Mozilla.

Update (4:54 PM EDT) - The module now supports pretty much all the markup tags (not links, lists, or obscure tags like abbr and acronym though). Something else of interest is that only the first body tag found will recolorize the widget as a whole; any additional body tags will only override the colors of the current text style, so you can get the "AIM effect" with it (where each message can have its own background color which covers its entire horizontal space).

Body BG Colors

I'll probably have the new module on CPAN within a few days.

Update (5/9) - It supports tables now!

Tables!

Tags: 0 comments | Permalink