Commit | Line | Data |
---|---|---|
84b74595 AT |
1 | /* (c) 2021 Aaron Taylor <ataylor at subgeniuskitty dot com> */ |
2 | /* See LICENSE.txt file for copyright and license details. */ | |
3 | ||
4 | // TODO: | |
5 | // - Write a brief description of the machine being simulated. Only one thread, reduced RAM, no meaningful console when running as screensaver, etc. | |
6 | ||
84b74595 AT |
7 | // Ideas for sample programs to include: |
8 | // - Build list of integers on the stack (DUP, IM_1, ADD). | |
9 | // - Calculate prime numbers, placing them on the stack. | |
10 | // - Lights sliding back and forth, like PDP-11 front panel with some OSes. | |
11 | ||
12 | #include "screenhack.h" | |
13 | #include "simulator.h" | |
14 | ||
15 | /* Keep this source code C89 compliant per XScreensaver's instructions. */ | |
16 | ||
17 | /* -------------------------------------------------------------------------- */ | |
18 | /* Data Structures */ | |
19 | /* -------------------------------------------------------------------------- */ | |
20 | ||
21 | struct NEDsim { | |
22 | /* Various X resources */ | |
23 | Display * dpy; | |
24 | Window win; | |
25 | GC gc; | |
26 | ||
27 | // TODO: Explain these | |
28 | int dpy_width, dpy_height; | |
29 | ||
30 | // TODO: Explain that this is created during init, then lights are populated/overwritten on each frame. | |
31 | Pixmap panel; | |
32 | ||
33 | /* Delay (in microseconds) between clock cycles in the NED CPU. */ | |
34 | size_t delay; | |
35 | ||
36 | int cell_size; | |
37 | int border_size; | |
38 | int origin_x_offset, origin_y_offset; | |
39 | int num_data_rows; | |
40 | ||
41 | size_t color_index; | |
42 | ||
43 | Bool suitable_display; | |
44 | ||
45 | char * current_font; | |
46 | ||
47 | // TODO: Explain that this contains all the actual state of the NED machine being simulated. | |
48 | struct NEDstate * nedstate; | |
49 | }; | |
50 | ||
51 | struct color_rgb { | |
52 | // TODO: Explain why this is an unsigned short. Copy from WolframAutomata. | |
53 | unsigned short red, green, blue; | |
54 | }; | |
55 | ||
56 | struct color_scheme { | |
57 | // TODO: Explain all this. | |
58 | struct color_rgb | |
59 | panel_bg, | |
60 | panel_fg, | |
61 | light_on, | |
62 | light_off, | |
63 | error_on, | |
64 | error_off, | |
65 | primary, | |
66 | secondary, | |
67 | tertiary, | |
68 | border, | |
69 | text; | |
70 | }; | |
71 | ||
72 | //static struct color_scheme color_list[] = { | |
73 | // // TODO: Explain all this. | |
74 | // // TODO: Add other color schemes, like purple PDP-11/70. | |
75 | // // TODO: http://www.chdickman.com/pdp8/DECcolors/ | |
76 | // { | |
77 | // {63479,63479,63479}, // 092-XXXX-123 | |
78 | // { 5140, 2056, 5654}, // 092-XXXX-152 | |
79 | // {40092,11051,15677}, // 092-XXXX-139 | |
80 | // {30326,13107,12850}, // 092-XXXX-154 | |
81 | // {65535,13107,12850}, // homemade | |
82 | // {30326,13107,12850}, // 092-XXXX-154 | |
83 | // { 4112,12850,20046}, // 092-XXXX-145 | |
84 | // {12336,29555,37008}, // 092-XXXX-151 | |
85 | // {30326,24158, 6425}, // 092-XXXX-157 | |
86 | // {63479,63479,63479}, // 092-XXXX-123 | |
87 | // {63479,63479,63479} // 092-XXXX-123 | |
88 | // } | |
89 | //}; | |
90 | ||
91 | static struct color_scheme color_list[] = { | |
92 | // TODO: Explain all this. | |
93 | // TODO: Add other color schemes, like purple PDP-11/70. | |
94 | // TODO: http://www.chdickman.com/pdp8/DECcolors/ | |
95 | { | |
96 | {63479,63479,63479}, // 092-XXXX-123 | |
97 | { 5140, 2056, 5654}, // 092-XXXX-152 | |
98 | {65535,11051,15677}, // 092-XXXX-139 - edit | |
99 | {20000,13107,12850}, // 092-XXXX-154 - edit | |
100 | {65535,13107,12850}, // homemade | |
101 | {20000,13107,12850}, // 092-XXXX-154 - edit | |
102 | { 4112,12850,20046}, // 092-XXXX-145 | |
103 | {12336,29555,37008}, // 092-XXXX-151 | |
104 | {30326,24158, 6425}, // 092-XXXX-157 | |
105 | {63479,63479,63479}, // 092-XXXX-123 | |
106 | {63479,63479,63479} // 092-XXXX-123 | |
107 | } | |
108 | }; | |
109 | ||
110 | /* -------------------------------------------------------------------------- */ | |
111 | /* Helper Functions */ | |
112 | /* -------------------------------------------------------------------------- */ | |
113 | ||
114 | // TODO: Explain | |
115 | static void | |
116 | set_color(struct NEDsim * nedsim, struct color_rgb * color) | |
117 | { | |
118 | XColor temp; | |
119 | XWindowAttributes xgwa; | |
120 | ||
121 | XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); | |
122 | ||
123 | temp.red = color->red; | |
124 | temp.green = color->green; | |
125 | temp.blue = color->blue; | |
126 | ||
127 | XAllocColor(nedsim->dpy, xgwa.colormap, &temp); | |
128 | XSetForeground(nedsim->dpy, nedsim->gc, temp.pixel); | |
129 | } | |
130 | ||
131 | // TODO: Explain | |
132 | // TODO: Make this a lot faster. | |
133 | // Input: size in 'cells', and sets font to fill that size, minus border and padding room. | |
134 | static void | |
135 | set_font_size(struct NEDsim * nedsim, int size) | |
136 | { | |
137 | // vvv--- for border ---vvv vvv--- for padding ---vvv | |
138 | int desired_height_in_pixels = (size * nedsim->cell_size) - (2 * nedsim->border_size) - (8 * nedsim->border_size); | |
139 | ||
140 | const char * font_size_prefix = "-*-helvetica-*-r-*-*-"; | |
141 | const char * font_size_suffix = "-*-*-*-*-*-*-*"; | |
142 | ||
143 | size_t buffer_size = strlen(font_size_prefix) + strlen(font_size_suffix) + 100; // '100' since nobody needs font with size in 'points' greater than 100 decimal digits long. | |
144 | char * font_full_name = malloc(buffer_size); | |
145 | int font_size_in_points = 2; // Start with a 2 pt font and work our way up. | |
146 | ||
147 | while (1) { | |
148 | // Load the font. | |
149 | snprintf(font_full_name, buffer_size, "%s%d%s", font_size_prefix, font_size_in_points, font_size_suffix); | |
150 | XFontStruct * font = XLoadQueryFont(nedsim->dpy, font_full_name); | |
151 | if (!font) { | |
152 | printf("WARNING: Unable to load font %s. Probably gonna look wonky.\n", font_full_name); | |
153 | font = XLoadQueryFont(nedsim->dpy, "fixed"); | |
154 | break; | |
155 | } | |
156 | XSetFont(nedsim->dpy, nedsim->gc, font->fid); | |
157 | ||
158 | // Get the height. | |
159 | int direction, ascent, descent; | |
160 | XCharStruct overall; | |
161 | XTextExtents(font, "X", 1, &direction, &ascent, &descent, &overall); | |
162 | ||
163 | // Compare the height. | |
164 | int height = overall.ascent - overall.descent; | |
165 | if (height == desired_height_in_pixels) { | |
166 | break; | |
167 | } else if (height > desired_height_in_pixels) { | |
168 | font_size_in_points--; | |
169 | snprintf(font_full_name, buffer_size, "%s%d%s", font_size_prefix, font_size_in_points, font_size_suffix); | |
170 | XFontStruct * font = XLoadQueryFont(nedsim->dpy, font_full_name); | |
171 | if (!font) { | |
172 | printf("WARNING: Unable to load font %s. Probably gonna look wonky.\n", font_full_name); | |
173 | font = XLoadQueryFont(nedsim->dpy, "fixed"); | |
174 | break; | |
175 | } | |
176 | XSetFont(nedsim->dpy, nedsim->gc, font->fid); | |
177 | break; | |
178 | } else { | |
179 | font_size_in_points++; | |
180 | } | |
181 | } | |
182 | ||
183 | free(nedsim->current_font); | |
184 | nedsim->current_font = font_full_name; | |
185 | } | |
186 | ||
187 | // TODO: Explain | |
188 | // TODO: Explain that this returns result in pixels so we can track fractional cell usage. | |
189 | static void | |
190 | get_text_size(struct NEDsim * nedsim, const char * text, int * x_size, int * y_size) | |
191 | { | |
192 | int direction, ascent, descent; | |
193 | XCharStruct overall; | |
194 | XFontStruct * font = XLoadQueryFont(nedsim->dpy, nedsim->current_font); | |
195 | XTextExtents(font, text, strlen(text), &direction, &ascent, &descent, &overall); | |
196 | *x_size = overall.width; | |
197 | *y_size = overall.ascent - overall.descent; | |
198 | } | |
199 | ||
200 | // TODO: Explain | |
201 | // TODO: Note that this might leave the foreground color changed. | |
202 | // Argument coordinates are in 'cells', not pixels. | |
203 | static void | |
204 | draw_rect_area(struct NEDsim * nedsim, size_t x_origin, size_t y_origin, size_t x_size, size_t y_size, | |
205 | Bool bord_top, Bool bord_bottom, Bool bord_left, Bool bord_right) | |
206 | { | |
207 | x_origin *= nedsim->cell_size; | |
208 | x_origin += nedsim->origin_x_offset; | |
209 | y_origin *= nedsim->cell_size; | |
210 | y_origin += nedsim->origin_y_offset; | |
211 | x_size *= nedsim->cell_size; | |
212 | y_size *= nedsim->cell_size; | |
213 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, x_size, y_size); | |
214 | ||
215 | set_color(nedsim, &color_list[nedsim->color_index].border); | |
216 | if (bord_top) { | |
217 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, x_size, nedsim->border_size); | |
218 | } | |
219 | if (bord_bottom) { | |
220 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, (y_origin + y_size - nedsim->border_size), x_size, nedsim->border_size); | |
221 | } | |
222 | if (bord_left) { | |
223 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, x_origin, y_origin, nedsim->border_size, y_size); | |
224 | } | |
225 | if (bord_right) { | |
226 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, (x_origin + x_size - nedsim->border_size), y_origin, nedsim->border_size, y_size); | |
227 | } | |
228 | } | |
229 | ||
230 | // TODO: Explain | |
231 | // Arguments are in units of 'cells', not pixels. | |
232 | // Will leave foreground color changed. | |
233 | // Draws filled circle with upper left corner at x,y. | |
234 | static void | |
235 | draw_circular_area(struct NEDsim * nedsim, size_t x, size_t y, double diameter) | |
236 | { | |
237 | // First, convert the function argument units from 'cells' to 'pixels' | |
238 | x *= nedsim->cell_size; | |
239 | y *= nedsim->cell_size; | |
240 | diameter *= nedsim->cell_size; | |
241 | ||
242 | // Add the panel's absolute x,y offset to the requested coordinates. | |
243 | x += nedsim->origin_x_offset; | |
244 | y += nedsim->origin_y_offset; | |
245 | ||
246 | // Shrink the circle to be a bit smaller than the bounding box allows. Note | |
247 | // that the three adjustment values must sum to 1.0. | |
248 | // For example, 0.7 + 0.15 + 0.15 = 1.0. | |
249 | x += (0.15 * diameter); | |
250 | y += (0.15 * diameter); | |
251 | diameter *= 0.7; | |
252 | ||
253 | // Because we only draw the bottom border on repeated rows (e.g. draw_wordline()), | |
254 | // we need to offset vertically by half a border height. | |
255 | y -= (0.5 * nedsim->border_size); | |
256 | ||
257 | // Start angle 0 and ending angle 360*64 is one full circle in Xlib units. | |
258 | XFillArc(nedsim->dpy, nedsim->win, nedsim->gc, x, y, diameter, diameter, 0, 360*64); | |
259 | } | |
260 | ||
261 | // TODO: Explain | |
262 | static void | |
263 | draw_panel(struct NEDsim * nedsim) | |
264 | { | |
265 | // TODO: Collect all relevant #defines somewhere. | |
266 | #define OVERALL_WIDTH_IN_CELLS 70 | |
267 | #define HEADER_HEIGHT_IN_CELLS 14 | |
268 | #define FOOTER_HEIGHT_IN_CELLS 2 | |
269 | ||
270 | // Draw background color over entire window. | |
271 | set_color(nedsim, &color_list[nedsim->color_index].panel_bg); | |
272 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, 0, 0, nedsim->dpy_width, nedsim->dpy_height); | |
273 | ||
274 | // Draw NED panel in foreground color. | |
275 | set_color(nedsim, &color_list[nedsim->color_index].panel_fg); | |
276 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, | |
277 | nedsim->origin_x_offset, | |
278 | nedsim->origin_y_offset, | |
279 | nedsim->cell_size * OVERALL_WIDTH_IN_CELLS, | |
280 | nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS) | |
281 | ); | |
a98a69c1 AT |
282 | |
283 | // Give the panel rounded corners by first deleting the four right-angle corners... | |
284 | set_color(nedsim, &color_list[nedsim->color_index].panel_bg); | |
285 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, | |
286 | nedsim->origin_x_offset, | |
287 | nedsim->origin_y_offset, | |
288 | nedsim->cell_size, | |
289 | nedsim->cell_size | |
290 | ); | |
291 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, | |
292 | nedsim->origin_x_offset + (nedsim->cell_size * (OVERALL_WIDTH_IN_CELLS-1)), | |
293 | nedsim->origin_y_offset, | |
294 | nedsim->cell_size, | |
295 | nedsim->cell_size | |
296 | ); | |
297 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, | |
298 | nedsim->origin_x_offset, | |
299 | nedsim->origin_y_offset + (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS - 1)), | |
300 | nedsim->cell_size, | |
301 | nedsim->cell_size | |
302 | ); | |
303 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, | |
304 | nedsim->origin_x_offset + (nedsim->cell_size * (OVERALL_WIDTH_IN_CELLS-1)), | |
305 | nedsim->origin_y_offset + (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS - 1)), | |
306 | nedsim->cell_size, | |
307 | nedsim->cell_size | |
308 | ); | |
309 | // ...and then replacing them with filled arcs, forming rounded corners. | |
310 | set_color(nedsim, &color_list[nedsim->color_index].panel_fg); | |
311 | XFillArc(nedsim->dpy, nedsim->win, nedsim->gc, | |
312 | nedsim->origin_x_offset, | |
313 | nedsim->origin_y_offset, | |
314 | nedsim->cell_size * 2, nedsim->cell_size * 2, | |
315 | 180*64, -90*64 | |
316 | ); | |
317 | XFillArc(nedsim->dpy, nedsim->win, nedsim->gc, | |
318 | nedsim->origin_x_offset + (nedsim->cell_size * (OVERALL_WIDTH_IN_CELLS-2)), | |
319 | nedsim->origin_y_offset, | |
320 | nedsim->cell_size * 2, nedsim->cell_size * 2, | |
321 | 0, 90*64 | |
322 | ); | |
323 | XFillArc(nedsim->dpy, nedsim->win, nedsim->gc, | |
324 | nedsim->origin_x_offset, | |
325 | nedsim->origin_y_offset + (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS - 2)), | |
326 | nedsim->cell_size * 2, nedsim->cell_size * 2, | |
327 | 180*64, 90*64 | |
328 | ); | |
329 | XFillArc(nedsim->dpy, nedsim->win, nedsim->gc, | |
330 | nedsim->origin_x_offset + (nedsim->cell_size * (OVERALL_WIDTH_IN_CELLS-2)), | |
331 | nedsim->origin_y_offset + (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS - 2)), | |
332 | nedsim->cell_size * 2, nedsim->cell_size * 2, | |
333 | 0, -90*64 | |
334 | ); | |
84b74595 AT |
335 | } |
336 | ||
337 | // TODO: Explain | |
338 | static void | |
339 | draw_logo(struct NEDsim * nedsim) | |
340 | { | |
341 | #define LOGO_X_OFFSET 2 | |
342 | #define LOGO_Y_OFFSET 2 | |
343 | #define LOGO_WIDTH 20 | |
344 | #define LOGO_NAME_HEIGHT 6 | |
345 | #define LOGO_WEBSITE_HEIGHT 2 | |
346 | ||
347 | // First draw the two colored boxes that comprise the logo area. | |
348 | set_color(nedsim, &color_list[nedsim->color_index].primary); | |
349 | draw_rect_area(nedsim, LOGO_X_OFFSET, LOGO_Y_OFFSET, LOGO_WIDTH, LOGO_NAME_HEIGHT, True, True, False, False); | |
350 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
351 | draw_rect_area(nedsim, LOGO_X_OFFSET, LOGO_Y_OFFSET+LOGO_NAME_HEIGHT, LOGO_WIDTH, LOGO_WEBSITE_HEIGHT, False, True, False, False); | |
352 | ||
353 | // Now draw the 'NED' text in the top box. | |
354 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
355 | set_font_size(nedsim, LOGO_NAME_HEIGHT); | |
356 | int text_x_size, text_y_size; | |
357 | get_text_size(nedsim, "NED", &text_x_size, &text_y_size); | |
358 | int local_x_offset = ((LOGO_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
359 | int local_y_offset = ((LOGO_NAME_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
360 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (LOGO_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
361 | ((LOGO_Y_OFFSET+LOGO_NAME_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "NED", 3); | |
362 | ||
363 | // And draw the 'subgeniuskitty.com' text in the bottom box. | |
364 | set_font_size(nedsim, LOGO_WEBSITE_HEIGHT); | |
365 | get_text_size(nedsim, "subgeniuskitty.com", &text_x_size, &text_y_size); | |
366 | local_x_offset = ((LOGO_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
367 | local_y_offset = ((LOGO_WEBSITE_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
368 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (LOGO_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
369 | ((LOGO_Y_OFFSET+LOGO_NAME_HEIGHT+LOGO_WEBSITE_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "subgeniuskitty.com", 18); | |
370 | } | |
371 | ||
372 | // TODO: Explain | |
373 | static void | |
374 | draw_halt(struct NEDsim * nedsim) | |
375 | { | |
376 | #define HALT_X_OFFSET 26 | |
377 | #define HALT_Y_OFFSET 2 | |
378 | #define HALT_WIDTH 6 | |
379 | #define HALT_LIGHT_HEIGHT 6 | |
380 | #define HALT_LABEL_HEIGHT 2 | |
381 | ||
382 | // First draw the two colored boxes that comprise the halt area. | |
383 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
384 | draw_rect_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET, HALT_WIDTH, HALT_LIGHT_HEIGHT, True, True, False, False); | |
385 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
386 | draw_rect_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET+HALT_LIGHT_HEIGHT, HALT_WIDTH, HALT_LABEL_HEIGHT, False, True, False, False); | |
387 | ||
388 | // And finally, draw the label. | |
389 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
390 | int text_x_size, text_y_size; | |
391 | get_text_size(nedsim, "HALT", &text_x_size, &text_y_size); | |
392 | int local_x_offset = ((HALT_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
393 | int local_y_offset = ((HALT_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
394 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (HALT_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
395 | ((HALT_Y_OFFSET+HALT_LIGHT_HEIGHT+HALT_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "HALT", 4); | |
396 | } | |
397 | ||
398 | // TODO: Explain | |
399 | static void | |
400 | draw_wordline_lights(struct NEDsim * nedsim, uint32_t word, int x, int y) | |
401 | { | |
402 | #define WORDLINE_BITS_PER_STRIPE 4 | |
403 | #define WORDLINE_WIDTH 32 | |
404 | #define WORDLINE_HEIGHT 1 | |
405 | ||
406 | for (int i = 0; i < WORDLINE_WIDTH; i++) { | |
407 | if (word & (1<<(WORDLINE_WIDTH-1-i))) { | |
408 | set_color(nedsim, &color_list[nedsim->color_index].light_on); | |
409 | } else { | |
410 | set_color(nedsim, &color_list[nedsim->color_index].light_off); | |
411 | } | |
412 | draw_circular_area(nedsim, x+i, y, WORDLINE_HEIGHT); | |
413 | } | |
414 | } | |
415 | ||
416 | // TODO: Explain | |
417 | static void | |
418 | draw_wordline(struct NEDsim * nedsim, int x, int y) | |
419 | { | |
420 | // First, draw a solid box in the primary color over the entire wordline area. | |
421 | set_color(nedsim, &color_list[nedsim->color_index].primary); | |
422 | draw_rect_area(nedsim, x, y, WORDLINE_WIDTH, WORDLINE_HEIGHT, False, True, False, False); | |
423 | ||
424 | // Now, draw stripes in the secondary color. | |
425 | int i; | |
426 | for (i = 0; i < (WORDLINE_WIDTH/(2*WORDLINE_BITS_PER_STRIPE)); i++) { | |
427 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
428 | draw_rect_area(nedsim, (x+(i*(WORDLINE_WIDTH/WORDLINE_BITS_PER_STRIPE))), y, | |
429 | WORDLINE_BITS_PER_STRIPE, WORDLINE_HEIGHT, False, True, False, False); | |
430 | } | |
431 | ||
432 | // Finally, draw the lights. | |
433 | draw_wordline_lights(nedsim, 0, x, y); | |
434 | } | |
435 | ||
436 | // TODO: Explain | |
437 | static void | |
438 | draw_pc(struct NEDsim * nedsim) | |
439 | { | |
440 | // TODO: Note that all #defines use units of 'cells', not 'pixels', etc. | |
441 | #define PC_X_OFFSET 36 | |
442 | #define PC_Y_OFFSET 7 | |
443 | #define PC_WIDTH 32 | |
444 | #define PC_LABEL_HEIGHT 2 | |
445 | #define PC_LIGHT_HEIGHT 1 | |
446 | ||
447 | // First draw the two colored boxes that comprise the PC area. | |
448 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
449 | draw_rect_area(nedsim, PC_X_OFFSET, PC_Y_OFFSET, PC_WIDTH, PC_LABEL_HEIGHT, True, True, False, False); | |
450 | draw_wordline(nedsim, PC_X_OFFSET, PC_Y_OFFSET+PC_LABEL_HEIGHT); | |
451 | ||
452 | // Now draw the label text "PC". | |
453 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
454 | int text_x_size, text_y_size; | |
455 | get_text_size(nedsim, "PC", &text_x_size, &text_y_size); | |
456 | int local_x_offset = ((PC_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
457 | int local_y_offset = ((PC_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
458 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PC_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
459 | ((PC_Y_OFFSET+PC_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "PC", 2); | |
460 | } | |
461 | ||
462 | // TODO: Explain | |
463 | static void | |
464 | draw_sc(struct NEDsim * nedsim) | |
465 | { | |
466 | #define SC_X_OFFSET 42 | |
467 | #define SC_Y_OFFSET 2 | |
468 | #define SC_WIDTH 5 | |
469 | #define SC_LABEL_HEIGHT 2 | |
470 | #define SC_LIGHT_HEIGHT 1 | |
471 | ||
472 | // First draw the two colored boxes that comprise the SC area. | |
473 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
474 | draw_rect_area(nedsim, SC_X_OFFSET, SC_Y_OFFSET, SC_WIDTH, SC_LABEL_HEIGHT, True, True, False, False); | |
475 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
476 | draw_rect_area(nedsim, SC_X_OFFSET, SC_Y_OFFSET+SC_LABEL_HEIGHT, SC_WIDTH, SC_LIGHT_HEIGHT, False, True, False, False); | |
477 | ||
478 | // Now draw the label text "SC". | |
479 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
480 | int text_x_size, text_y_size; | |
481 | get_text_size(nedsim, "SC", &text_x_size, &text_y_size); | |
482 | int local_x_offset = ((SC_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
483 | int local_y_offset = ((SC_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
484 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (SC_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
485 | ((SC_Y_OFFSET+SC_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "SC", 2); | |
486 | } | |
487 | ||
488 | // TODO: Explain | |
489 | static void | |
490 | draw_psw(struct NEDsim * nedsim) | |
491 | { | |
492 | #define PSW_N_X_OFFSET 51 | |
493 | #define PSW_Z_X_OFFSET 58 | |
494 | #define PSW_Y_OFFSET 2 | |
495 | #define PSW_LABEL_WIDTH 3 | |
496 | #define PSW_LABEL_HEIGHT 2 | |
497 | #define PSW_LIGHT_WIDTH 1 | |
498 | #define PSW_LIGHT_HEIGHT 1 | |
499 | ||
500 | // First draw the four colored boxes that comprise the two PSW areas. | |
501 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
502 | draw_rect_area(nedsim, PSW_N_X_OFFSET, PSW_Y_OFFSET, PSW_LABEL_WIDTH, PSW_LABEL_HEIGHT, True, True, False, False); | |
503 | set_color(nedsim, &color_list[nedsim->color_index].secondary); | |
504 | draw_rect_area(nedsim, PSW_Z_X_OFFSET, PSW_Y_OFFSET, PSW_LABEL_WIDTH, PSW_LABEL_HEIGHT, True, True, False, False); | |
505 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
506 | draw_rect_area(nedsim, (PSW_N_X_OFFSET + 1), PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_WIDTH, PSW_LIGHT_HEIGHT, False, True, False, False); | |
507 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
508 | draw_rect_area(nedsim, (PSW_Z_X_OFFSET + 1), PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_WIDTH, PSW_LIGHT_HEIGHT, False, True, False, False); | |
509 | ||
510 | // Now draw the label text "N". | |
511 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
512 | int text_x_size, text_y_size; | |
513 | get_text_size(nedsim, "N", &text_x_size, &text_y_size); | |
514 | int local_x_offset = ((PSW_LABEL_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
515 | int local_y_offset = ((PSW_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
516 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PSW_N_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
517 | ((PSW_Y_OFFSET+PSW_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "N", 1); | |
518 | ||
519 | // Now draw the label text "Z". | |
520 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
521 | get_text_size(nedsim, "Z", &text_x_size, &text_y_size); | |
522 | local_x_offset = ((PSW_LABEL_WIDTH * nedsim->cell_size) - text_x_size) / 2; | |
523 | local_y_offset = ((PSW_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
524 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, (PSW_Z_X_OFFSET * nedsim->cell_size + nedsim->origin_x_offset + local_x_offset), | |
525 | ((PSW_Y_OFFSET+PSW_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "Z", 1); | |
526 | } | |
527 | ||
528 | // TODO: Explain | |
529 | static void | |
530 | draw_stack(struct NEDsim * nedsim) | |
531 | { | |
532 | #define STACK_X_OFFSET 2 | |
533 | #define STACK_Y_OFFSET 12 | |
534 | #define STACK_WIDTH 32 | |
535 | #define STACK_LABEL_HEIGHT 2 | |
536 | #define STACK_LIGHT_HEIGHT 1 | |
537 | ||
538 | // First draw the two colored boxes that comprise the stack area. | |
539 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
540 | draw_rect_area(nedsim, STACK_X_OFFSET, STACK_Y_OFFSET, STACK_WIDTH, STACK_LABEL_HEIGHT, True, True, False, False); | |
541 | for (int i = 0; i < nedsim->num_data_rows; i++) { | |
542 | draw_wordline(nedsim, STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); | |
543 | } | |
544 | ||
545 | // Now draw the label text "Stack Size:". | |
546 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
547 | int text_x_size, text_y_size; | |
548 | get_text_size(nedsim, "Stack Size:", &text_x_size, &text_y_size); | |
549 | int local_y_offset = ((STACK_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
550 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((STACK_X_OFFSET + 1) * nedsim->cell_size + nedsim->origin_x_offset), | |
551 | ((STACK_Y_OFFSET+STACK_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "Stack Size:", 11); | |
552 | } | |
553 | ||
554 | // TODO: Explain | |
555 | static void | |
556 | draw_heap(struct NEDsim * nedsim) | |
557 | { | |
558 | #define HEAP_X_OFFSET 36 | |
559 | #define HEAP_Y_OFFSET 12 | |
560 | #define HEAP_WIDTH 32 | |
561 | #define HEAP_LABEL_HEIGHT 2 | |
562 | #define HEAP_LIGHT_HEIGHT 1 | |
563 | // TODO: What should I do about this define? I would like to be able to specify the address so I can do things like display the code itself if nothign interesting happens in RAM. | |
564 | #define HEAP_START_ADDRESS 0x20000000 | |
565 | ||
566 | // First draw the two colored boxes that comprise the heap area. | |
567 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
568 | draw_rect_area(nedsim, HEAP_X_OFFSET, HEAP_Y_OFFSET, HEAP_WIDTH, HEAP_LABEL_HEIGHT, True, True, False, False); | |
569 | for (int i = 0; i < nedsim->num_data_rows; i++) { | |
570 | draw_wordline(nedsim, HEAP_X_OFFSET, HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT+i); | |
571 | } | |
572 | ||
573 | // Now draw the label text "RAM Base:". | |
574 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
575 | int text_x_size, text_y_size; | |
576 | get_text_size(nedsim, "RAM Base:", &text_x_size, &text_y_size); | |
577 | int local_y_offset = ((HEAP_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
578 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((HEAP_X_OFFSET + 1) * nedsim->cell_size + nedsim->origin_x_offset), | |
579 | ((HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), "RAM Base:", 9); | |
580 | ||
581 | // Now draw the address text. | |
582 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
583 | char address[11]; | |
584 | snprintf(address, sizeof(address), "0x%08X", HEAP_START_ADDRESS); | |
585 | get_text_size(nedsim, address, &text_x_size, &text_y_size); | |
586 | local_y_offset = ((HEAP_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
587 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((HEAP_X_OFFSET + 1 + (HEAP_WIDTH / 2)) * nedsim->cell_size + nedsim->origin_x_offset), | |
588 | ((HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), address, strlen(address)); | |
589 | } | |
590 | ||
591 | // TODO: Explain | |
592 | static void | |
593 | update_display(struct NEDsim * nedsim) | |
594 | { | |
595 | // Draw the halt indicator. | |
596 | if (nedsim->nedstate->halted) { | |
597 | set_color(nedsim, &color_list[nedsim->color_index].error_on); | |
598 | } else { | |
599 | set_color(nedsim, &color_list[nedsim->color_index].error_off); | |
600 | } | |
601 | draw_circular_area(nedsim, HALT_X_OFFSET, HALT_Y_OFFSET, HALT_WIDTH); | |
602 | ||
603 | // Draw the PSW "N" light. | |
604 | if (nedsim->nedstate->active_thread->psw->negative) { | |
605 | set_color(nedsim, &color_list[nedsim->color_index].light_on); | |
606 | } else { | |
607 | set_color(nedsim, &color_list[nedsim->color_index].light_off); | |
608 | } | |
609 | draw_circular_area(nedsim, PSW_N_X_OFFSET+1, PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_HEIGHT); | |
610 | ||
611 | // Draw the PSW "Z" light. | |
612 | if (nedsim->nedstate->active_thread->psw->zero) { | |
613 | set_color(nedsim, &color_list[nedsim->color_index].light_on); | |
614 | } else { | |
615 | set_color(nedsim, &color_list[nedsim->color_index].light_off); | |
616 | } | |
617 | draw_circular_area(nedsim, PSW_Z_X_OFFSET+1, PSW_Y_OFFSET+PSW_LABEL_HEIGHT, PSW_LIGHT_HEIGHT); | |
618 | ||
619 | ||
620 | // Draw the SC. | |
621 | int i; | |
622 | for (i = 0; i < SC_WIDTH; i++) { | |
623 | if ((SC_WIDTH-1-i) == nedsim->nedstate->active_thread->sc) { | |
624 | set_color(nedsim, &color_list[nedsim->color_index].light_on); | |
625 | } else { | |
626 | set_color(nedsim, &color_list[nedsim->color_index].light_off); | |
627 | } | |
628 | draw_circular_area(nedsim, SC_X_OFFSET+i, SC_Y_OFFSET+SC_LABEL_HEIGHT, SC_LIGHT_HEIGHT); | |
629 | } | |
630 | ||
631 | // Draw the PC. | |
632 | draw_wordline_lights(nedsim, nedsim->nedstate->active_thread->pc, PC_X_OFFSET, PC_Y_OFFSET+PC_LABEL_HEIGHT); | |
633 | ||
634 | // Draw the stack lights. | |
635 | int64_t top_of_stack = ((int64_t)nedsim->nedstate->active_thread->sp) - 1; | |
636 | for (i = 0; i < nedsim->num_data_rows; i++) { | |
637 | if ((top_of_stack-i) >= 0) { | |
638 | draw_wordline_lights(nedsim, nedsim->nedstate->active_thread->stack[top_of_stack-i], STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); | |
639 | } else { | |
640 | draw_wordline_lights(nedsim, 0, STACK_X_OFFSET, STACK_Y_OFFSET+STACK_LABEL_HEIGHT+i); | |
641 | } | |
642 | } | |
643 | ||
644 | // Draw the stack size in text. | |
645 | set_color(nedsim, &color_list[nedsim->color_index].tertiary); | |
646 | draw_rect_area(nedsim, STACK_X_OFFSET+(STACK_WIDTH/2), STACK_Y_OFFSET, STACK_WIDTH/2, STACK_LABEL_HEIGHT, True, True, False, False); | |
647 | set_color(nedsim, &color_list[nedsim->color_index].text); | |
648 | char stack_size[11]; | |
649 | snprintf(stack_size, sizeof(stack_size), "0x%08X", nedsim->nedstate->active_thread->sp); | |
650 | int text_x_size, text_y_size; | |
651 | get_text_size(nedsim, stack_size, &text_x_size, &text_y_size); | |
652 | int local_y_offset = ((STACK_LABEL_HEIGHT * nedsim->cell_size) - text_y_size) / 2; | |
653 | XDrawString(nedsim->dpy, nedsim->win, nedsim->gc, ((STACK_X_OFFSET + 1 + (STACK_WIDTH / 2)) * nedsim->cell_size + nedsim->origin_x_offset), | |
654 | ((STACK_Y_OFFSET+STACK_LABEL_HEIGHT) * nedsim->cell_size + nedsim->origin_y_offset - local_y_offset), stack_size, strlen(stack_size)); | |
655 | ||
656 | // Draw the heap lights. | |
657 | for (i = 0; i < nedsim->num_data_rows; i++) { | |
658 | draw_wordline_lights(nedsim, ram_r_word(nedsim->nedstate, HEAP_START_ADDRESS+(i*BPW)), HEAP_X_OFFSET, HEAP_Y_OFFSET+HEAP_LABEL_HEIGHT+i); | |
659 | } | |
660 | } | |
661 | ||
662 | /* -------------------------------------------------------------------------- */ | |
663 | /* Screenhack API Functions */ | |
664 | /* -------------------------------------------------------------------------- */ | |
665 | ||
666 | static Bool | |
667 | NEDsim_event(Display * dpy, Window win, void * closure, XEvent * event) | |
668 | { | |
669 | return False; | |
670 | } | |
671 | ||
672 | static void | |
673 | NEDsim_free(Display * dpy, Window win, void * closure) | |
674 | { | |
84b74595 | 675 | struct NEDsim * nedsim = closure; |
d87b1e06 AT |
676 | |
677 | if (nedsim->nedstate != NULL) { | |
678 | free(nedsim->nedstate->active_thread->psw); | |
679 | free(nedsim->nedstate->active_thread); | |
680 | free(nedsim->nedstate->hack); | |
681 | free(nedsim->nedstate); | |
682 | } | |
683 | ||
684 | // TODO: Replace all this with proper code to free everything related to the screensaver itself. | |
84b74595 AT |
685 | XFreeGC(nedsim->dpy, nedsim->gc); |
686 | free(nedsim); | |
687 | } | |
688 | ||
689 | static void * | |
690 | NEDsim_init(Display * dpy, Window win) | |
691 | { | |
692 | struct NEDsim * nedsim; | |
693 | XGCValues gcv; | |
694 | XWindowAttributes xgwa; | |
695 | ||
696 | nedsim = calloc(1, sizeof(*nedsim)); | |
697 | if (!nedsim) { | |
698 | fprintf(stderr, "ERROR: Failed to calloc() for NEDsim struct in NEDsim_init().\n"); | |
699 | exit(EXIT_FAILURE); | |
700 | } | |
701 | ||
702 | nedsim->dpy = dpy; | |
703 | nedsim->win = win; | |
704 | ||
705 | XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); | |
706 | nedsim->dpy_width = xgwa.width; | |
707 | nedsim->dpy_height = xgwa.height; | |
708 | ||
709 | // TODO: Explain that this is the delay between each clock cycle of the simulated NED CPU. | |
710 | nedsim->delay = get_integer_resource(nedsim->dpy, "delay", "Integer"); | |
711 | nedsim->delay *= 1000; /* Turn milliseconds into microseconds. */ | |
712 | ||
5923644e AT |
713 | // Load the program file specified by the user or, if none is specified, |
714 | // randomly select one of the included programs. | |
715 | char * input_file = get_string_resource(nedsim->dpy, "binary", "String"); | |
716 | if (input_file == NULL) { | |
717 | // TODO: Need to include some default programs and randomly select one to load into RAM. | |
718 | nedsim->nedstate = init_simulator("./test.out"); | |
719 | } else { | |
720 | nedsim->nedstate = init_simulator(input_file); | |
721 | } | |
84b74595 AT |
722 | |
723 | nedsim->gc = XCreateGC(nedsim->dpy, nedsim->win, GCForeground, &gcv); | |
724 | ||
725 | // TODO: Do this properly. | |
726 | nedsim->color_index = 0; | |
727 | ||
728 | // TODO: Save the GIMP reference diagram somewhere, along with notes about the size/spacing, and that each cell is 10 pixels across in the reference image. | |
729 | ||
730 | nedsim->cell_size = nedsim->dpy_width / OVERALL_WIDTH_IN_CELLS ; // make panel as wide as it can be while keeping every cell an integer pixel size. | |
731 | // TODO: What is my minimum cell_size? Below that, I should simply paint the window red and print an error in the console. Perform that check here, right after setting cell_size. | |
732 | // For now, we'll just make it 10 pixels? | |
733 | if (nedsim->cell_size < 10) { | |
734 | nedsim->suitable_display = False; | |
735 | return nedsim; | |
736 | } | |
737 | nedsim->origin_x_offset = (nedsim->dpy_width - (nedsim->cell_size * OVERALL_WIDTH_IN_CELLS)) / 2; // center panel horizontally | |
738 | ||
739 | // Determine how many rows for the stack and heap displays. Make it the largest power of two that fits on the display. | |
740 | int available_space_for_data_rows = nedsim->dpy_height - (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + FOOTER_HEIGHT_IN_CELLS)); | |
741 | for (int i = 0; ; i++) { | |
742 | if ((nedsim->cell_size * (1 << i)) > available_space_for_data_rows) { | |
743 | nedsim->num_data_rows = (1 << --i); | |
744 | break; | |
745 | } | |
746 | } | |
747 | if (nedsim->num_data_rows < 4) { | |
748 | nedsim->suitable_display = False; | |
749 | return nedsim; | |
750 | } | |
751 | nedsim->origin_y_offset = (nedsim->dpy_height - (nedsim->cell_size * (HEADER_HEIGHT_IN_CELLS + nedsim->num_data_rows + FOOTER_HEIGHT_IN_CELLS))) / 2; // center panel vertically | |
752 | ||
753 | // Scale border relative to cell_size in a 1:10 relationship. | |
754 | nedsim->border_size = nedsim->cell_size / 10; | |
755 | ||
756 | draw_panel(nedsim); | |
757 | draw_logo(nedsim); | |
758 | draw_halt(nedsim); | |
759 | draw_pc(nedsim); | |
760 | draw_sc(nedsim); | |
761 | draw_psw(nedsim); | |
762 | draw_stack(nedsim); | |
763 | draw_heap(nedsim); | |
764 | ||
765 | nedsim->suitable_display = True; | |
766 | ||
767 | return nedsim; | |
768 | } | |
769 | ||
770 | static unsigned long | |
771 | NEDsim_draw(Display * dpy, Window win, void * closure) | |
772 | { | |
773 | struct NEDsim * nedsim = closure; | |
774 | ||
775 | if (nedsim->suitable_display) { | |
776 | nedsim->nedstate = run_simulator(nedsim->nedstate); | |
777 | update_display(nedsim); | |
778 | } else { | |
779 | set_color(nedsim, &color_list[nedsim->color_index].error_on); | |
780 | XFillRectangle(nedsim->dpy, nedsim->win, nedsim->gc, 0, 0, nedsim->dpy_width, nedsim->dpy_height); | |
781 | } | |
782 | ||
783 | return nedsim->delay; | |
784 | } | |
785 | ||
786 | static void | |
787 | NEDsim_reshape(Display * dpy, Window win, void * closure, unsigned int w, unsigned int h) | |
788 | { | |
789 | struct NEDsim * nedsim = closure; | |
790 | XWindowAttributes xgwa; | |
791 | XGetWindowAttributes(nedsim->dpy, nedsim->win, &xgwa); | |
792 | /* Only restart the simulation if the window changed size. */ | |
793 | if (nedsim->dpy_width != xgwa.width || nedsim->dpy_height != xgwa.height) { | |
503470d7 AT |
794 | struct NEDstate * original_nedstate = nedsim->nedstate; |
795 | nedsim->nedstate = NULL; | |
796 | NEDsim_free(dpy, win, nedsim); | |
797 | struct NEDsim * new_nedsim = NEDsim_init(dpy, win); | |
798 | new_nedsim->nedstate = original_nedstate; | |
799 | closure = new_nedsim; | |
84b74595 AT |
800 | } |
801 | } | |
802 | ||
803 | static const char * NEDsim_defaults[] = { | |
804 | "*delay: 250", | |
805 | 0 | |
806 | }; | |
807 | ||
808 | static XrmOptionDescRec NEDsim_options[] = { | |
809 | { "-delay", ".delay", XrmoptionSepArg, 0 }, | |
5923644e | 810 | { "-binary", ".binary", XrmoptionSepArg, 0 }, |
84b74595 AT |
811 | { 0, 0, 0, 0 } |
812 | }; | |
813 | ||
814 | XSCREENSAVER_MODULE ("Blinken-lights simulator for NED1 CPU architecture.", NEDsim) |