summaryrefslogblamecommitdiffstats
path: root/README.hacking
blob: 39f9e141e6e84e8f73f4d174c106e79535bff07f (plain) (tree)



















































                                                                          




                                                                         







































                                                                              
                                                                          






















































































                                                                            





                                                                      



                                                                        
















                                                                          

























                                                                               
                                                                          

==========================================================================

                     Writing new XScreenSaver modules

==========================================================================

Any program that can be made to render on an X window created by another
process can be used as a screen saver.  Just get the window ID out of
$XSCREENSAVER_WINDOW, draw on that, and you're done.

In theory, you can write a screen saver in any language you like.  In
practice, however, languages other than C or C++ tend not to allow you to
draw to windows that they did not create themselves.  Unfortunately, this
means that if you want to write a screen saver, you must write it in C.

Given that you're going to be writing in C, you might as well take
advantage of the various utility functions that I have written to make
that easier.  Writing a new screen saver in C using the frameworks
included with xscreensaver simplifies things enormously.

Generally, the best way to learn how to do something is to find a similar
program, and play around with it until you understand it.  Another
approach is to not worry about understanding it, but to just hack it out.
Either way, your best bet is probably to start with one of the existing
xscreensaver demos, included in the "hacks/" directory, rename the file,
and edit it until it does what you want.

The "Greynetic" and "Deluxe" hacks are probably good ones to start with,
since they are so very simple.  For OpenGL programs, "DangerBall" is a
good example.


==========================================================================
Requirements for inclusion with the XScreenSaver collection
==========================================================================

  If you come up with anything, send it to me!  If it's good, I'd love to
  include it in the xscreensaver distribution.  However, there are a few
  requirements for me to distribute it:

  - Write in portable ANSI C.  No C++.  No nonstandard libraries.

  - Write a .man page describing all command-line options.

  - Write an .xml file describing the graphical configuration dialog box.

  - Include a BSD-like copyright/license notice at the top of each source
    file (preferably, just use the one from "screenhack.h", and include
    your name and the current year).  The GNU GPL is not compatible with
    the rest of XScreenSaver.

  - No clocks! Just as time travellers always try to kill Hitler on their
    first trip, everyone seems to think that their first screen saver
    should be a clock of some kind.  Nobody needs to know what time it is
    with such frequency.  Fight The Tyranny Of The Clock.


==========================================================================
The XScreenSaver API
==========================================================================

  - Start with #include "screenhack.h"

  - Define 2 global variables:

      yoursavername_defaults -- Default values for the resources you use.
      yoursavername_options  -- The command-line options you accept.

  - Define 5 functions:

      yoursavername_init     -- Return an object holding your global state.
      yoursavername_draw     -- Draw a single frame, quickly.
      yoursavername_free     -- Free everything you've allocated.
      yoursavername_reshape  -- Called when the window is resized.
      yoursavername_event    -- Called when a keyboard or mouse event happens.

      The "event" function will only be called when running in a window
      (not as a screen saver).  The "reshape" event will be called when the
      window size changes, or (as a screen saver) when the display size
      changes as a result of a RANDR event (e.g., plugging in a new monitor).

      It's ok for both the "event" and "resize" functions to do nothing.

  - All other functions should be static.

  - The last line of the file should be 
    XSCREENSAVER_MODULE ("YourSaverName", yoursavername)

  - Finally, edit the Makefile to add a rule for your program.
    Just cut-and-paste one of the existing rules.

  Your "draw" must not run for more than a fraction of a second without
  returning.  This means "don't call usleep()".  Everything has to be a
  state machine.

  You may not store global state in global variables, or in function-local
  static variables.  All of your runtime state must be encapsulated in the
  "state" object created by your "init" function.  If you use global or
  static variables, your screen saver will not work properly on macOS.

  Do not call XSync() or XFlush().  If you think you need to do that, it
  probably means that you are you are relying on the speed of the graphics
  card for timing, which probably means that your "draw" function is
  taking too long.


