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