| 1 | |
| 2 | ========================================================================== |
| 3 | |
| 4 | Writing new XScreenSaver modules |
| 5 | |
| 6 | ========================================================================== |
| 7 | |
| 8 | Any program that can be made to render on an X window created by another |
| 9 | process can be used as a screen saver. Just get the window ID out of |
| 10 | $XSCREENSAVER_WINDOW, draw on that, and you're done. |
| 11 | |
| 12 | In theory, you can write a screen saver in any language you like. In |
| 13 | practice, however, languages other than C or C++ tend not to allow you to |
| 14 | draw to windows that they did not create themselves. Unfortunately, this |
| 15 | means that if you want to write a screen saver, you must write it in C. |
| 16 | |
| 17 | Given that you're going to be writing in C, you might as well take |
| 18 | advantage of the various utility functions that I have written to make |
| 19 | that easier. Writing a new screen saver in C using the frameworks |
| 20 | included with xscreensaver simplifies things enormously. |
| 21 | |
| 22 | Generally, the best way to learn how to do something is to find a similar |
| 23 | program, and play around with it until you understand it. Another |
| 24 | approach is to not worry about understanding it, but to just hack it out. |
| 25 | Either way, your best bet is probably to start with one of the existing |
| 26 | xscreensaver demos, included in the "hacks/" directory, rename the file, |
| 27 | and edit it until it does what you want. |
| 28 | |
| 29 | The "Greynetic" and "Deluxe" hacks are probably good ones to start with, |
| 30 | since they are so very simple. For OpenGL programs, "DangerBall" is a |
| 31 | good example. |
| 32 | |
| 33 | |
| 34 | ========================================================================== |
| 35 | Requirements for inclusion with the XScreenSaver collection |
| 36 | ========================================================================== |
| 37 | |
| 38 | If you come up with anything, send it to me! If it's good, I'd love to |
| 39 | include it in the xscreensaver distribution. However, there are a few |
| 40 | requirements for me to distribute it: |
| 41 | |
| 42 | - Write in portable ANSI C. No C++. No nonstandard libraries. |
| 43 | |
| 44 | - Write a .man page describing all command-line options. |
| 45 | |
| 46 | - Write an .xml file describing the graphical configuration dialog box. |
| 47 | |
| 48 | - Include a BSD-like copyright/license notice at the top of each source |
| 49 | file (preferably, just use the one from "screenhack.h", and include |
| 50 | your name and the current year). The GNU GPL is not compatible with |
| 51 | the rest of XScreenSaver. |
| 52 | |
| 53 | - No clocks! Just as time travellers always try to kill Hitler on their |
| 54 | first trip, everyone seems to think that their first screen saver |
| 55 | should be a clock of some kind. Nobody needs to know what time it is |
| 56 | with such frequency. Fight The Tyranny Of The Clock. |
| 57 | |
| 58 | |
| 59 | ========================================================================== |
| 60 | The XScreenSaver API |
| 61 | ========================================================================== |
| 62 | |
| 63 | - Start with #include "screenhack.h" |
| 64 | |
| 65 | - Define 2 global variables: |
| 66 | |
| 67 | yoursavername_defaults -- Default values for the resources you use. |
| 68 | yoursavername_options -- The command-line options you accept. |
| 69 | |
| 70 | - Define 5 functions: |
| 71 | |
| 72 | yoursavername_init -- Return an object holding your global state. |
| 73 | yoursavername_draw -- Draw a single frame, quickly. |
| 74 | yoursavername_free -- Free everything you've allocated. |
| 75 | yoursavername_reshape -- Called when the window is resized. |
| 76 | yoursavername_event -- Called when a keyboard or mouse event happens. |
| 77 | |
| 78 | The "event" function will only be called when running in a window |
| 79 | (not as a screen saver). The "reshape" event will be called when the |
| 80 | window size changes, or (as a screen saver) when the display size |
| 81 | changes as a result of a RANDR event (e.g., plugging in a new monitor). |
| 82 | |
| 83 | It's ok for both the "event" and "resize" functions to do nothing. |
| 84 | |
| 85 | - All other functions should be static. |
| 86 | |
| 87 | - The last line of the file should be |
| 88 | XSCREENSAVER_MODULE ("YourSaverName", yoursavername) |
| 89 | |
| 90 | - Finally, edit the Makefile to add a rule for your program. |
| 91 | Just cut-and-paste one of the existing rules. |
| 92 | |
| 93 | Your "draw" must not run for more than a fraction of a second without |
| 94 | returning. This means "don't call usleep()". Everything has to be a |
| 95 | state machine. |
| 96 | |
| 97 | You may not store global state in global variables, or in function-local |
| 98 | static variables. All of your runtime state must be encapsulated in the |
| 99 | "state" object created by your "init" function. If you use global or |
| 100 | static variables, your screen saver will not work properly on macOS. |
| 101 | |
| 102 | Do not call XSync() or XFlush(). If you think you need to do that, it |
| 103 | probably means that you are you are relying on the speed of the graphics |
| 104 | card for timing, which probably means that your "draw" function is |
| 105 | taking too long. |
| 106 | |
| 107 | |
| 108 | ========================================================================== |
| 109 | The xlockmore API |
| 110 | ========================================================================== |
| 111 | |
| 112 | Some of the display modes that come with xscreensaver were ported from |
| 113 | xlock, and so, for historical reasons, they follow a slightly different |
| 114 | programming convention. For newly-written Xlib programs, you'd be |
| 115 | better off following the pattern used in hacks like "Deluxe" than in |
| 116 | hacks like "Flag". The xlockmore ones are the ones that begin with |
| 117 | "#ifdef STANDALONE" and #include "xlockmore.h". |
| 118 | |
| 119 | But, all OpenGL screen savers have to follow the xlockmore API. |
| 120 | |
| 121 | The xlockmore API is similar to the XScreenSaver API, in that you define |
| 122 | (roughly) the same set of functions, but the naming conventions are |
| 123 | slightly different. Instead of "hackname_init", it's "init_hackname", |
| 124 | and it should be preceeded with the pseudo-declaration ENTRYPOINT. |
| 125 | |
| 126 | One annoying mis-feature of the xlockmore API is that it *requires* you |
| 127 | to make use of global variables for two things: first, for each of your |
| 128 | command line options; and second, for an array that holds your global |
| 129 | state objects. These are the only exceptions to the "never use global |
| 130 | variables" rule. |
| 131 | |
| 132 | There are a few important differences between the original xlockmore API |
| 133 | and XScreenSaver's implementation: |
| 134 | |
| 135 | - XScreenSaver does not use refresh_hackname or change_hackname. |
| 136 | |
| 137 | - XScreenSaver provides two additional hooks not present in xlockmore: |
| 138 | reshape_hackname, and hackname_handle_event. These are respectively |
| 139 | equivalent to hackname_reshape and hackname_event in the XScreenSaver |
| 140 | API. |
| 141 | |
| 142 | - Under Xlib, MI_CLEARWINDOW doesn't clear the window immediately. |
| 143 | Instead, due to double buffering on macOS/iOS/Android, it waits until |
| 144 | after init_hackname or draw_hackname returns before clearing. Make |
| 145 | sure not to issue any Xlib drawing calls immediately after a call to |
| 146 | MI_CLEARWINDOW, as these will be erased. To clear the window |
| 147 | immediately, just use XClearWindow. |
| 148 | |
| 149 | |
| 150 | ========================================================================== |
| 151 | Programming Tips |
| 152 | ========================================================================== |
| 153 | |
| 154 | - Your screen saver should look reasonable at 20-30 frames per second. |
| 155 | That is, do not assume that your "draw" function will be called more |
| 156 | than 20 times a second. Even if you return a smaller requested delay |
| 157 | than that, you might not get it. Likewise, if your "draw" function |
| 158 | takes longer than 1/20th of a second to run, your screen saver may be |
| 159 | consuming too much CPU. |
| 160 | |
| 161 | - Don't make assumptions about the depth of the display, or whether it |
| 162 | is colormapped. You must allocate all your colors explicitly: do not |
| 163 | assume you can construct an RGB value and use that as a pixel value |
| 164 | directly. In particular, you can't make assumptions about whether |
| 165 | pixels are RGB, RGBA, ARGB, ABGR, or even whether they are 32, 24, |
| 166 | 16 or 8 bits. Use the utility routines provided by "utils/colors.h" |
| 167 | to simplify color allocation. |
| 168 | |
| 169 | - It is better to eliminate flicker by double-buffering to a Pixmap |
| 170 | than by erasing and re-drawing objects. Do not use drawing tricks |
| 171 | involving XOR. |
| 172 | |
| 173 | - If you use double-buffering, have a resource to turn it off. (MacOS, |
| 174 | iOS and Android have double-buffering built in, so you'd end up |
| 175 | triple-buffering.) |
| 176 | |
| 177 | - Understand the differences between Pixmaps and XImages, and keep in |
| 178 | mind which operations are happening in client memory and which are in |
| 179 | server memory, and which cause latency due to server round-trips. |
| 180 | Sometimes using the Shared Memory extension can be helpful, but |
| 181 | probably not as often as you might think. |
| 182 | |
| 183 | - On modern machines, OpenGL will always run faster than Xlib. It's |
| 184 | also more portable. Consider writing in OpenGL whenever possible. |
| 185 | |
| 186 | - Free any memory you allocate. While screen savers under X11 have |
| 187 | their memory freed automatically when their process is killed by |
| 188 | the XScreenSaver daemon, under iOS and Android screen savers exist |
| 189 | in long-lived processes where no such cleanup takes place. |
| 190 | Consider Valgrind or gcc -fsanitize=leak to find memory leaks. |
| 191 | |
| 192 | - Again, don't use global variables. If you are doing your developent |
| 193 | under X11, test your saver from the command line with the "-pair" |
| 194 | argument. If that crashes, you're using global variables! |
| 195 | |
| 196 | |
| 197 | ========================================================================== |
| 198 | macOS, iOS and Android |
| 199 | ========================================================================== |
| 200 | |
| 201 | Though XScreenSaver started its life as an X11 program, it also now runs |
| 202 | on macOS, iOS and Android, due to a maniacal collection of compatibility |
| 203 | shims. If you do your development on an X11 system, and follow the |
| 204 | usual XScreenSaver APIs, you shouldn't need to do anything special for |
| 205 | it to also work on macOS and on phones. |
| 206 | |
| 207 | See the READMEs in the OSX/ and android/ directories for instructions on |
| 208 | compiling for those platforms. |
| 209 | |
| 210 | To check that an X11 saver will fit well on a mobile device, test it |
| 211 | with -geometry 640x1136 and 640x960. That's a good first step, anyway. |
| 212 | |
| 213 | ========================================================================== |