Commit | Line | Data |
---|---|---|
6881658b AT |
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 | ========================================================================== |