Creating a Screensaver Using SFML - Part 1

 

There are many reasons to create a screensaver using SFML. Perhaps you would like to distribute one with your game or as a freebie marketing whatever you are developing. Whatever the purpose, if you have used SFML before you know that it is a great graphics library and it is more than capable of the task at hand. Of course, if you want to get straight to the meat and potatoes, check out a copy of the source on github.

The purpose of Part 1 of this tutorial is to get a working example of a Windows screensaver up and running with the bare minimum code, using the Windows API and SFML. It will consist of simply creating a window with the Windows API and passing the handle to SFML for rendering. No frills, no effects - just the basics. Although this is the very thing that SFML abstracts (thankfully - and for sanity's sake), it will be important to use this technique for a more advanced screensaver utilizing more available features. If I find an easier way to create a screensaver, I will update this post, or perhaps create a new one.

Basic knowledge of C++ and SFML are assumed, no arkane knowledge of the Windows API is necessary at this point. At the time of this writing, I am using Code::Blocks IDE with GCC compiler (version 4.7.2).

If you are using the Code::Blocks IDE, creating a working skeleton for a Windows GUI app is pain-free. Just create a new "Win32 GUI" project, compile, and grab a beer. If it compiles successfully, you should see a plain grey window. For our purposes we will create a new "Win32 GUI" project in Code::Blocks, and gut it down to the essentials.

The first things that are generated are some defines followed by header includes. We will add some additional headers so that it looks like the following:


#if defined(UNICODE) && !defined(_UNICODE)
#define _UNICODE
#elif defined(_UNICODE) && !defined(UNICODE)
#define UNICODE
#endif

#include <tchar.h>
#include <iostream>
#include <stdlib.h>
#include <windows.h>
#include <scrnsave.h>
#include <SFML/Graphics.hpp>

The next thing we encounter is a function prototype for our Windows procedure and a c-style string which corresponds to the class name of our window, which will be used in the main procedure. Because we are using ScreenSaverProc and it is already prototyped in <scrnsave.h>, we do not need a prototype here, and certainly not for WindowProcedure (which is what Code::Blocks gives you by default). Get rid of it so that the remaining code in this section looks like this:


/*  Make the class name into a global variable  */
TCHAR szClassName[ ] = _T("SFML and Win32 API"); 

The next thing you will see is a function called WinMain. At first glance this function may be confusing, but it is just the application's main entry point. In this block of code, you will instantiate a handle for your window, a message object to keep track of messages your window receives, and most importantly create a window class. You also need to know that "lpfnWndProc" is a member of your windows class that must point to the ScreenSaverProc. "lpfn" stands for long pointer, and your ScreenSaverProc function is of type LRESULT CALLBACK, which if you scour the interwebs you will find that it is an alias which means "long _stdcall". See this discussion and this wikipedia article for all the details you can handle.

The code that is generated by Code::Blocks is nice and all, but we don't need most of it. All we need is the code that creates the window handle and the window class. We will let SFML do most of the hard work handling events and rendering. After revamping the WinMain function, it should look something like this:


int WINAPI WinMain (HINSTANCE hThisInstance, HINSTANCE hPrevInstance, LPSTR lpszArgument, int nCmdShow)
{
    //check the available video modes
    //documentation can be found here:
    //http://www.sfml-dev.org/documentation/2.1/classsf_1_1VideoMode.php
    std::vector<sf::VideoMode> modes = sf::VideoMode::getFullscreenModes();
    for (std::size_t i = 0; i < modes.size(); ++i)
    {
        sf::VideoMode mode = modes[i];
        std::cout<<"Mode #"<<i<<": "
        <<mode.width<<"x"<<mode.height<<" - "
        <<mode.bitsPerPixel<<"bpp"<<std::endl;
    }

    HWND hwnd;               /* This is the handle for our window */
    MSG Message;            /* Here messages to the application are saved */
    WNDCLASSEX wincl;        /* Data structure for the windowclass */

    /* The Window structure */
    wincl.hInstance = hThisInstance;
    wincl.lpszClassName = szClassName;
    wincl.lpfnWndProc = ScreenSaverProc;      /* This function is called by windows */
    wincl.style = CS_DBLCLKS;                 /* Catch double-clicks */
    wincl.cbSize = sizeof (WNDCLASSEX);

    /* Use default icon and mouse-pointer */
    wincl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
    wincl.hCursor = LoadCursor (NULL, IDC_ARROW);
    wincl.lpszMenuName = NULL;                 /* No menu */
    wincl.cbClsExtra = 0;                      /* No extra bytes after the window class */
    wincl.cbWndExtra = 0;                      /* structure or the window instance */
    /* Use Windows's default colour as the background of the window */
    wincl.hbrBackground = (HBRUSH) COLOR_BACKGROUND;

    /* Register the window class, and if it fails quit the program */
    if (!RegisterClassEx (&wincl))
    {
        std::cout<<"Class failed to register."<<std::endl;
        return 0;
    }

    /* The class is registered, let's create the program*/
    hwnd = CreateWindowEx (
               0,                               /* Extended possibilites for variation */
               szClassName,                     /* Classname */
               _T("SFML and Win32 API"),        /* Title Text */
               WS_OVERLAPPEDWINDOW,             /* default window */
               CW_USEDEFAULT,                   /* Windows decides the position */
               CW_USEDEFAULT,                   /* where the window ends up on the screen */
               800,                             /* The programs width */
               600,                             /* and height in pixels */
               HWND_DESKTOP,                    /* The window is a child-window to desktop */
               NULL,                            /* No menu */
               hThisInstance,                   /* Program Instance handler */
               NULL                             /* No Window Creation data */
           );

    //pass the window handle to SFML
    sf::RenderWindow window(hwnd);

    //use sfml to create the screen in fullscreen mode
    //Create a window with the same width, height, and pixel depth as the desktop
    sf::VideoMode desktop = sf::VideoMode::getDesktopMode();
    window.create(sf::VideoMode(desktop.width,desktop.height,desktop.bitsPerPixel), "SFML and Win32 API", sf::Style::Fullscreen);
    //create variables for tracking mouse movements
    sf::Mouse::setPosition(sf::Vector2i(400,300), window);
    sf::Vector2i initpos = sf::Mouse::getPosition(window);
    sf::Vector2i movement;
    //fudge factor is to account for mouse sensitivity
    //this eliminates problems with the screensaver
    //exiting at the slightest nudge of the mouse
    sf::Vector2i fudgefactor = sf::Vector2i(4,4);

    //set the cursor inside the window to track movements
    window.setActive(true);

    //////////////////////////////
    //***************************
    //create some SFML drawables
    //***************************
    //////////////////////////////

    //first grab the location of the user's Windows path
    //where the fonts are located - hopefully they have arial.ttf
    //...it's a pretty standard font
    std::string str = getenv("WinDir");
    str += "\\fonts\\arial.ttf";
    std::cout<<str<<std::endl;

    //load the font
    sf::Font font;
    if(!font.loadFromFile(str))
    {
        std::cerr<<"Could not load the font."<<std::endl;
    }

    //create the screensaver message
    sf::Text words;
    words.setFont(font);
    words.setString("Hello Screensaver!");
    words.setOrigin(words.getGlobalBounds().width/2.f, words.getGlobalBounds().height/2.f);
    words.setPosition(window.getSize().x/2.f, window.getSize().y/2.f);

    //handle events in SFML - it's much cleaner
    sf::Event e;
    bool running = true;
    while(running)
    {
        while(window.pollEvent(e))
        {
            if(e.type == sf::Event::Closed)
            {
                //destroy window and unregister the class
                DestroyWindow(hwnd);
                UnregisterClass("SFML and Win32 API", hThisInstance);
                return 0;
            }

            if(e.type == sf::Event::KeyPressed)
            {
                //destroy window and unregister the class
                DestroyWindow(hwnd);
                UnregisterClass("SFML and Win32 API", hThisInstance);
                return 0;
            }

            if(e.type == sf::Event::MouseMoved)
            {
                movement = sf::Mouse::getPosition(window);
                //make sure to use the absolute value of the difference
                //between the initial mouse position and the movement
                //and compare it to the fudge factor
                //if this value is greater exit the screensaver
                if (abs(movement.x - initpos.x) > fudgefactor.x
                    || abs(movement.y - initpos.y) > fudgefactor.y)
                {
                    //destroy window and unregister the class
                    DestroyWindow(hwnd);
                    UnregisterClass("SFML and Win32 API", hThisInstance);
                    return 0;
                }
            }
        }

        //clear the window in SFML
        window.clear();

        //draw stuff
        window.draw(words);

        //display stuff
        window.display();
    }
    //just in case the app makes it to this point
    //destroy window and unregister the class
    DestroyWindow(hwnd);
    UnregisterClass("SFML and Win32 API", hThisInstance);

    return 0;
}

It now looks a lot more like an SFML app than the original "Win32 GUI" boilerplate from earlier. As you can see from above, we've successfully passed the window handle to SFML via the special constructor for sf::RenderWindow. This allows us to return to writing much easier to understand rendering and event handling code that we are used to with SFML, avoiding the headaches and swearing associated with using the Windows API directly. But first, a word about the first bit of code in the WinMain function:


std::vector modes = sf::VideoMode::getFullscreenModes();

This piece of code returns a std::vector packed to the gills with information about your user's available video modes. This is useful if you want to target machines at varying resolutions. The vector contains the data sorted in best to worst order - in other words for this code the best video mode available to your user will be on the index modes[0], and they only get worse from there. It is up to you how you will handle different resolutions, but this is a simple way to retrieve the information you need. Apart from this, everything else should be commented enough to understand what is going on - pretty easy stuff.

The next function you will encounter is the WindowsProcedure function. However, we need to define the ScreenSaverProc not WindowsProcedure, which as I discussed earlier, is prototyped in <scrnsave.h>. Because the parameters to the function are exactly the same, we can just change "WindowsProcedure" to "ScreenSaverProc". Also, we don't need most of the code in ScreenSaverProc, so we can cut it down to the following:


/*  This function is called by the Windows function DispatchMessage()  */
LRESULT CALLBACK ScreenSaverProc (HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
{
    //replaced messages with SFML events in main loop
    //but it is still necessary to return DefWindowProc
    return DefWindowProc (hwnd, message, wParam, lParam);
}

Next, there are two functions that need to be defined that were also prototyped in <scrnsave.h>. They are the ScreenSaverConfigureDialog and RegisterDialogClasses functions. Although we are not going to use them in this example, we will need them later. Here is the code:


BOOL WINAPI ScreenSaverConfigureDialog(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
    return false;
}

BOOL WINAPI RegisterDialogClasses(HANDLE hInst)
{
    return true;
}

Not bad - a functioning screensaver in under 200 lines. If you compile the program right now, you should just get a plain black fullscreen window that reads "Hello Screensaver!". Exciting, I know, but it will improve over time. All you need to do is compile the project in release mode and change the extension of your executable to .scr. Throw this bad boy in your "Windows/System32" folder and there you have it - a working screensaver. Just don't forget to activate it in the control panel or you'll never see it in action.

In the next part of this series, I will show you how to read and write configuration data from your user to customize and improve the usability of your screensaver.