LinuxQuestions.org
Latest LQ Deal: Latest LQ Deals
Home Forums Tutorials Articles Register
Go Back   LinuxQuestions.org > Forums > Linux Forums > Linux - Software
User Name
Password
Linux - Software This forum is for Software issues.
Having a problem installing a new program? Want to know which application is best for the job? Post your question in this forum.

Notices


Reply
  Search this Thread
Old 01-05-2012, 05:04 PM   #1
systemlordanubis
Member
 
Registered: Jun 2010
Distribution: Debian, Ubuntu, Win
Posts: 143

Rep: Reputation: 16
Custom console and/or splash screen


Hi All,

I'd like to know if there's a way I can create a custom console/splash screen for a server I'm developing. I'm talking non-GUI, command line style here.

Normally when Debian boots, the end result is a console prompt from which you then work.

What I'd like to do is when my service is booting, I'd like to first display a logo (ascii or image) to the screen then provide my own custom display output without giving the console prompt to the screen.

I'm sure there's a way this can be done but don't know where to start looking.

Thanks
Anubis
 
Old 01-05-2012, 07:00 PM   #2
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
Quote:
Originally Posted by systemlordanubis View Post
I'd like to know if there's a way I can create a custom console/splash screen for a server I'm developing. I'm talking non-GUI, command line style here.
There are two separate things here:
  1. Splash, or more specifically, boot splash.
    This is a graphic or text displayed while the kernel boots.
    This is created by the bootloader (grub, for example). If the kernel is told about the boot splash, it will keep showing it until init (process 1) is started. Obviously, init can also be made aware of the boot splash, too; the boot splash should be pretty much seamless then.
  2. System console.
    If you look at the processes running on your server, you can see one or more that end with getty, and are owned by root. These are the processes that provide the system console. These processes are also started by init ; the kernel does not care whether you have them or not.
    You can replace one (the one that connects to tty1), or all, with your custom process or getty simply by editing init configuration. It is in /etc/inittab (traditional SysV init) or /etc/init/ (upstart), or perhaps elsewhere, depending which variety of init your system uses.

If you have an interactive application already written, all you need to do is to remove the getty for tty1 (because that one is active during boot), and start your application during bootup. Just remember to redirect standard input from /dev/tty (if you need keyboard input), and standard output and standard error to /dev/tty1 for your program.

You can use ANSI escape sequences for multicolor output as early in the boot sequence as you wish. If you depend on libraries or services, you must make sure they're available before your application starts.

If you use a framebuffer console, you can draw (graphics) directly to to console without X11 at all. The kernel will still happily draw characters on top of it, of course.

The difference between processes started by init scripts, and by init itself (via /etc/inittab or /etc/init/), is that if asked to, init will restart the latter if/when they exit. Login prompts, gettys, rely very heavily on this mechanism. They authenticate the user, and if successful, replace themselves with the user shell. When the shell exits, init notices (it gets a SIGCHLD signal), and will restart the getty.

I've written both interactive and noninteractive processes (for benchmarking hardware as easily as possible, from an USB stick) to be used on tty1 instead of a getty, using Bash only; it really is that simple. (It asked for an identifier to save with the results, and confirmation on which storage devices could be overwritten with garbage for benchmarking. After that, it was fully autonomous, shutting down the computer when the benchmarking was complete.)

If you want your console to work like a getty, allowing users to log in, you might wish to look at projects like mingetty and fbgetty.

Hope this helps,
 
Old 01-05-2012, 08:40 PM   #3
systemlordanubis
Member
 
Registered: Jun 2010
Distribution: Debian, Ubuntu, Win
Posts: 143

Original Poster
Rep: Reputation: 16
Hi Nominal Animal,

Thanks very much for your detailed reply.


Going on with your points, in Debian 6, what would be the preferred method of making a boot splash for both boot and init; a quick search reveals that this would require a kernel re-compilation; if so, what is the best package to achieve this?


