...making Linux just a little more fun!

<-- prev | next -->

# Creating a Rudimentary Kiosk System using FVWM

By Thomas Adam

### Introduction

It is always interesting to see how and where Linux is used within the wider community, especially where the general public are concerned. Indeed, it is quite often the case for public libraries to use a batch of computer terminals to offer a portal via their own intranet so that they can do searches for books, renew overdue books, etc.

However, ensuring that those terminals only provide that specific service can be something of a challenge. Because they're public-facing terminals, their functionality is likely to be limited to a specific subset of the available applications. Ensuring that these applications are the only ones the user of the terminal sees, along with making sure that no other application is accessible unless it's explicitly needed in such a way that it doesn't break the system can be an interesting task, to say the least.

This is where some sort of kiosk comes into play. Note that the concept is nothing new, although the current HOWTO on the issue is somewhat out of date. FVWM, which is mentioned in the above HOWTO, has advanced a lot since it was written. Of course, since then, desktop environments such as KDE have also had a kiosk extension added to them.

This article will focus on using Firefox as the main and only application the user will be using. By using FVWM to manage this application, it is possible to provide restrictions not only on the application, but also the environment the user will be using.

The motivation for this article is mostly derived from a series of e-mails I have been exchanging with someone who seems to be trying to setup a kiosk system in a corporate environment. The actual content of the e-mail that turned into this article (as a very odd coincidence) is born out of an e-mail from another TAG member, who asked the same question.

Originally, I had planned to discuss how to lock down Firefox. While this is applicable within the context of this article, it's also somewhat less restrictive, given that different people require different circumstances. Firefox itself has a series of extensions dedicated to kiosk browsing that you can further use to lock Firefox down. This article will concentrate on getting a window manager (FVWM) to do the rest of the work.

### Preliminaries

In the best case, what you'll probably want to do is something like the following:

- Run the application maximized
- Possibly remove window decorations (run full-screen)
- Ensure that Firefox is the only application running
- Ensure that's the only application that can be run

From first principles, here's what your ~/.fvwm/config file ought to contain. Note that any decoration changes for this are out of scope for this article; if you don't like hideous pink borders, change them. (I'm going to suggest FVWM version >=2.5.16; anything I write here won't work on FVWM 2.4.X).

### Enter FVWM

StartFunction is a function that FVWM runs at both restarts and init. So, in this, all that's needed is to start your application. Since there are some crude preventative measures one can take to ensure this application doesn't die once the WM has loaded, we'll just start this at init time.

    DestroyFunc StartFunction
    AddToFunc   StartFunction
    + I Test (Init) Exec exec firefox

That's that, all taken care of. Next thing to consider is whether you want any window decorations or not, for Firefox. Let's assume for the moment that you do want the title and borders, but want to restrict the buttons that appear on the title. That's fine; just set a style line for it:

    Style Firefox-bin NoButton 1, NoButton 2, NoButton 4, ResizeHintOverride

NoButton removes a button from being displayed in the title. ResizeHintOverride makes those column-dependent aware applications not worry about it. The number buttons are ordered thus, on a titlebar:

    +----------------------------------------------------------+
    | .  .  .  .  .                              .  .  .  .  . |
    +----------------------------------------------------------+
    | 1  3  5  7  9                             10  8  6  4  2 |
    |                                                          |

Typically (and as is the default), buttons 1, 2, and 4 are defined for normal operations of displaying a menu (button 1) and minimising and maximising an application (buttons 4 and 2 respectively).

The next thing to do is remove any possible mouse bindings on the title bar that could disrupt its operation, or that could cause some arbitrary operation to be performed on it. This also includes bindings for both the frame and the sides of the window.

    Mouse 1 SF A -
    Mouse 1 T  A -
    Mouse 2 SF A -
    Mouse 2 T  A -

That's just an example. You ought to do the same for key bindings, although by default FVWM defines no key bindings other than bringing up a popup menu.

So what happens once the application starts? You'll presumably want it maximised. Read the FvwmEvent article for details of this. Essentially, the following could be used:

    DestroyModuleConfig FE-SM: *
    *FE-SM: Cmd Function
    *FE-SM: add_window StartFirefoxMaximised

    Module FvwmEvent FE-SM