==========================================================================
The xlockmore API
==========================================================================

  Some of the display modes that come with xscreensaver were ported from
  xlock, and so, for historical reasons, they follow a slightly different
  programming convention.  For newly-written Xlib programs, you'd be
  better off following the pattern used in hacks like "Deluxe" than in
  hacks like "Flag".  The xlockmore ones are the ones that begin with
  "#ifdef STANDALONE" and #include "xlockmore.h".

  But, all OpenGL screen savers have to follow the xlockmore API.

  The xlockmore API is similar to the XScreenSaver API, in that you define
  (roughly) the same set of functions, but the naming conventions are
  slightly different.  Instead of "hackname_init", it's "init_hackname",
  and it should be preceeded with the pseudo-declaration ENTRYPOINT.

  One annoying mis-feature of the xlockmore API is that it *requires* you
  to make use of global variables for two things: first, for each of your
  command line options; and second, for an array that holds your global
  state objects.  These are the only exceptions to the "never use global
  variables" rule.

  There are a few important differences between the original xlockmore API
  and XScreenSaver's implementation:

  - XScreenSaver does not use refresh_hackname or change_hackname.

  - XScreenSaver provides two additional hooks not present in xlockmore:
    reshape_hackname, and hackname_handle_event. These are respectively
    equivalent to hackname_reshape and hackname_event in the XScreenSaver
    API.

  - Under Xlib, MI_CLEARWINDOW doesn't clear the window immediately.
    Instead, due to double buffering on macOS/iOS/Android, it waits until
    after init_hackname or draw_hackname returns before clearing. Make
    sure not to issue any Xlib drawing calls immediately after a call to
    MI_CLEARWINDOW, as these will be erased. To clear the window
    immediately, just use XClearWindow.


==========================================================================
Programming Tips
==========================================================================  

  - Your screen saver should look reasonable at 20-30 frames per second.
    That is, do not assume that your "draw" function will be called more
    than 20 times a second.  Even if you return a smaller requested delay
    than that, you might not get it.  Likewise, if your "draw" function
    takes longer than 1/20th of a second to run, your screen saver may be
    consuming too much CPU.

  - Don't make assumptions about the depth of the display, or whether it
    is colormapped.  You must allocate all your colors explicitly: do not
    assume you can construct an RGB value and use that as a pixel value
    directly.  In particular, you can't make assumptions about whether
    pixels are RGB, RGBA, ARGB, ABGR, or even whether they are 32, 24,
    16 or 8 bits.  Use the utility routines provided by "utils/colors.h"
    to simplify color allocation.

  - It is better to eliminate flicker by double-buffering to a Pixmap
    than by erasing and re-drawing objects.  Do not use drawing tricks
    involving XOR.

  - If you use double-buffering, have a resource to turn it off. (MacOS,
    iOS and Android have double-buffering built in, so you'd end up
    triple-buffering.)

  - Understand the differences between Pixmaps and XImages, and keep in
    mind which operations are happening in client memory and which are in
    server memory, and which cause latency due to server round-trips.
    Sometimes using the Shared Memory extension can be helpful, but
    probably not as often as you might think.

  - On modern machines, OpenGL will always run faster than Xlib.  It's
    also more portable.  Consider writing in OpenGL whenever possible.

  - Free any memory you allocate. While screen savers under X11 have
    their memory freed automatically when their process is killed by
    the XScreenSaver daemon, under iOS and Android screen savers exist
    in long-lived processes where no such cleanup takes place.
    Consider Valgrind or gcc -fsanitize=leak to find memory leaks.

  - Again, don't use global variables.  If you are doing your developent
    under X11, test your saver from the command line with the "-pair"
    argument.  If that crashes, you're using global variables!


==========================================================================
macOS, iOS and Android
==========================================================================

  Though XScreenSaver started its life as an X11 program, it also now runs
  on macOS, iOS and Android, due to a maniacal collection of compatibility
  shims.  If you do your development on an X11 system, and follow the
  usual XScreenSaver APIs, you shouldn't need to do anything special for
  it to also work on macOS and on phones.

  See the READMEs in the OSX/ and android/ directories for instructions on
  compiling for those platforms.

  To check that an X11 saver will fit well on a mobile device, test it
  with -geometry 640x1136 and 640x960.  That's a good first step, anyway.


==========================================================================
Theory behind the ifdefs
==========================================================================  

  HAVE_ macros indicate that an API is available.
  USE_ macros indicate that a feature is requested.

  Some notable ones:

  HAVE_GL	The OpenGL 1.3 API is available, natively or through emulation.
  HAVE_GLES	The OpenGLES 1.x API is available.
  HAVE_COCOA	The Cocoa API is available, meaning compiling for macOS or iOS.
  HAVE_IPHONE	Compiling for iOS, including iPad.
  HAVE_ANDROID	Compiling for Android.
  HAVE_MOBILE	iOS or Android, typically used for things related to rotation
		or screen size that apply to all phones and tablets.
  HAVE_JWXYZ	Compiling on a system where the X11 API is emulated
		(macOS, iOS or Android).
  HAVE_JWZGLES	Compiling on a system where the OpenGL 1.3 API is emulated
		(iOS, Android, possibly Linux).
  HAVE_GLSL	Compiling against a library that supports the GL Shading
		Language.  Note that using GLSL also requires a runtime check
		to see which version of GLSL the *running* system supports.
  HAVE_EGL	OpenGL interfaces with native windows via EGL instead of GLX.

==========================================================================