On the second point, I'm going to have a poke around in getty and the others that you mentioned. Ideally, I'd like to have some sort of a visual header (again ASCII or image) then some real-time status information followed by some pre-defined menu options.

Most of the actual server configuration comes via it's web administration so the console really just needs the ability to have menu options like 'shutdown' and 'reboot' etc.


You wouldn't happen to be willing to share your interactive version or a stripped-down version of your console?


Thanks again for all your help and advice.
Anubis.
 
Old 01-06-2012, 05:54 AM   #4
Nominal Animal
Senior Member
 
Registered: Dec 2010
Location: Finland
Distribution: Xubuntu, CentOS, LFS
Posts: 1,723
Blog Entries: 3

Rep: Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948Reputation: 948
Quote:
Originally Posted by systemlordanubis View Post
Going on with your points, in Debian 6, what would be the preferred method of making a boot splash for both boot and init; a quick search reveals that this would require a kernel re-compilation; if so, what is the best package to achieve this?
I'd look at plymouth (Debian Wiki) first. I think you can create a text-only themes for it, but I'm not sure; I haven't played with it.

Alternatively, you could use a quiet boot, i.e. no splash during kernel boot at all.

Quote:
Originally Posted by systemlordanubis View Post
Ideally, I'd like to have some sort of a visual header (again ASCII or image) then some real-time status information followed by some pre-defined menu options.
For text one, a dash script should be sufficient.

For framebuffer console, you might be able to use fim, although you could control it fully through a C program. If you convert your images to 24-bit PPM (PNM) format, it is very easy to "blit" images to the framebuffer. One of the problems you may encounter is that if your program is dependent on libraries, you cannot start it too early in the boot sequence. If you compile your program statically, then there is no problem, and you can start it as early as you wish.