That sets up FvwmEvent using a module alias of 'FE-SM'. It's listening for the add_window event, and hence will call the 'StartFirefoxMaximised' when any new window is mapped. As for what StartFirefoxMaximised looks like:

    DestroyFunc StartFirefoxMaximised
    AddToFunc   StartFirefoxMaximised
    + I ThisWindow (Firefox-bin, !Maximized) Maximize

The function says that if the window mapped matches "Firefox-bin" as either its window name, class, or resource (class in this case), and it is not maximised, to maximise it.

And that's it, right? Pffft, if only. What if, by some means unknown to us, the window were un-maximised? That's not something we probably want within this kiosk environment, much less allowing the window to be moved once it has been maximised. That's OK, since we can restrict this. You might think it's a simple case of adding to the style definition we defined for Firefox earlier:

     Style firefox  NoButton 1, NoButton 2, NoButton 4, FixedSize, FixedPosition, !Maximizable

But that's not quite true. What happens here is that the style preferences are applied before the add_window event is triggered. Whilst it is true that "FixedSize" and "FixedPosition" do exactly what we want (i.e., doesn't allow resizing, or moving the window, or not allowing the window to become un-maximized, respectively), we have to apply it afterwards. This is where the WindowStyle command can be used.

The WindowStyle command works like Style, except that it pertains to a specific window currently mapped and visible. Under the hood, it just assigns various struct events via the window's WindowId, but that's out of scope, here. Hence in the StartFirefoxMaximised function, we can now expand upon this further:

    DestroyFunc StartFirefoxMaximised
    AddToFunc   StartFirefoxMaximised
    + I ThisWindow (Firefox-bin, !Maximized) Maximize
    + I ThisWindow WindowStyle FixedSize, FixedPosition, !Maximizable
    + I UpdateStyles

I mentioned earlier that you may or may not want any window decorations. If that's the case, then you can do the following to turn them off:

    Style firefox !Title, !Borders, HandleWidth 0, BorderWidth 0

That's a bit better, right? Well, maybe. There's still a few other considerations that should be taken into account. When a window is mapped, it is put into a layer (layer 4 by default, which can be changed using the DefaultLayers command). This is fine, but means some windows can cover up this window. This is unlikely to happen here, given that it's unlikely you're going to be running any other application, but, for the theoretical concept alone, it's best to perhaps put this window into a much higher layer than normal.

    Style Firefox-bin  NoButton 1, NoButton 2, NoButton 4, Layer 8

FVWM (like several other window managers) has virtual desktops. Wooo. You aren't going to need them here, so ditch them. You can do this using the DesktopSize command, and perhaps the DesktopName command , to define how many desks you want.

    DesktopSize 1x1
    DesktopName Main

Hence the DesktopSize command restricts us to one page defining a single desk, whose name is "Main". I also alluded to earlier ensuring that you turn off all mouse bindings. This is also true of the root window that might bring up any menus.

    Mouse 1 R A -
    Mouse 0 R A -
    Mouse 2 R A -

Again, it's unlikely to much of an issue, given the application runs in some sort of full screen mode, but it's better to cover one's bases than not at all.

There are two more things that need mentioning. Ensuring that the Firefox window is the only running instance can be a little tricky. We could prepend some stuff to the StartFirefoxMaximised function to try and enforce such a policy.

    DestroyFunc StartFirefoxMaximised
    AddToFunc   StartFirefoxMaximised
    + I ThisWindow (!Firefox-bin) Close
    + I ThisWindow (Firefox-bin, !Maximized, !Transient) Maximize
    + I TestRc (Match) ThisWindow WindowStyle FixedSize, FixedPosition, !Maximizable
    + I UpdateStyles

...although that may or may not be overkill for some purposes. It might be the case, of course, that you only want to allow a certain subset of applications to run. This would presumably be via Firefox itself (perhaps some filetypes spawn mplayer or RealPlayer), hence you could expand upon StartFirefoxMaximised even more.

    DestroyFunc StartFirefoxMaximised
    AddToFunc   StartFirefoxMaximised
    + I ThisWindow (!"App1|App2|App3|App4") Close
    + I ThisWindow (Firefox-bin, !Maximized) Maximize
    + I ThisWindow WindowStyle FixedSize, FixedPosition, !Maximizable
    + I UpdateStyles

