Linux – How to let an SDL app (not running as root) use the console

consoledeviceslinuxpermissions

I want to use an SDL-based program to display graphics on the console, without having to log on from the console, and without running the program as root. For example, I want to be able to run it via ssh. The target OS is raspbian.

Here is a short example in python to illustrate the problem:

import os, pygame
os.environ['SDL_VIDEODRIVER'] = 'fbcon'
pygame.init()
s = pygame.display.set_mode()
print "Success"

This works (runs to completion, does not throw exceptions) if I run it from the console, and it works via ssh if I run it as root.

I have checked that my user is in the audio and video groups.

I have used strace to see what is different between running it from the console (which works), running it as root via ssh (also works), and running it as a regular user via ssh (doesn't work).

The first difference was that my user did not have permission to access /dev/tty0. I created a new group (tty0), put my user in that group, and added a udev rule to give that group access to /dev/tty0.

The strace output diverges at this ioctl call – the failure is show here; ioctl returns 0 when the program is run from the console or run from ssh as root:

open("/dev/tty", O_RDWR)                = 4
ioctl(4, VT_GETSTATE, 0xbeaa01f8)       = -1 EINVAL (Invalid argument)

(The addresses also differ, but that isn't important.)

Given that my program works when it runs as root, I think this means I have a permissions problem. How do I give the necessary permissions to my user to be able to run this program without logging on at the console (and without running as root)?

Best Answer

My aim was the same as original poster’s one, but with one difference: I needed to run SDL application as a systemd daemon. My Linux machine is Raspberry Pi 3 and operating system is Raspbian Jessie. There is no keyboard or mouse connected to RPi. I connect to it using SSH. My SDL app is actually a Pygame-based app. I set pygame/SDL to use the “fbcon” framebuffer driver via the SDL_VIDEODRIVER environment variable. My systemd --version output is:

systemd 215 +PAM +AUDIT +SELINUX +IMA +SYSVINIT +LIBCRYPTSETUP +GCRYPT +ACL +XZ -SECCOMP -APPARMOR

My pygame package version is: (aptitude show python-pygame):

1.9.2~pre~r3348-2~bpo8+rpi1

My libSDL 1.2 version is: (aptitude show libsdl1.2debian - on your machine package name can be different):

1.2.15-10+rpi1

The recipe

  1. Set up permissions for /dev/tty and /dev/fb0 files as described in UDude’s answer. I discovered that /dev/console permission changes are not necessary in Raspbian Jessie.
  2. Add these lines to the [Service] section of your daemon’s .service file:

    User=pi #Your limited user name goes here
    StandardInput=tty
    StandardOutput=tty
    TTYPath=/dev/tty2   # I also tried /dev/tty1 and that didn't work for me
    

    In case anyone interested, here is the complete pyscopefb.service file I used:

    [Unit]
    Description=Pyscopefb test service 
    Wants=network-online.target
    After=rsyslog.service
    After=network-online.target
    
    [Service]
    Restart=no
    ExecStart=/home/pi/Soft/Test/pygame/pyscopefb
    ExecStop=/bin/kill -INT $MAINPID
    OOMScoreAdjust=-100
    TimeoutStopSec=10s
    User=pi
    WorkingDirectory=/home/pi/Soft/Test/pygame
    StandardInput=tty
    StandardOutput=tty
    TTYPath=/dev/tty2
    
    [Install]
    WantedBy=multi-user.target
    
  3. Issue these commands in the command prompt (I assume pyscopefb.service file is already placed to the correct location where systemd can find it):

    sudo systemctl daemon-reload
    sudo systemctl start pyscopefb
    

This is working for me. Please note I didn’t test whether pygame application is able to receive keyboard and mouse events or not.

Bonus

I also had to solve another 2 problems which may be of interest as well

  1. There was blinking text cursor at the bottom of the screen with framebuffer graphics. To resolve that, I added to my application the following Python code which runs in my app before Pygame/SDL initialization:

    def _disable_text_cursor_blinking(self):
        command_to_run = ["/usr/bin/sudo", "sh", "-c", "echo 0 > /sys/class/graphics/fbcon/cursor_blink"]
        try:
            output = subprocess32.check_output(command_to_run, universal_newlines = True)
            self._log.info("_disable_text_cursor_blinking succeeded! Output was:\n{output}", output = output)
        except subprocess32.CalledProcessError:
            self._log.failure("_disable_text_cursor_blinking failed!")
            raise
    
  2. After about 10 minutes screen connected to Raspberry Pi’s HDMI output turned black (but not powered off) and my graphics didn’t display, though Pygame reported no errors. This turned out to be a power saving feature. To disable that, I added the following Python code which also runs before Pygame/SDL initialization:

    def _disable_screen_blanking(self):
        command_to_run = ["/usr/bin/setterm", "--blank", "0"]
        try:
            output = subprocess32.check_output(command_to_run, universal_newlines = True)
            self._log.info("_disable_screen_blanking succeeded! Output was:\n{output}", output = output)
        except subprocess32.CalledProcessError:
            self._log.failure("_disable_screen_blanking failed!")
            raise
    
Related Question