Not all systems support a framebuffer console (I'm specifically thinking about some server hardware), so if you want maximum compatibility, use text mode only.

Quote:
Originally Posted by systemlordanubis View Post
You wouldn't happen to be willing to share your interactive version or a stripped-down version of your console?
Perhaps we could develop something here together instead?

First, I use this trivial program I call waitesc.c:
Code:
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/time.h>
#include <signal.h>
#include <errno.h>

/* Alarm signal handler (does nothing, just interrupts)
*/
static void alarm_handler(int signum)
{
    return;
}

/* Cancel interrupts.
*/
static int cancel_interrupt(void)
{
    struct itimerval    timer;

    timer.it_interval.tv_sec  = 0L;
    timer.it_interval.tv_usec = 0L;
    timer.it_value.tv_sec  = 0L;
    timer.it_value.tv_usec = 0L;

    if (setitimer(ITIMER_REAL, &timer, NULL) == -1)
        return errno;

    return 0;
}

/* Set interrupting timeout.
 * Returns 0 if success, errno otherwise.
*/
static int set_interrupt(const struct timeval timeout)
{
    struct sigaction    act;
    struct itimerval    timer;
    int                 saved_errno;

    /* Save errno so we keep it unchanged if successful. */
    saved_errno = errno;

    /* Set alarm signal handlers. */
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    act.sa_handler = alarm_handler;
    if (sigaction(SIGALRM, &act, NULL) == -1)
        return errno;

    /* This timer repeats a hundred times a second. */
    timer.it_interval.tv_sec  = 0L;
    timer.it_interval.tv_usec = 10000L;

    /* Set expiry time. */
    timer.it_value.tv_sec  = timeout.tv_sec;
    timer.it_value.tv_usec = timeout.tv_usec;

    /* Arm timer. */
    if (setitimer(ITIMER_REAL, &timer, NULL) == -1)
        return errno;

    /* Done. */
    errno = saved_errno;
    return 0;
}

/* Helper function: Write to standard error.
*/
static int wrerr(const void *const message)
{
    const char *head = (const char *)message;
    const char *tail = (const char *)message;
    ssize_t     written;
    int         saved_errno;

    /* If no message, return immediately. */
    if (!head || !*head)
        return 0;

    /* Save errno. */
    saved_errno = errno;

    /* Find end of string. */
    while (*tail)
        tail++;

    /* Write loop. */
    while (head < tail) {

        written = write(STDERR_FILENO, head, (size_t)(tail - head));
        if (written > (ssize_t)0)
            head += written;
        else
        if (written != (ssize_t)-1)
            return errno = EIO;
        else
        if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
            return errno;
    }

    /* Done. */
    errno = saved_errno;
    return 0;
}

static int parse_seconds(const char *s, struct timeval *t)
{
    long    sec, old, usec;
    int     negative = 0;

    if (!s)
        return ENOENT;

    /* Skip leading whitespace and signs. */
    while (*s == '\t' || *s == '\n' || *s == '\v' ||
           *s == '\f' || *s == '\r' || *s == ' ' ||
           *s == '+' || *s == '-')
        if (*(s++) == '-')
            negative = !negative;

    /* Negative is unacceptable. */
    if (negative)
        return EDOM;

    /* Full seconds? */
    if (*s >= '0' && *s <= '9') {
        sec = *(s++) - '0';

        while (*s >= '0' && *s <= '9') {
            old = sec;
            sec = 10L * sec + (*(s++) - '0');
            if (sec < old)
                return EDOM;
        }

    } else
    if (*s == '.' || *s == ',') {
        sec = 0L;

    } else
        return ENOENT;

    /* Fractional part? */
    if (*s == '.' || *s == ',') {
        s++;
        usec = 0L;

        if (*s >= '0' && *s <= '9') usec += 100000L * (*(s++) - '0');
        if (*s >= '0' && *s <= '9') usec +=  10000L * (*(s++) - '0');
        if (*s >= '0' && *s <= '9') usec +=   1000L * (*(s++) - '0');
        if (*s >= '0' && *s <= '9') usec +=    100L * (*(s++) - '0');
        if (*s >= '0' && *s <= '9') usec +=     10L * (*(s++) - '0');
        if (*s >= '0' && *s <= '9') usec +=      1L * (*(s++) - '0');

        while (*s >= '0' && *s <= '9')
            s++;
    } else
        usec = 0L;

    /* Skip trailing whitespace. */
    while (*s == '\t' || *s == '\n' || *s == '\v' ||
           *s == '\f' || *s == '\r' || *s == ' ')
        s++;

    /* Trailing garbage? */
    if (*s)
        return EINVAL;

    /* Save time. */
    if (t) {
        t->tv_sec = sec;
        t->tv_usec = usec;
    }
    return 0;
}


int main(int argc, char *argv[])
{
    struct timeval  timeout;
    char            buffer[65536];
    ssize_t         bytes;

    if (argc != 2) {
        wrerr("\n"
              "Usage: "); wrerr(argv[0]); wrerr(" seconds\n"
              "\n"
              "This program will read from standard input.\n"
              "When input is available, it is returned immediately,\n"
              "otherwise this waits for up to the specified time.\n"
              "Fractional seconds like 0.001 work just fine.\n"
              "\n"
              "ASCII control characters are escaped using backslash\n"
              "followed by three octal digits. Backslash itself is\n"
              "escaped as \\134.\n"
              "\n");
        return (argc == 1) ? 0 : 1;
    }

    if (parse_seconds(argv[1], &timeout)) {
        wrerr(argv[1]);
        wrerr(": Invalid timeout (in seconds).\n");
        return 1;
    }

    /* Clamp minimum timeout to ten milliseconds. */
    if ((timeout.tv_sec < 0L) ||
        (timeout.tv_sec == 0L && timeout.tv_usec < 10000L)) {
        timeout.tv_sec  = 0L;
        timeout.tv_usec = 10000L;
    }

    if (set_interrupt(timeout)) {
        wrerr(argv[1]);
        wrerr(": Cannot set timeout.\n");
        return 1;
    }

    /* Read. */
    bytes = read(STDIN_FILENO, buffer, sizeof(buffer)/4);
    if ((bytes == (ssize_t)-1) &&
        (errno == EINTR || errno == EWOULDBLOCK || errno == EAGAIN))
        bytes = 0;

    /* Cancel timeout. */
    cancel_interrupt();

    /* Anything to output? */
    if (bytes > (ssize_t)0) {
        unsigned char *in_head = (unsigned char *)buffer;
        unsigned char *in_tail = (unsigned char *)buffer + bytes;
        unsigned char *out_head = (unsigned char *)buffer + sizeof(buffer);
        unsigned char *out_tail = (unsigned char *)buffer + sizeof(buffer);

        while (in_tail-->in_head)
            if (*in_tail < 32 || *in_tail == 92 || *in_tail == 127) {
                *(--out_head) = '0' + ( *in_tail        & 7U);
                *(--out_head) = '0' + ((*in_tail >> 3U) & 7U);
                *(--out_head) = '0' + ((*in_tail >> 6U) & 3U);
                *(--out_head) = '\\';
            } else
                *(--out_head) = *in_tail;

        while (out_head < out_tail) {

            bytes = write(STDOUT_FILENO, out_head, (size_t)(out_tail - out_head));
            if (bytes > (ssize_t)0)
                out_head += bytes;
            else
            if (bytes != (ssize_t)-1)
                return 2;
            else
            if (errno != EINTR && errno != EAGAIN && errno != EWOULDBLOCK)
                return 2;

        }
    }

    return 0;
}
Compile and install it as /sbin/waitesc using
Code:
gcc waitesc.c -Wall -O3 -static -s -o waitesc
sudo install -m 0755 waitesc /sbin/waitesc
waitesc is a replacement for both reading input and sleeping between display updates. It takes one argument, the maximum sleep time in seconds (minimum is 0.01 or one hundredth of a second). If there is input, it outputs the input immediately, otherwise it waits for input, up to the specified wall clock time limit. It escapes all ASCII control characters using octal notation (\nnn) and backslash as \134.

(It works by setting an alarm, initially at the desired time, and every 0.01s afterwards. The alarm handler does nothing. The signal delivery will interrupt the read waiting for input. After the read is interrupted, the alarm is canceled. This makes sure the read will be interrupted, without using a pipe and select() to work around a race condition between the timeout and the read. Only a fourth of the output buffer is used for input, because any input byte may expand to four bytes in output. I scan the input buffer for bytes to escape in reverse order, filling the output buffer starting from the end of the buffer; this way the input and output contents will never overlap, and I only need one buffer.)

If you use stty -icanon -echoto set the tty to raw mode, you can use waitesc to read individual keypresses. To return the terminal to normal state, use stty icanon echo .

Here is a table of special keys and what waitesc outputs:
Code:
Backspace:    \010 or \177
Tab:          \011
Enter:        \012
ESC:          \033
Insert:       \033[2~
Delete:       \033[3~
PageUp:       \033[5~
PageDown:     \033[6~
Home:         \033OH
End:          \033OF
Up:           \033[A
Down:         \033[B
Left:         \033[D
Right:        \033[C
Note that the right side is exact, and will need to be escaped or quoted when written in a shell script. Non-ASCII characters and ASCII printable characters will be shown as-is in the output.

Let's start with this skeleton dash script. It does nothing but update the time every half second or so, showing the update counter, and let the user move an imaginary (not drawn) cursor around using the arrow keys. Only the coordinates are calculated for the imaginary cursor. It uses /sbin/waitesc to get user input and the half second wait between updates, and /bin/stty to control the terminal; otherwise only dash built-ins are used.

Code:
#!/bin/dash

# System console (tty1) LQ example script
# License: CC0 (Public Domain), see
#   http://creativecommons.org/publicdomain/zero/1.0/
# You run this script on your own risk!

# Before anything else, ignore INT, QUIT, TSTP, and PIPE signals.
trap "" INT QUIT TSTP PIPE

# If terminated, reset the terminal to sane state.
trap "/bin/stty icanon echo ; printf '\033[?25h\033[0m\033[2J\033[1;1H' ; exit 0" TERM

# Make sure we are connected to the tty1,
# but redirect errors to /dev/null.
exec </dev/tty1 >/dev/tty1 2>/dev/null
# Comment out the above line, if you run in a virtual terminal (e.g. xterm).

# Set terminal to raw mode, so we get individual keypresses.
/bin/stty -icanon -echo

# ANSI: Disable line wrapping.
printf '\033[7l'

# ANSI: Clear all tab stops.
printf '\033[3g'

# ANSI: Hide cursor.
printf '\033[?25l'

# ANSI: Clear screen, and move cursor to upper left corner.
printf '\033[2J\033[1;1H'

# Find out the size of the screen.
COLS=$(stty size | (read rows cols ignore && printf '%d' $cols))
ROWS=$(stty size | (read rows cols ignore && printf '%d' $rows))

# Set safe defaults for screen size, just in case.
[ -n "$COLS" ] || COLS=80
[ -n "$ROWS" ] || ROWS=25

# No field separator.
unset IFS

# Flush input buffer.
/sbin/waitesc 0.01 >/dev/null

# ANSI sequence to clear the rest of the line, and move to next line.
# Use this instead of clearing the screen, to avoid flicker when updating.
END="\033[0m\033[K\n"

# Just some variables modified in the main loop.
counter=0
x=1
y=1

# Main loop.
input=""
while [ 1 ]; do

    # Parse input.
    while [ -n "$input" ]; do
        case "$input" in
        \\010*|\\177*) # Backspace
            input="${input#????}"
            # Just a placeholder, does nothing.
            ;;
        \\033\[\[3\~*) # Delete
            input="${input#????????}"
            # Just a placeholder, does nothing.
            ;;
        \\012*|\\015*) # Enter
            input="${input#????}"
            # Just a placeholder, does nothing.
            ;;
        \\011*) # Tab
            input="${input#????}"
            # Just a placeholder, does nothing.
            ;;
        \\033\[A*) # Up
            input="${input#??????}"
            y=$(( y - 1 ))
            [ $y -lt 1 ] && y=1
            ;;
        \\033\[B*) # Down
            input="${input#??????}"
            y=$(( y + 1 ))
            [ $y -gt $ROWS ] && y=$ROWS
            ;;
        \\033\[D*) # Left
            input="${input#??????}"
            x=$(( x - 1 ))
            [ $x -lt 1 ] && x=1
            ;;
        \\033\[C*) # Right
            input="${input#??????}"
            x=$(( x + 1 ))
            [ $x -gt $COLS ] && x=$COLS
            ;;
        \ *) # Space
            input="${input#?}"
            # Just a placeholder, does nothing.
            ;;
        *) # Ignore all others.
            input="${input#?}"
            ;;
        esac
    done

    # Update counter.
    counter=$(( counter + 1 ))

    # Start updating by moving to upper left corner.
    # Does NOT clear the screen.
    printf '\033[1;1H'

    # Output date (in brown).
    printf '\033[0;33m%s'$END "$(date --rfc-3339=seconds)"

    # Output an empty line.
    printf $END

    # Output counter using light and dark red:
    printf '\033[1;31mCounter\033[0;31m = \033[1;31m%d'$END $counter

    # Output X and Y coordinates using light and dark green:
    printf '\033[1;32mX\033[0;32m = \033[1;32m%d\033[0;32m, \033[1;32mY\033[0;32m = \033[1;32m%d'$END $x $y

    # Output an empty line.
    printf $END

    # Get new input, but don't spend more than half a second.
    input="$input$(/sbin/waitesc .5)"
done
Note that this script cannot be killed from the keyboard. It does exit nicely (restoring the terminal to normal state) if you send a TERM signal to it, however. And you can use Alt+Fn, Alt+Left, and Alt+Right to switch to a getty for a login. (This type of TERM signal handling is important, as otherwise you'll have trouble shutting down the machine.)

If you are targeting a minimal, embedded system, then all you might need is an initrd. You'd need to copy both your script and the utility commands it uses to the ramdisk. (There is really no need to have a "real" root filesystem; the initrd works fine for that. The space is quite limited, though.)

For a Debian server, you should just remove the getty for tty1, and replace it with this script. Edit either /etc/inittab or /etc/init/tty1.conf (whichever you have); replace /sbin/getty -8 38400 tty1 with /path/to/the/above/dash/script .

I recommend using a virtual machine to test and develop this. VirtualBox is quite suitable. If you make a checkpoint immediately after installing Debian on it, developing the modifications to the init system is very easy, because you can always restore the machine state to any checkpoint you've made. Also, the virtual machine boots up much faster, so you'll save a lot of time, too.

Edit to add: For the ANSI escape codes used to change text and background colors, moving the cursor around, and so on, see ANSI escape code Wikipedia page, and ANSI escape sequences at ascii-table.com.

Last edited by Nominal Animal; 01-06-2012 at 05:57 AM.
 
Old 01-08-2012, 05:45 PM   #5
systemlordanubis
Member
 
Registered: Jun 2010
Distribution: Debian, Ubuntu, Win
Posts: 143

Original Poster
Rep: Reputation: 16
Hi Nominal Animal,

Thanks for the very detailed reply. Give me a little bit to go through it and I'll get back to you.

Thanks again.

Cheers.
Steve.
 
Old 01-23-2012, 11:48 PM   #6
systemlordanubis
Member
 
Registered: Jun 2010
Distribution: Debian, Ubuntu, Win
Posts: 143

Original Poster
Rep: Reputation: 16
Hi NominalAnimal,

Thanks again for your help.

I've managed to get Plymouth working like a charm using the references you provided; this looks stunning. One thing I couldn't find was if there was some way to disable the 'esc' option while plymouth is booting or if there was a way I can output text status information on top of the booting image.


I've also tinkered with FIM and got a PNG to load in the framebuffer console, I've also tested the dash script that you provided to replace getty. The dash script looks reasonably easy to edit to get it to do what I want (by displaying relative information) but what I can't work out is how I could get it to have a FIM loaded image at the top say 2/3 of the screen with the bottom 1/3 of the screen reserved for the 'relevant information'.


I'm making notes on the scripts and information used and issues I've encountered to get these various parts working which I'll post at the end for others to follow as a guide to full customization of these screens.


Thanks
Anubis.
 
  


Reply



Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is Off
HTML code is Off



Similar Threads
Thread Thread Starter Forum Replies Last Post
Custom bootup splash screen galapogos Linux - Software 1 02-27-2009 12:24 AM
Creating a custom boot splash screen Nemus Linux - General 7 06-01-2007 08:28 AM
Custom Splash Animated Boot Screen sadarax Linux - General 2 03-09-2006 10:47 PM
custom linux splash screen Darkfalz Linux - Software 1 12-17-2004 03:38 PM
Framebuffer Console & Splash Screen jspenguin Linux - Software 9 05-03-2003 11:34 PM

LinuxQuestions.org > Forums > Linux Forums > Linux - Software

All times are GMT -5. The time now is 03:03 PM.

Main Menu
Advertisement
My LQ
Write for LQ
LinuxQuestions.org is looking for people interested in writing Editorials, Articles, Reviews, and more. If you'd like to contribute content, let us know.
Main Menu
Syndicate
RSS1  Latest Threads
RSS1  LQ News
Twitter: @linuxquestions
Open Source Consulting | Domain Registration