'ThisWindow (!"App1|App2|App3|App4") Close' says something like the following: "If the window just created is not one of App1 or App2 or App3 or App4, then close it." Thus, in this way, one can conditionally place restrictions.

One final consideration. How do you ensure that the Firefox window is never closed? Well, FVWM has a style consideration "!Closable" that when applied means the application cannot be closed... almost. It only goes as far as trying to circumvent the various events generated. It does not, for example, stop xkill closing the application, or for some external source to do so.

You could use some sort of script to monitor whether the application is still running (which assumes this script also runs Firefox).

    #!/bin/sh
    firefox &
    ffpid=$!

    while sleep 60; do
      [ kill -0 $ffpid ] || {
    	# Spawn firefox
    	firefox &
    	break
      }
    done

But that's far from ideal. You might be better off using FvwmEvent to monitor when windows are closed, and to respawn them as apropos. Hence adding to the "FE-SM" definition from earlier.

    *FE-SM: destroy_window CheckWindowClosed

Tells that module alias to now also listen for any windows closing and take action. The function CheckWindowClosed might look like this.

    DestroyFunc CheckWindowClosed
    AddToFunc   CheckWindowClosed
    + I ThisWindow (Firefox-bin) Exec exec firefox

That works, right? Well, yes it does. But what if there were more than one instance of Firefox running? What if we wanted to respawn this application only if the last remaining instance of this application died? That's no problem:

    DestroyFunc CheckWindowClosed
    AddToFunc   CheckWindowClosed
    + I None (Firefox-bin) Exec exec firefox

None will only work if there's no instances of the matched window being asked for — in this case, "Firefox-bin".

### Conclusion

Some may argue that the use of a window manager is in itself overkill, given that one could easily dispense with using one, and just start Firefox in fullscreen mode as soon as X11 is logged into. However, that's potentially disastrous, given that the windows aren't controlled. A transient window (such as an open dialogue box) wouldn't be managed — if it were opened in an awkward place on the screen, there's be no way of moving it. Neither, of course, does not using a window manager preclude the possibility of closing other windows (applications) down that don't ultimately belong there.

In short, there is no real solution or easy answer to creating a true kiosk system. At best it can simply be managed by enforcing certain rules specific to its environment.

Talkback: Discuss this article with The Answer Gang

I used to write the long-running series "The Linux Weekend Mechanic", which was started by John Fisk (the founder of Linux Gazette) in 1996 and continued until 1998. Articles in that format have been intermittent, but might still continue in the future. I currently write occasional articles for LG, whilst doing a few things behind the scenes for it. I'm also a member of The Answer Gang.

I was born in Hammersmith (London UK) in 1983. When I was 13, I moved to the sleepy, thatched-roofed, village of East Chaldon in the county of Dorset. It is very near the coast, and Lulworth Cove, which is where I used to work. Since then I have moved to Southampton, and currently attend University there, studying for a degree in Software Engineering.

I first got interested in Linux in 1996 having seen a review of it in a magazine (Slackware 2.0). I was fed up with the instability that the then-new operating system Win95 had and so I decided to give it a go. Slackware 2.0 was great. I have been a massive Linux enthusiast ever since. I ended up with running SuSE on both my desktop and laptop computers. Although I now use Debian as my primary operating system.

I am actively involved with the FVWM project, writing documentation, providing user-support, writing ad-hoc and somewhat esoteric patches for it.

Other hobbies include reading. I especially enjoy reading plays (Henrik Ibsen, Chekhov, George Bernard Shaw), and I also enjoy literature (Edgar Allan Poe, Charles Dickens, Jane Austen to name but a few).

I am also a keen musician. I play the piano in my spare time.

Some would consider me an arctophile (teddy bear collector).

I listen to a variety of music.

Copyright © 2006, Thomas Adam. Released under the Open Publication license unless otherwise noted in the body of the article. Linux Gazette is not produced, sponsored, or endorsed by its prior host, SSC, Inc.

Published in Issue 128 of Linux Gazette, July 2006

<-- prev | next -->