1/*****************************************************************************\
2 Snes9x - Portable Super Nintendo Entertainment System (TM) emulator.
3 This file is licensed under the Snes9x License.
4 For further information, consult the LICENSE file in the root directory.
5\*****************************************************************************/
6
7#include <stdlib.h>
8#include <unistd.h>
9#include <ctype.h>
10#include <fcntl.h>
11#include <errno.h>
12#include <string.h>
13#ifdef HAVE_STRINGS_H
14#include <strings.h>
15#endif
16#include <sys/stat.h>
17#include <sys/ioctl.h>
18
19#include <X11/Xlib.h>
20#include <X11/Xutil.h>
21#include <X11/Xatom.h>
22#include <X11/keysym.h>
23#include <X11/cursorfont.h>
24
25#ifdef USE_XVIDEO
26#include <X11/extensions/Xvlib.h>
27
28#define FOURCC_YUY2 0x32595559
29#endif
30
31#ifdef USE_XINERAMA
32#include <X11/extensions/Xinerama.h>
33#endif
34
35#ifdef MITSHM
36#include <sys/ipc.h>
37#include <sys/shm.h>
38#include <X11/extensions/XShm.h>
39#endif
40
41#include "snes9x.h"
42#include "memmap.h"
43#include "ppu.h"
44#include "controls.h"
45#include "movie.h"
46#include "logger.h"
47#include "conffile.h"
48#include "blit.h"
49#include "display.h"
50
51// Wrapper struct to make generic XvImage vs XImage
52struct Image
53{
54#ifdef USE_XVIDEO
55 union
56 {
57 XvImage* xvimage;
58#endif
59 XImage* ximage;
60#ifdef USE_XVIDEO
61 };
62#endif
63
64 char *data;
65
66 uint32 height;
67 uint32 data_size;
68 uint32 bits_per_pixel;
69 uint32 bytes_per_line;
70};
71
72struct GUIData
73{
74 Display *display;
75 Screen *screen;
76 Visual *visual;
77 GC gc;
78 int screen_num;
79 int depth;
80 int pixel_format;
81 int bytes_per_pixel;
82 uint32 red_shift;
83 uint32 blue_shift;
84 uint32 green_shift;
85 uint32 red_size;
86 uint32 green_size;
87 uint32 blue_size;
88 Window window;
89 Image *image;
90 uint8 *snes_buffer;
91 uint8 *filter_buffer;
92 uint8 *blit_screen;
93 uint32 blit_screen_pitch;
94 bool8 need_convert;
95 Cursor point_cursor;
96 Cursor cross_hair_cursor;
97 int video_mode;
98 int mouse_x;
99 int mouse_y;
100 bool8 mod1_pressed;
101 bool8 no_repeat;
102 bool8 fullscreen;
103 bool8 js_event_latch;
104 int x_offset;
105 int y_offset;
106#ifdef USE_XVIDEO
107 bool8 use_xvideo;
108 int xv_port;
109 int scale_w;
110 int scale_h;
111
112 bool8 maxaspect;
113 int imageHeight;
114
115 int xv_format;
116 int xv_bpp;
117 unsigned char y_table[1 << 15];
118 unsigned char u_table[1 << 15];
119 unsigned char v_table[1 << 15];
120#endif
121#ifdef USE_XINERAMA
122 uint32 xinerama_head;
123#endif
124#ifdef MITSHM
125 XShmSegmentInfo sm_info;
126 bool8 use_shared_memory;
127#endif
128};
129
130static struct GUIData GUI;
131
132typedef std::pair<std::string, std::string> strpair_t;
133extern std::vector<strpair_t> keymaps;
134
135typedef void (* Blitter) (uint8 *, int, uint8 *, int, int, int);
136
137#ifdef __linux
138// Select seems to be broken in 2.x.x kernels - if a signal interrupts a
139// select system call with a zero timeout, the select call is restarted but
140// with an infinite timeout! The call will block until data arrives on the
141// selected fd(s).
142//
143// The workaround is to stop the X library calling select in the first
144// place! Replace XPending - which polls for data from the X server using
145// select - with an ioctl call to poll for data and then only call the blocking
146// XNextEvent if data is waiting.
147#define SELECT_BROKEN_FOR_SIGNALS
148#endif
149
150enum
151{
152 VIDEOMODE_BLOCKY = 1,
153 VIDEOMODE_TV,
154 VIDEOMODE_SMOOTH,
155 VIDEOMODE_SUPEREAGLE,
156 VIDEOMODE_2XSAI,
157 VIDEOMODE_SUPER2XSAI,
158 VIDEOMODE_EPX,
159 VIDEOMODE_HQ2X
160};
161
162static int ErrorHandler (Display *, XErrorEvent *);
163static bool8 CheckForPendingXEvents (Display *);
164static void SetXRepeat (bool8);
165static void SetupImage (void);
166static void TakedownImage (void);
167static void SetupXImage (void);
168static void TakedownXImage (void);
169#ifdef USE_XVIDEO
170static void SetupXvImage (void);
171static void TakedownXvImage (void);
172#endif
173static void Repaint (bool8);
174static void Convert16To24 (int, int);
175static void Convert16To24Packed (int, int);
176
177
178void S9xExtraDisplayUsage (void)
179{
180 /* 12345678901234567890123456789012345678901234567890123456789012345678901234567890 */
181
182 S9xMessage(S9X_INFO, S9X_USAGE, "-setrepeat Allow altering keyboard auto-repeat");
183 S9xMessage(S9X_INFO, S9X_USAGE, "");
184 S9xMessage(S9X_INFO, S9X_USAGE, "-fullscreen Switch to full-screen on start");
185#ifdef USE_XVIDEO
186 S9xMessage(S9X_INFO, S9X_USAGE, "-xvideo Hardware accelerated scaling");
187 S9xMessage(S9X_INFO, S9X_USAGE, "-maxaspect Try to fill the display, in fullscreen");
188#endif
189#ifdef USE_XINERAMA
190 S9xMessage(S9X_INFO, S9X_USAGE, "-xineramahead Xinerama head number for multi-monitor setups");
191#endif
192 S9xMessage(S9X_INFO, S9X_USAGE, "");
193 S9xMessage(S9X_INFO, S9X_USAGE, "-v1 Video mode: Blocky (default)");
194 S9xMessage(S9X_INFO, S9X_USAGE, "-v2 Video mode: TV");
195 S9xMessage(S9X_INFO, S9X_USAGE, "-v3 Video mode: Smooth");
196 S9xMessage(S9X_INFO, S9X_USAGE, "-v4 Video mode: SuperEagle");
197 S9xMessage(S9X_INFO, S9X_USAGE, "-v5 Video mode: 2xSaI");
198 S9xMessage(S9X_INFO, S9X_USAGE, "-v6 Video mode: Super2xSaI");
199 S9xMessage(S9X_INFO, S9X_USAGE, "-v7 Video mode: EPX");
200 S9xMessage(S9X_INFO, S9X_USAGE, "-v8 Video mode: hq2x");
201 S9xMessage(S9X_INFO, S9X_USAGE, "");
202}
203
204void S9xParseDisplayArg (char **argv, int &i, int argc)
205{
206 if (!strcasecmp(argv[i], "-setrepeat"))
207 GUI.no_repeat = FALSE;
208 else
209 if (!strcasecmp(argv[i], "-fullscreen"))
210 GUI.fullscreen = TRUE;
211 else
212#ifdef USE_XVIDEO
213 if (!strcasecmp(argv[i], "-xvideo"))
214 GUI.use_xvideo = TRUE;
215 else
216 if (!strcasecmp(argv[i], "-maxaspect"))
217 GUI.maxaspect = TRUE;
218 else
219#endif
220#ifdef USE_XINERAMA
221 if (!strcasecmp(argv[i], "-xineramahead"))
222 {
223 if (i + 1 < argc)
224 GUI.xinerama_head = atoi(argv[++i]);
225 else
226 S9xUsage();
227 }
228 else
229#endif
230 if (!strncasecmp(argv[i], "-v", 2))
231 {
232 switch (argv[i][2])
233 {
234 case '1': GUI.video_mode = VIDEOMODE_BLOCKY; break;
235 case '2': GUI.video_mode = VIDEOMODE_TV; break;
236 case '3': GUI.video_mode = VIDEOMODE_SMOOTH; break;
237 case '4': GUI.video_mode = VIDEOMODE_SUPEREAGLE; break;
238 case '5': GUI.video_mode = VIDEOMODE_2XSAI; break;
239 case '6': GUI.video_mode = VIDEOMODE_SUPER2XSAI; break;
240 case '7': GUI.video_mode = VIDEOMODE_EPX; break;
241 case '8': GUI.video_mode = VIDEOMODE_HQ2X; break;
242 }
243 }
244 else
245 S9xUsage();
246}
247
248const char * S9xParseDisplayConfig (ConfigFile &conf, int pass)
249{
250 if (pass != 1)
251 return ("Unix/X11");
252
253 if (!conf.GetBool("Unix::ClearAllControls", false))
254 {
255 keymaps.push_back(strpair_t("K00:k", "Joypad1 Right"));
256 keymaps.push_back(strpair_t("K00:Right", "Joypad1 Right"));
257 keymaps.push_back(strpair_t("K00:h", "Joypad1 Left"));
258 keymaps.push_back(strpair_t("K00:Left", "Joypad1 Left"));
259 keymaps.push_back(strpair_t("K00:j", "Joypad1 Down"));
260 keymaps.push_back(strpair_t("K00:n", "Joypad1 Down"));
261 keymaps.push_back(strpair_t("K00:Down", "Joypad1 Down"));
262 keymaps.push_back(strpair_t("K00:u", "Joypad1 Up"));
263 keymaps.push_back(strpair_t("K00:Up", "Joypad1 Up"));
264 keymaps.push_back(strpair_t("K00:Return", "Joypad1 Start"));
265 keymaps.push_back(strpair_t("K00:space", "Joypad1 Select"));
266 keymaps.push_back(strpair_t("K00:S+d", "Joypad1 ToggleTurbo A"));
267 keymaps.push_back(strpair_t("K00:C+d", "Joypad1 ToggleSticky A"));
268 keymaps.push_back(strpair_t("K00:d", "Joypad1 A"));
269 keymaps.push_back(strpair_t("K00:S+c", "Joypad1 ToggleTurbo B"));
270 keymaps.push_back(strpair_t("K00:C+c", "Joypad1 ToggleSticky B"));
271 keymaps.push_back(strpair_t("K00:c", "Joypad1 B"));
272 keymaps.push_back(strpair_t("K00:S+s", "Joypad1 ToggleTurbo X"));
273 keymaps.push_back(strpair_t("K00:C+s", "Joypad1 ToggleSticky X"));
274 keymaps.push_back(strpair_t("K00:s", "Joypad1 X"));
275 keymaps.push_back(strpair_t("K00:S+x", "Joypad1 ToggleTurbo Y"));
276 keymaps.push_back(strpair_t("K00:C+x", "Joypad1 ToggleSticky Y"));
277 keymaps.push_back(strpair_t("K00:x", "Joypad1 Y"));
278 keymaps.push_back(strpair_t("K00:S+a", "Joypad1 ToggleTurbo L"));
279 keymaps.push_back(strpair_t("K00:S+v", "Joypad1 ToggleTurbo L"));
280 keymaps.push_back(strpair_t("K00:C+a", "Joypad1 ToggleSticky L"));
281 keymaps.push_back(strpair_t("K00:C+v", "Joypad1 ToggleSticky L"));
282 keymaps.push_back(strpair_t("K00:a", "Joypad1 L"));
283 keymaps.push_back(strpair_t("K00:v", "Joypad1 L"));
284 keymaps.push_back(strpair_t("K00:S+z", "Joypad1 ToggleTurbo R"));
285 keymaps.push_back(strpair_t("K00:C+z", "Joypad1 ToggleSticky R"));
286 keymaps.push_back(strpair_t("K00:z", "Joypad1 R"));
287
288 keymaps.push_back(strpair_t("K00:KP_Left", "Joypad2 Left"));
289 keymaps.push_back(strpair_t("K00:KP_Right", "Joypad2 Right"));
290 keymaps.push_back(strpair_t("K00:KP_Up", "Joypad2 Up"));
291 keymaps.push_back(strpair_t("K00:KP_Down", "Joypad2 Down"));
292 keymaps.push_back(strpair_t("K00:KP_Enter", "Joypad2 Start"));
293 keymaps.push_back(strpair_t("K00:KP_Add", "Joypad2 Select"));
294 keymaps.push_back(strpair_t("K00:Prior", "Joypad2 A"));
295 keymaps.push_back(strpair_t("K00:Next", "Joypad2 B"));
296 keymaps.push_back(strpair_t("K00:Home", "Joypad2 X"));
297 keymaps.push_back(strpair_t("K00:End", "Joypad2 Y"));
298 keymaps.push_back(strpair_t("K00:Insert", "Joypad2 L"));
299 keymaps.push_back(strpair_t("K00:Delete", "Joypad2 R"));
300
301 keymaps.push_back(strpair_t("K00:A+F4", "SoundChannel0"));
302 keymaps.push_back(strpair_t("K00:C+F4", "SoundChannel0"));
303 keymaps.push_back(strpair_t("K00:A+F5", "SoundChannel1"));
304 keymaps.push_back(strpair_t("K00:C+F5", "SoundChannel1"));
305 keymaps.push_back(strpair_t("K00:A+F6", "SoundChannel2"));
306 keymaps.push_back(strpair_t("K00:C+F6", "SoundChannel2"));
307 keymaps.push_back(strpair_t("K00:A+F7", "SoundChannel3"));
308 keymaps.push_back(strpair_t("K00:C+F7", "SoundChannel3"));
309 keymaps.push_back(strpair_t("K00:A+F8", "SoundChannel4"));
310 keymaps.push_back(strpair_t("K00:C+F8", "SoundChannel4"));
311 keymaps.push_back(strpair_t("K00:A+F9", "SoundChannel5"));
312 keymaps.push_back(strpair_t("K00:C+F9", "SoundChannel5"));
313 keymaps.push_back(strpair_t("K00:A+F10", "SoundChannel6"));
314 keymaps.push_back(strpair_t("K00:C+F10", "SoundChannel6"));
315 keymaps.push_back(strpair_t("K00:A+F11", "SoundChannel7"));
316 keymaps.push_back(strpair_t("K00:C+F11", "SoundChannel7"));
317 keymaps.push_back(strpair_t("K00:A+F12", "SoundChannelsOn"));
318 keymaps.push_back(strpair_t("K00:C+F12", "SoundChannelsOn"));
319
320 keymaps.push_back(strpair_t("K00:S+1", "BeginRecordingMovie"));
321 keymaps.push_back(strpair_t("K00:S+2", "EndRecordingMovie"));
322 keymaps.push_back(strpair_t("K00:S+3", "LoadMovie"));
323 keymaps.push_back(strpair_t("K00:A+F1", "SaveSPC"));
324 keymaps.push_back(strpair_t("K00:C+F1", "SaveSPC"));
325 keymaps.push_back(strpair_t("K00:F10", "LoadOopsFile"));
326 keymaps.push_back(strpair_t("K00:A+F2", "LoadFreezeFile"));
327 keymaps.push_back(strpair_t("K00:C+F2", "LoadFreezeFile"));
328 keymaps.push_back(strpair_t("K00:F11", "LoadFreezeFile"));
329 keymaps.push_back(strpair_t("K00:A+F3", "SaveFreezeFile"));
330 keymaps.push_back(strpair_t("K00:C+F3", "SaveFreezeFile"));
331 keymaps.push_back(strpair_t("K00:F12", "SaveFreezeFile"));
332 keymaps.push_back(strpair_t("K00:F1", "QuickLoad000"));
333 keymaps.push_back(strpair_t("K00:F2", "QuickLoad001"));
334 keymaps.push_back(strpair_t("K00:F3", "QuickLoad002"));
335 keymaps.push_back(strpair_t("K00:F4", "QuickLoad003"));
336 keymaps.push_back(strpair_t("K00:F5", "QuickLoad004"));
337 keymaps.push_back(strpair_t("K00:F6", "QuickLoad005"));
338 keymaps.push_back(strpair_t("K00:F7", "QuickLoad006"));
339 keymaps.push_back(strpair_t("K00:F8", "QuickLoad007"));
340 keymaps.push_back(strpair_t("K00:F9", "QuickLoad008"));
341 keymaps.push_back(strpair_t("K00:S+F1", "QuickSave000"));
342 keymaps.push_back(strpair_t("K00:S+F2", "QuickSave001"));
343 keymaps.push_back(strpair_t("K00:S+F3", "QuickSave002"));
344 keymaps.push_back(strpair_t("K00:S+F4", "QuickSave003"));
345 keymaps.push_back(strpair_t("K00:S+F5", "QuickSave004"));
346 keymaps.push_back(strpair_t("K00:S+F6", "QuickSave005"));
347 keymaps.push_back(strpair_t("K00:S+F7", "QuickSave006"));
348 keymaps.push_back(strpair_t("K00:S+F8", "QuickSave007"));
349 keymaps.push_back(strpair_t("K00:S+F9", "QuickSave008"));
350
351 keymaps.push_back(strpair_t("K00:Scroll_Lock", "Pause"));
352 keymaps.push_back(strpair_t("K00:CS+Escape", "Reset"));
353 keymaps.push_back(strpair_t("K00:S+Escape", "SoftReset"));
354 keymaps.push_back(strpair_t("K00:Escape", "ExitEmu"));
355 keymaps.push_back(strpair_t("K00:Tab", "EmuTurbo"));
356 keymaps.push_back(strpair_t("K00:S+Tab", "ToggleEmuTurbo"));
357 keymaps.push_back(strpair_t("K00:A+equal", "IncEmuTurbo"));
358 keymaps.push_back(strpair_t("K00:A+minus", "DecEmuTurbo"));
359 keymaps.push_back(strpair_t("K00:C+equal", "IncTurboSpeed"));
360 keymaps.push_back(strpair_t("K00:C+minus", "DecTurboSpeed"));
361 keymaps.push_back(strpair_t("K00:equal", "IncFrameRate"));
362 keymaps.push_back(strpair_t("K00:minus", "DecFrameRate"));
363 keymaps.push_back(strpair_t("K00:S+equal", "IncFrameTime"));
364 keymaps.push_back(strpair_t("K00:S+minus", "DecFrameTime"));
365 keymaps.push_back(strpair_t("K00:6", "SwapJoypads"));
366 keymaps.push_back(strpair_t("K00:Print", "Screenshot"));
367
368 keymaps.push_back(strpair_t("K00:1", "ToggleBG0"));
369 keymaps.push_back(strpair_t("K00:2", "ToggleBG1"));
370 keymaps.push_back(strpair_t("K00:3", "ToggleBG2"));
371 keymaps.push_back(strpair_t("K00:4", "ToggleBG3"));
372 keymaps.push_back(strpair_t("K00:5", "ToggleSprites"));
373 keymaps.push_back(strpair_t("K00:9", "ToggleTransparency"));
374 keymaps.push_back(strpair_t("K00:BackSpace", "ClipWindows"));
375 keymaps.push_back(strpair_t("K00:A+Escape", "Debugger"));
376
377 keymaps.push_back(strpair_t("M00:B0", "{Mouse1 L,Superscope Fire,Justifier1 Trigger}"));
378 keymaps.push_back(strpair_t("M00:B1", "{Justifier1 AimOffscreen Trigger,Superscope AimOffscreen}"));
379 keymaps.push_back(strpair_t("M00:B2", "{Mouse1 R,Superscope Cursor,Justifier1 Start}"));
380 keymaps.push_back(strpair_t("M00:Pointer", "Pointer Mouse1+Superscope+Justifier1"));
381 keymaps.push_back(strpair_t("K00:grave", "Superscope ToggleTurbo"));
382 keymaps.push_back(strpair_t("K00:slash", "Superscope Pause"));
383
384 keymaps.push_back(strpair_t("K00:r", "Rewind"));
385 keymaps.push_back(strpair_t("K00:l", "Advance"));
386 }
387
388 GUI.no_repeat = !conf.GetBool("Unix/X11::SetKeyRepeat", TRUE);
389 GUI.fullscreen = conf.GetBool("Unix/X11::Fullscreen", FALSE);
390#ifdef USE_XVIDEO
391 GUI.use_xvideo = conf.GetBool("Unix/X11::Xvideo", FALSE);
392 GUI.maxaspect = conf.GetBool("Unix/X11::MaxAspect", FALSE);
393#endif
394#ifdef USE_XINERAMA
395 GUI.xinerama_head = conf.GetUInt("Unix/X11::XineramaHead", 0);
396#endif
397
398 if (conf.Exists("Unix/X11::VideoMode"))
399 {
400 GUI.video_mode = conf.GetUInt("Unix/X11::VideoMode", VIDEOMODE_BLOCKY);
401 if (GUI.video_mode < 1 || GUI.video_mode > 8)
402 GUI.video_mode = VIDEOMODE_BLOCKY;
403 }
404 else
405 GUI.video_mode = VIDEOMODE_BLOCKY;
406
407 return ("Unix/X11");
408}
409
410static void FatalError (const char *str)
411{
412 fprintf(stderr, "%s\n", str);
413 S9xExit();
414}
415
416static int ErrorHandler (Display *display, XErrorEvent *event)
417{
418#ifdef MITSHM
419 GUI.use_shared_memory = FALSE;
420#endif
421 return (0);
422}
423
424#ifdef USE_XVIDEO
425static int get_inv_shift (uint32 mask, int bpp)
426{
427 int i;
428
429 // Find mask
430 for (i = 0; (i < bpp) && !(mask & (1 << i)); i++) {};
431
432 // Find start of mask
433 for (; (i < bpp) && (mask & (1 << i)); i++) {};
434
435 return (bpp - i);
436}
437
438static unsigned char CLAMP (int v, int min, int max)
439{
440 if (v < min) return min;
441 if (v > max) return max;
442 return v;
443}
444
445static bool8 SetupXvideo()
446{
447 int ret;
448
449 // Init xv_port
450 GUI.xv_port = -1;
451
452 /////////////////////
453 // Check that Xvideo extension seems OK
454 unsigned int p_version, p_release, p_request_base, p_event_base, p_error_base;
455 ret = XvQueryExtension(GUI.display,
456 &p_version, &p_release, &p_request_base,
457 &p_event_base, &p_error_base);
458 if (ret != Success) { fprintf(stderr,"XvQueryExtension error\n"); return FALSE; }
459 printf("XvExtension version %i.%i\n",p_version,p_release);
460
461 /////////////////////
462 // Get info about the Adaptors available for this window
463 unsigned int p_num_adaptors;
464 XvAdaptorInfo* ai;
465 ret = XvQueryAdaptors(GUI.display, GUI.window, &p_num_adaptors, &ai);
466
467 if (ret != Success || p_num_adaptors == 0) {
468 fprintf(stderr,"XvQueryAdaptors error.");
469 return FALSE;
470 }
471 printf("XvQueryAdaptors: %d adaptor(s) found.\n",p_num_adaptors);
472
473 unsigned int minAdaptor = 0, maxAdaptor = p_num_adaptors;
474 // Allow user to force adaptor choice
475 /* if (adaptor >= 0 && adaptor < p_num_adaptors)
476 {
477 if (verbose) std::cout << "Forcing adaptor " << adaptor << ", '" << ai[adaptor].name << "'" << std::endl;
478 minAdaptor = adaptor;
479 maxAdaptor = adaptor + 1;
480 } */
481
482 /////////////////////
483 // Iterate through list of available adaptors.
484 // Grab a port if we can.
485 for (unsigned int i = minAdaptor; i < maxAdaptor && GUI.xv_port < 0; i++)
486 {
487 // We need to find one supporting XvInputMask and XvImageMask.
488 if (! (ai[i].type & XvImageMask)) continue;
489 if (! (ai[i].type & XvInputMask)) continue;
490
491 printf("\tAdaptor #%d: [%s]: %ld port(s) available.\n", i, ai[i].name, ai[i].num_ports);
492
493 // Get encodings available here
494 // AFAIK all ports on an adapter share the same encodings info.
495 unsigned int encodings;
496 XvEncodingInfo *ei;
497 ret = XvQueryEncodings(GUI.display, ai[i].base_id, &encodings, &ei);
498 if (ret != Success || encodings == 0) {
499 fprintf(stderr,"XvQueryEncodings error.");
500 continue;
501 }
502
503 // Ensure the XV_IMAGE encoding available has sufficient width/height for us.
504 bool8 can_fit = FALSE;
505 for (unsigned int j = 0; j < encodings; j++)
506 {
507 if (strcmp(ei[j].name,"XV_IMAGE")) continue;
508 if (ei[j].width >= SNES_WIDTH * 2 &&
509 ei[j].height >= SNES_HEIGHT_EXTENDED * 2)
510 {
511 can_fit = TRUE;
512 break;
513 }
514 }
515 XvFreeEncodingInfo(ei);
516
517 if (can_fit == FALSE)
518 {
519 fprintf(stderr,"\tDid not find XV_IMAGE encoding with enough max size\n");
520 continue;
521 }
522
523 // Phew. If we've made it this far, we can try to choose it for our output port.
524 for (unsigned int p = ai[i].base_id; p < ai[i].base_id+ai[i].num_ports; p++)
525 {
526 ret = XvGrabPort(GUI.display, p, CurrentTime);
527 if (ret == Success)
528 {
529 printf("\tSuccessfully bound to Xv port %d\n",p);
530 GUI.xv_port = p;
531 break;
532 } else {
533 fprintf(stderr,"\tXvGrabPort port %d fail.\n",p);
534 }
535 }
536 }
537 XvFreeAdaptorInfo(ai);
538
539 /////////////////////
540 // Bail out here if we haven't managed to bind to any port.
541 if (GUI.xv_port < 0)
542 {
543 fprintf(stderr,"No suitable xv_port found in any Adaptors.\n");
544 return FALSE;
545 }
546
547 // Xv ports can have Attributes (hue, saturation, etc)
548 /* Set XV_AUTOPAINT_COLORKEY _only_ if available */
549 int num_attrs;
550 XvAttribute* port_attr;
551 port_attr = XvQueryPortAttributes (GUI.display, GUI.xv_port, &num_attrs);
552
553 for (int i = 0; i < num_attrs; i++)
554 {
555 if (!strcmp (port_attr[i].name, "XV_AUTOPAINT_COLORKEY"))
556 {
557 Atom colorkey;
558
559 colorkey = XInternAtom (GUI.display, "XV_AUTOPAINT_COLORKEY", True);
560 if (colorkey != None)
561 {
562 XvSetPortAttribute (GUI.display, GUI.xv_port, colorkey, 1);
563 printf("\tSet XV_AUTOPAINT_COLORKEY.\n");
564 }
565 }
566 }
567 XFree(port_attr);
568
569 // Now we need to find to find the image format to use for output.
570 // There are two steps to this:
571 // Prefer an XvRGB version of lowest bitdepth.
572 // If that's not available use YUY2
573 int formats;
574 XvImageFormatValues* fo;
575 fo = XvListImageFormats(GUI.display, GUI.xv_port, &formats);
576 if (formats == 0)
577 {
578 fprintf(stderr,"No valid image formats for Xv port!");
579 return FALSE;
580 }
581
582 /* Ok time to search for a good Format */
583 GUI.xv_format = FOURCC_YUY2;
584 GUI.xv_bpp = 0x7FFFFFFF;
585
586 for (int i = 0; i < formats; i++)
587 {
588 if (fo[i].id == 0x3 || fo[i].type == XvRGB)
589 {
590 if (fo[i].bits_per_pixel < GUI.xv_bpp)
591 {
592 GUI.xv_format = fo[i].id;
593 GUI.xv_bpp = fo[i].bits_per_pixel;
594 GUI.bytes_per_pixel = (GUI.xv_bpp == 15) ? 2 : GUI.xv_bpp >> 3;
595 GUI.depth = fo[i].depth;
596
597 GUI.red_shift = get_inv_shift (fo[i].red_mask, GUI.xv_bpp);
598 GUI.green_shift = get_inv_shift (fo[i].green_mask, GUI.xv_bpp);
599 GUI.blue_shift = get_inv_shift (fo[i].blue_mask, GUI.xv_bpp);
600
601 /* Check for red-blue inversion on SiliconMotion drivers */
602 if (fo[i].red_mask == 0x001f &&
603 fo[i].blue_mask == 0x7c00)
604 {
605 int copy = GUI.red_shift;
606 GUI.red_shift = GUI.blue_shift;
607 GUI.blue_shift = copy;
608 }
609
610 /* on big-endian Xv still seems to like LSB order */
611 /*if (config->force_inverted_byte_order)
612 S9xSetEndianess (ENDIAN_MSB);
613 else
614 S9xSetEndianess (ENDIAN_LSB); */
615 }
616 }
617 }
618 free (fo);
619
620 if (GUI.xv_format != FOURCC_YUY2)
621 {
622 printf("Selected XvRGB format: %d bpp\n",GUI.xv_bpp);
623 } else {
624 // use YUY2
625 printf("Fallback to YUY2 format.\n");
626 GUI.depth = 15;
627
628 /* Build a table for yuv conversion */
629 for (unsigned int color = 0; color < (1 << 15); color++)
630 {
631 int r, g, b;
632 int y, u, v;
633
634 r = (color & 0x7c00) >> 7;
635 g = (color & 0x03e0) >> 2;
636 b = (color & 0x001F) << 3;
637
638 y = (int) ((0.257 * ((double) r)) + (0.504 * ((double) g)) + (0.098 * ((double) b)) + 16.0);
639 u = (int) ((-0.148 * ((double) r)) + (-0.291 * ((double) g)) + (0.439 * ((double) b)) + 128.0);
640 v = (int) ((0.439 * ((double) r)) + (-0.368 * ((double) g)) + (-0.071 * ((double) b)) + 128.0);
641
642 GUI.y_table[color] = CLAMP (y, 0, 255);
643 GUI.u_table[color] = CLAMP (u, 0, 255);
644 GUI.v_table[color] = CLAMP (v, 0, 255);
645 }
646 }
647
648 return TRUE;
649}
650#endif
651
652void S9xInitDisplay (int argc, char **argv)
653{
654 GUI.display = XOpenDisplay(NULL);
655 if (GUI.display == NULL)
656 FatalError("Failed to connect to X server.");
657
658 GUI.screen = DefaultScreenOfDisplay(GUI.display);
659 GUI.screen_num = XScreenNumberOfScreen(GUI.screen);
660 GUI.visual = DefaultVisualOfScreen(GUI.screen);
661
662 XVisualInfo plate, *matches;
663 int count;
664
665 plate.visualid = XVisualIDFromVisual(GUI.visual);
666 matches = XGetVisualInfo(GUI.display, VisualIDMask, &plate, &count);
667 if (!count)
668 FatalError("Your X Window System server is unwell!");
669
670 GUI.depth = matches[0].depth;
671 if ((GUI.depth != 15 && GUI.depth != 16 && GUI.depth != 24) || (matches[0].c_class != TrueColor))
672 FatalError("Requiers 15, 16, 24 or 32-bit color depth supporting TrueColor.");
673
674 GUI.red_shift = ffs(matches[0].red_mask) - 1;
675 GUI.green_shift = ffs(matches[0].green_mask) - 1;
676 GUI.blue_shift = ffs(matches[0].blue_mask) - 1;
677 GUI.red_size = matches[0].red_mask >> GUI.red_shift;
678 GUI.green_size = matches[0].green_mask >> GUI.green_shift;
679 GUI.blue_size = matches[0].blue_mask >> GUI.blue_shift;
680 if (GUI.depth == 16 && GUI.green_size == 63)
681 GUI.green_shift++;
682
683 XFree(matches);
684
685 // Init various scale-filters
686 S9xBlitFilterInit();
687 S9xBlit2xSaIFilterInit();
688 S9xBlitHQ2xFilterInit();
689
690 /* Set up parameters for creating the window */
691 XSetWindowAttributes attrib;
692
693 memset(&attrib, 0, sizeof(attrib));
694 attrib.background_pixel = BlackPixelOfScreen(GUI.screen);
695 attrib.colormap = XCreateColormap(GUI.display, RootWindowOfScreen(GUI.screen), GUI.visual, AllocNone);
696
697 int screen_left = 0, screen_top = 0;
698 int screen_w = WidthOfScreen(GUI.screen), screen_h = HeightOfScreen(GUI.screen);
699
700#ifdef USE_XINERAMA
701 int heads = 0;
702 XineramaScreenInfo* si = 0;
703
704 int useless1, useless2;
705 if (!XineramaQueryExtension(GUI.display, &useless1, &useless2)) {
706 puts("Xinerama is not available");
707 goto xinerama_end;
708 }
709
710 if (!XineramaIsActive(GUI.display)) {
711 puts("Xinerama is not active");
712 goto xinerama_end;
713 }
714
715 si = XineramaQueryScreens(GUI.display, &heads);
716 if (!si) {
717 puts("XineramaQueryScreens failed");
718 goto xinerama_end;
719 }
720
721 if (GUI.xinerama_head >= heads) {
722 printf("Invalid xinerama head id (expected 0-%d, got %u)\n", heads - 1, GUI.xinerama_head);
723 goto xinerama_end;
724 }
725
726 si = &si[GUI.xinerama_head];
727 screen_left = si->x_org;
728 screen_top = si->y_org;
729 screen_w = si->width;
730 screen_h = si->height;
731
732 printf("Selected xinerama head %u (%d,%d %dx%d)\n", GUI.xinerama_head, screen_left, screen_top, screen_w, screen_h);
733
734xinerama_end:
735#endif
736
737 XSizeHints Hints;
738 memset((void *) &Hints, 0, sizeof(XSizeHints));
739
740 /* Try to switch to Fullscreen. */
741 if (GUI.fullscreen == TRUE)
742 {
743 Hints.flags = PPosition;
744 Hints.x = screen_left;
745 Hints.y = screen_top;
746
747 /* Create the window with maximum screen width,height positioned at 0,0. */
748 GUI.window = XCreateWindow(GUI.display, RootWindowOfScreen(GUI.screen),
749 Hints.x, Hints.y,
750 screen_w, screen_h, 0,
751 GUI.depth, InputOutput, GUI.visual, CWBackPixel | CWColormap, &attrib);
752
753 /* Try to tell the Window Manager not to decorate this window. */
754 Atom wm_state = XInternAtom (GUI.display, "_NET_WM_STATE", true );
755 Atom wm_fullscreen = XInternAtom (GUI.display, "_NET_WM_STATE_FULLSCREEN", true );
756
757 XChangeProperty(GUI.display, GUI.window, wm_state, XA_ATOM, 32, PropModeReplace, (unsigned char *)&wm_fullscreen, 1);
758
759#ifdef USE_XVIDEO
760 if (GUI.use_xvideo)
761 {
762 // Set some defaults
763 GUI.scale_w = screen_w;
764 GUI.scale_h = screen_h;
765
766 GUI.imageHeight = SNES_HEIGHT_EXTENDED * 2;
767
768 if (! GUI.maxaspect)
769 {
770 // Compute the maximum screen size for scaling xvideo window.
771 double screenAspect = (double)screen_w / screen_h;
772 double snesAspect = (double)SNES_WIDTH / SNES_HEIGHT_EXTENDED;
773 double ratio = screenAspect / snesAspect;
774
775 printf("\tScreen (%dx%d) aspect %f vs SNES (%dx%d) aspect %f (ratio: %f)\n",
776 screen_w,screen_h,screenAspect,
777 SNES_WIDTH,SNES_HEIGHT_EXTENDED,snesAspect,
778 ratio);
779
780 // Correct aspect ratio
781 if (screenAspect > snesAspect)
782 {
783 // widescreen monitor, 4:3 snes
784 // match height, scale width
785 GUI.scale_w /= ratio;
786 GUI.x_offset = (screen_w - GUI.scale_w) / 2;
787 } else {
788 // narrow monitor, 4:3 snes
789 // match width, scale height
790 GUI.scale_h *= ratio;
791 GUI.y_offset = (screen_h - GUI.scale_h) / 2;
792 }
793 }
794
795 printf("\tUsing size %dx%d with offset (%d,%d)\n",GUI.scale_w,GUI.scale_h,GUI.x_offset,GUI.y_offset);
796 }
797 else
798#endif
799 {
800 /* Last: position the output window in the center of the screen. */
801 GUI.x_offset = (screen_w - SNES_WIDTH * 2) / 2;
802 GUI.y_offset = (screen_h - SNES_HEIGHT_EXTENDED * 2) / 2;
803 }
804 } else {
805 /* Tell the Window Manager that we do not wish to be resizable */
806 Hints.flags = PSize | PMinSize | PMaxSize | PPosition;
807 Hints.x = screen_left + (screen_w - SNES_WIDTH * 2) / 2;
808 Hints.y = screen_top + (screen_h - SNES_HEIGHT_EXTENDED * 2) / 2;
809 Hints.min_width = Hints.max_width = Hints.base_width = SNES_WIDTH * 2;
810 Hints.min_height = Hints.max_height = Hints.base_height = SNES_HEIGHT_EXTENDED * 2;
811
812 /* Create the window. */
813 GUI.window = XCreateWindow(GUI.display, RootWindowOfScreen(GUI.screen),
814 Hints.x, Hints.y,
815 SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, 0, GUI.depth, InputOutput, GUI.visual, CWBackPixel | CWColormap, &attrib);
816
817 /* Last: Windowed SNES is not drawn with any offsets. */
818 GUI.x_offset = GUI.y_offset = 0;
819#ifdef USE_XVIDEO
820 GUI.scale_w = SNES_WIDTH * 2;
821 GUI.scale_h = SNES_HEIGHT_EXTENDED * 2;
822#endif
823 }
824
825 XSetWMNormalHints(GUI.display, GUI.window, &Hints);
826
827 /* Load UI cursors */
828 static XColor bg, fg;
829 static char data[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
830 Pixmap bitmap;
831
832 bitmap = XCreateBitmapFromData(GUI.display, GUI.window, data, 8, 8);
833 GUI.point_cursor = XCreatePixmapCursor(GUI.display, bitmap, bitmap, &fg, &bg, 0, 0);
834 XDefineCursor(GUI.display, GUI.window, GUI.point_cursor);
835 GUI.cross_hair_cursor = XCreateFontCursor(GUI.display, XC_crosshair);
836
837 GUI.gc = DefaultGCOfScreen(GUI.screen);
838
839 /* Other window-manager hints */
840 XWMHints WMHints;
841
842 memset((void *) &WMHints, 0, sizeof(XWMHints));
843
844 /* Rely on the Window Manager to provide us with keyboard input */
845 WMHints.input = True;
846 WMHints.flags = InputHint;
847
848 XSetWMHints(GUI.display, GUI.window, &WMHints);
849 XSelectInput(GUI.display, GUI.window, FocusChangeMask | ExposureMask | KeyPressMask | KeyReleaseMask | StructureNotifyMask | ButtonPressMask | ButtonReleaseMask);
850
851 /* Bring up our window (and put it in foreground) */
852 XMapRaised(GUI.display, GUI.window);
853 XClearWindow(GUI.display, GUI.window);
854
855 // Wait for map
856 XEvent event;
857 do {
858 XNextEvent(GUI.display, &event);
859 } while (event.type != MapNotify || event.xmap.event != GUI.window);
860
861#ifdef USE_XVIDEO
862 if (GUI.use_xvideo)
863 {
864 GUI.use_xvideo = SetupXvideo();
865 }
866#endif
867
868 GUI.pixel_format = 565;
869
870 SetupImage();
871
872 switch (GUI.depth)
873 {
874 default:
875 case 32:
876 GUI.bytes_per_pixel = 4;
877 break;
878
879 case 24:
880 if (GUI.image->bits_per_pixel == 24)
881 GUI.bytes_per_pixel = 3;
882 else
883 GUI.bytes_per_pixel = 4;
884 break;
885
886 case 15:
887 case 16:
888 GUI.bytes_per_pixel = 2;
889 break;
890 }
891
892 printf("Using internal pixel format %d\n",GUI.pixel_format);
893}
894
895void S9xDeinitDisplay (void)
896{
897 TakedownImage();
898 if (GUI.display != NULL)
899 {
900#ifdef USE_XVIDEO
901 if (GUI.use_xvideo)
902 {
903 XvUngrabPort(GUI.display,GUI.xv_port,CurrentTime);
904 }
905#endif
906 S9xTextMode();
907 XSync(GUI.display, False);
908 XCloseDisplay(GUI.display);
909 }
910 S9xBlitFilterDeinit();
911 S9xBlit2xSaIFilterDeinit();
912 S9xBlitHQ2xFilterDeinit();
913}
914
915static void SetupImage (void)
916{
917 TakedownImage();
918
919 // Create new image struct
920 GUI.image = (Image *) calloc(sizeof(Image), 1);
921
922#ifdef USE_XVIDEO
923 if (GUI.use_xvideo)
924 SetupXvImage();
925 if (!GUI.use_xvideo)
926#endif
927 SetupXImage();
928
929 // Setup SNES buffers
930 GFX.Pitch = SNES_WIDTH * 2 * 2;
931 GUI.snes_buffer = (uint8 *) calloc(GFX.Pitch * ((SNES_HEIGHT_EXTENDED + 4) * 2), 1);
932 if (!GUI.snes_buffer)
933 FatalError("Failed to allocate GUI.snes_buffer.");
934
935 GFX.Screen = (uint16 *) (GUI.snes_buffer + (GFX.Pitch * 2 * 2));
936
937 GUI.filter_buffer = (uint8 *) calloc((SNES_WIDTH * 2) * 2 * (SNES_HEIGHT_EXTENDED * 2), 1);
938 if (!GUI.filter_buffer)
939 FatalError("Failed to allocate GUI.filter_buffer.");
940
941#ifdef USE_XVIDEO
942 if ((GUI.depth == 15 || GUI.depth == 16) && GUI.xv_format != FOURCC_YUY2)
943#else
944 if (GUI.depth == 15 || GUI.depth == 16)
945#endif
946 {
947 GUI.blit_screen_pitch = GUI.image->bytes_per_line;
948 GUI.blit_screen = (uint8 *) GUI.image->data;
949 GUI.need_convert = FALSE;
950 }
951 else
952 {
953 GUI.blit_screen_pitch = (SNES_WIDTH * 2) * 2;
954 GUI.blit_screen = GUI.filter_buffer;
955 GUI.need_convert = TRUE;
956 }
957 if (GUI.need_convert) { printf("\tImage conversion needed before blit.\n"); }
958
959 S9xGraphicsInit();
960}
961
962static void TakedownImage (void)
963{
964 if (GUI.snes_buffer)
965 {
966 free(GUI.snes_buffer);
967 GUI.snes_buffer = NULL;
968 }
969
970 if (GUI.filter_buffer)
971 {
972 free(GUI.filter_buffer);
973 GUI.filter_buffer = NULL;
974 }
975
976 if (GUI.image)
977 {
978#ifdef USE_XVIDEO
979 if (GUI.use_xvideo)
980 TakedownXvImage();
981 else
982#endif
983 TakedownXImage();
984
985 free(GUI.image);
986 GUI.image = NULL;
987 }
988
989 S9xGraphicsDeinit();
990}
991
992static void SetupXImage (void)
993{
994#ifdef MITSHM
995 GUI.use_shared_memory = TRUE;
996
997 int major, minor;
998 Bool shared;
999
1000 if (!XShmQueryVersion(GUI.display, &major, &minor, &shared) || !shared)
1001 GUI.image->ximage = NULL;
1002 else
1003 GUI.image->ximage = XShmCreateImage(GUI.display, GUI.visual, GUI.depth, ZPixmap, NULL, &GUI.sm_info, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);
1004
1005 if (!GUI.image->ximage)
1006 GUI.use_shared_memory = FALSE;
1007 else
1008 {
1009 // set main Image struct vars
1010 GUI.image->height = GUI.image->ximage->height;
1011 GUI.image->bytes_per_line = GUI.image->ximage->bytes_per_line;
1012 GUI.image->data_size = GUI.image->bytes_per_line * GUI.image->height;
1013
1014 GUI.sm_info.shmid = shmget(IPC_PRIVATE, GUI.image->data_size, IPC_CREAT | 0777);
1015 if (GUI.sm_info.shmid < 0)
1016 {
1017 XDestroyImage(GUI.image->ximage);
1018 GUI.use_shared_memory = FALSE;
1019 }
1020 else
1021 {
1022 GUI.image->ximage->data = GUI.sm_info.shmaddr = (char *) shmat(GUI.sm_info.shmid, 0, 0);
1023 if (!GUI.image->ximage->data)
1024 {
1025 XDestroyImage(GUI.image->ximage);
1026 shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
1027 GUI.use_shared_memory = FALSE;
1028 }
1029 else
1030 {
1031 GUI.sm_info.readOnly = False;
1032
1033 XSetErrorHandler(ErrorHandler);
1034 XShmAttach(GUI.display, &GUI.sm_info);
1035 XSync(GUI.display, False);
1036
1037 // X Error handler might clear GUI.use_shared_memory if XShmAttach failed.
1038 if (!GUI.use_shared_memory)
1039 {
1040 XDestroyImage(GUI.image->ximage);
1041 shmdt(GUI.sm_info.shmaddr);
1042 shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
1043 } else
1044 printf("Created XShmImage, size %d\n",GUI.image->data_size);
1045 }
1046 }
1047 }
1048
1049 if (!GUI.use_shared_memory)
1050 {
1051 fprintf(stderr, "use_shared_memory failed, switching to XPutImage.\n");
1052#endif
1053 GUI.image->ximage = XCreateImage(GUI.display, GUI.visual, GUI.depth, ZPixmap, 0, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, BitmapUnit(GUI.display), 0);
1054 // set main Image struct vars
1055 GUI.image->height = GUI.image->ximage->height;
1056 GUI.image->bytes_per_line = GUI.image->ximage->bytes_per_line;
1057 GUI.image->data_size = GUI.image->bytes_per_line * GUI.image->height;
1058
1059 GUI.image->ximage->data = (char *) malloc(GUI.image->data_size);
1060 if (!GUI.image->ximage || !GUI.image->ximage->data)
1061 FatalError("XCreateImage failed.");
1062 printf("Created XImage, size %d\n",GUI.image->data_size);
1063#ifdef MITSHM
1064 }
1065#endif
1066
1067 // Set final values
1068 GUI.image->bits_per_pixel = GUI.image->ximage->bits_per_pixel;
1069 GUI.image->data = GUI.image->ximage->data;
1070
1071#ifdef LSB_FIRST
1072 GUI.image->ximage->byte_order = LSBFirst;
1073#else
1074 GUI.image->ximage->byte_order = MSBFirst;
1075#endif
1076}
1077
1078static void TakedownXImage (void)
1079{
1080 if (GUI.image->ximage)
1081 {
1082 #ifdef MITSHM
1083 if (GUI.use_shared_memory)
1084 {
1085 XShmDetach(GUI.display, &GUI.sm_info);
1086 GUI.image->ximage->data = NULL;
1087 XDestroyImage(GUI.image->ximage);
1088 if (GUI.sm_info.shmaddr)
1089 shmdt(GUI.sm_info.shmaddr);
1090 if (GUI.sm_info.shmid >= 0)
1091 shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
1092 GUI.image->ximage = NULL;
1093 }
1094 else
1095 #endif
1096 {
1097 XDestroyImage(GUI.image->ximage);
1098 GUI.image->ximage = NULL;
1099 }
1100 }
1101}
1102
1103#ifdef USE_XVIDEO
1104static void SetupXvImage (void)
1105{
1106#ifdef MITSHM
1107 GUI.use_shared_memory = TRUE;
1108
1109 int major, minor;
1110 Bool shared;
1111
1112 if (!XShmQueryVersion(GUI.display, &major, &minor, &shared) || !shared)
1113 GUI.image->xvimage = NULL;
1114 else
1115 GUI.image->xvimage = XvShmCreateImage(GUI.display, GUI.xv_port, GUI.xv_format, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, &GUI.sm_info);
1116
1117 if (!GUI.image->xvimage)
1118 GUI.use_shared_memory = FALSE;
1119 else
1120 {
1121 GUI.image->height = SNES_HEIGHT_EXTENDED * 2;
1122 GUI.image->data_size = GUI.image->xvimage->data_size;
1123 GUI.image->bytes_per_line = GUI.image->data_size / GUI.image->height;
1124 GUI.sm_info.shmid = shmget(IPC_PRIVATE, GUI.image->data_size, IPC_CREAT | 0777);
1125 if (GUI.sm_info.shmid < 0)
1126 {
1127 XFree(GUI.image->xvimage);
1128 GUI.use_shared_memory = FALSE;
1129 }
1130 else
1131 {
1132 GUI.image->xvimage->data = GUI.sm_info.shmaddr = (char *) shmat(GUI.sm_info.shmid, 0, 0);
1133 if (!GUI.image->xvimage->data)
1134 {
1135 XFree(GUI.image->xvimage);
1136 shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
1137 GUI.use_shared_memory = FALSE;
1138 }
1139 else
1140 {
1141 GUI.sm_info.readOnly = False;
1142
1143 XSetErrorHandler(ErrorHandler);
1144 XShmAttach(GUI.display, &GUI.sm_info);
1145 XSync(GUI.display, False);
1146
1147 // X Error handler might clear GUI.use_shared_memory if XShmAttach failed.
1148 if (!GUI.use_shared_memory)
1149 {
1150 XFree(GUI.image->xvimage);
1151 shmdt(GUI.sm_info.shmaddr);
1152 shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
1153 } else
1154 printf("Created XvShmImage, size %d\n",GUI.image->data_size);
1155 }
1156 }
1157 }
1158
1159 if (!GUI.use_shared_memory)
1160 {
1161 fprintf(stderr, "use_shared_memory failed, switching to XvPutImage.\n");
1162#endif
1163 GUI.image->xvimage = XvCreateImage(GUI.display, GUI.xv_port, GUI.xv_format, NULL, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);
1164 GUI.image->height = SNES_HEIGHT_EXTENDED * 2;
1165 GUI.image->data_size = GUI.image->xvimage->data_size;
1166 GUI.image->bytes_per_line = GUI.image->data_size / GUI.image->height;
1167
1168 GUI.image->xvimage->data = (char *) malloc(GUI.image->data_size);
1169 if (!GUI.image->xvimage || !GUI.image->xvimage->data)
1170 {
1171 fprintf(stderr, "XvCreateImage failed, falling back to software blit.\n");
1172 GUI.use_xvideo = FALSE;
1173 return;
1174 }
1175 printf("Created XvImage, size %d\n",GUI.image->data_size);
1176#ifdef MITSHM
1177 }
1178#endif
1179 // Set final values
1180 GUI.image->bits_per_pixel = GUI.xv_bpp;
1181 GUI.image->data = GUI.image->xvimage->data;
1182}
1183
1184static void TakedownXvImage (void)
1185{
1186 if (GUI.image->xvimage)
1187 {
1188 #ifdef MITSHM
1189 if (GUI.use_shared_memory)
1190 {
1191 XShmDetach(GUI.display, &GUI.sm_info);
1192 GUI.image->xvimage->data = NULL;
1193 XFree(GUI.image->xvimage);
1194 if (GUI.sm_info.shmaddr)
1195 shmdt(GUI.sm_info.shmaddr);
1196 if (GUI.sm_info.shmid >= 0)
1197 shmctl(GUI.sm_info.shmid, IPC_RMID, 0);
1198 GUI.image->xvimage = NULL;
1199 }
1200 else
1201 #endif
1202 {
1203 free(GUI.image->xvimage->data);
1204 //GUI.image->xvimage->data = NULL;
1205 XFree(GUI.image->xvimage);
1206 GUI.image->xvimage = NULL;
1207 }
1208 }
1209}
1210#endif
1211
1212void S9xPutImage (int width, int height)
1213{
1214 static int prevWidth = 0, prevHeight = 0;
1215 int copyWidth, copyHeight;
1216 Blitter blitFn = NULL;
1217
1218 if (GUI.video_mode == VIDEOMODE_BLOCKY || GUI.video_mode == VIDEOMODE_TV || GUI.video_mode == VIDEOMODE_SMOOTH)
1219 if ((width <= SNES_WIDTH) && ((prevWidth != width) || (prevHeight != height)))
1220 S9xBlitClearDelta();
1221
1222 if (width <= SNES_WIDTH)
1223 {
1224 if (height > SNES_HEIGHT_EXTENDED)
1225 {
1226 copyWidth = width * 2;
1227 copyHeight = height;
1228 blitFn = S9xBlitPixSimple2x1;
1229 }
1230 else
1231 {
1232 copyWidth = width * 2;
1233 copyHeight = height * 2;
1234
1235 switch (GUI.video_mode)
1236 {
1237 case VIDEOMODE_BLOCKY: blitFn = S9xBlitPixSimple2x2; break;
1238 case VIDEOMODE_TV: blitFn = S9xBlitPixTV2x2; break;
1239 case VIDEOMODE_SMOOTH: blitFn = S9xBlitPixSmooth2x2; break;
1240 case VIDEOMODE_SUPEREAGLE: blitFn = S9xBlitPixSuperEagle16; break;
1241 case VIDEOMODE_2XSAI: blitFn = S9xBlitPix2xSaI16; break;
1242 case VIDEOMODE_SUPER2XSAI: blitFn = S9xBlitPixSuper2xSaI16; break;
1243 case VIDEOMODE_EPX: blitFn = S9xBlitPixEPX16; break;
1244 case VIDEOMODE_HQ2X: blitFn = S9xBlitPixHQ2x16; break;
1245 }
1246 }
1247 }
1248 else
1249 if (height <= SNES_HEIGHT_EXTENDED)
1250 {
1251 copyWidth = width;
1252 copyHeight = height * 2;
1253
1254 switch (GUI.video_mode)
1255 {
1256 default: blitFn = S9xBlitPixSimple1x2; break;
1257 case VIDEOMODE_TV: blitFn = S9xBlitPixTV1x2; break;
1258 }
1259 }
1260 else
1261 {
1262 copyWidth = width;
1263 copyHeight = height;
1264 blitFn = S9xBlitPixSimple1x1;
1265 }
1266 blitFn((uint8 *) GFX.Screen, GFX.Pitch, GUI.blit_screen, GUI.blit_screen_pitch, width, height);
1267
1268 if (height < prevHeight)
1269 {
1270 int p = GUI.blit_screen_pitch >> 2;
1271 for (int y = SNES_HEIGHT * 2; y < SNES_HEIGHT_EXTENDED * 2; y++)
1272 {
1273 uint32 *d = (uint32 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
1274 for (int x = 0; x < p; x++)
1275 *d++ = 0;
1276 }
1277 }
1278
1279#ifdef USE_XVIDEO
1280 // Adjust source blit region if SNES would only fill half the screen.
1281 if (height <= SNES_HEIGHT_EXTENDED)
1282 GUI.imageHeight = height * 2;
1283
1284 if (GUI.use_xvideo && (GUI.xv_format == FOURCC_YUY2))
1285 {
1286 uint16 *s = (uint16 *)GUI.blit_screen;
1287 uint8 *d = (uint8 *)GUI.image->data;
1288
1289 // convert GUI.blit_screen and copy to XV image
1290 for (int y = 0; y < copyHeight; y++)
1291 {
1292 for (int x = 0; x < SNES_WIDTH * 2; x += 2)
1293 {
1294 // Read two RGB pxls
1295 // Now that we are RGB565 throughout, need to drop the Green LSB
1296 unsigned short rgb1 = (*s & 0xFFC0) >> 1 | (*s & 0x001F); s++;
1297 unsigned short rgb2 = (*s & 0xFFC0) >> 1 | (*s & 0x001F); s++;
1298
1299 // put two YUYV pxls
1300 // lum1
1301 *d = GUI.y_table[rgb1]; d++;
1302 // U
1303 *d = (GUI.u_table[rgb1] + GUI.u_table[rgb2]) / 2; d++;
1304 // lum2
1305 *d = GUI.y_table[rgb2]; d++;
1306 // V
1307 *d = (GUI.v_table[rgb1] + GUI.v_table[rgb2]) / 2; d++;
1308 }
1309 }
1310 }
1311 else
1312#endif
1313 if (GUI.need_convert)
1314 {
1315 if (GUI.bytes_per_pixel == 3)
1316 Convert16To24Packed(copyWidth, copyHeight);
1317 else
1318 Convert16To24(copyWidth, copyHeight);
1319 }
1320
1321 Repaint(TRUE);
1322
1323 prevWidth = width;
1324 prevHeight = height;
1325}
1326
1327static void Convert16To24 (int width, int height)
1328{
1329 if (GUI.pixel_format == 565)
1330 {
1331 for (int y = 0; y < height; y++)
1332 {
1333 uint16 *s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
1334 uint32 *d = (uint32 *) (GUI.image->data + y * GUI.image->bytes_per_line);
1335
1336 for (int x = 0; x < width; x++)
1337 {
1338 uint32 pixel = *s++;
1339 *d++ = (((pixel >> 11) & 0x1f) << (GUI.red_shift + 3)) | (((pixel >> 6) & 0x1f) << (GUI.green_shift + 3)) | ((pixel & 0x1f) << (GUI.blue_shift + 3));
1340 }
1341 }
1342 }
1343 else
1344 {
1345 for (int y = 0; y < height; y++)
1346 {
1347 uint16 *s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
1348 uint32 *d = (uint32 *) (GUI.image->data + y * GUI.image->bytes_per_line);
1349
1350 for (int x = 0; x < width; x++)
1351 {
1352 uint32 pixel = *s++;
1353 *d++ = (((pixel >> 10) & 0x1f) << (GUI.red_shift + 3)) | (((pixel >> 5) & 0x1f) << (GUI.green_shift + 3)) | ((pixel & 0x1f) << (GUI.blue_shift + 3));
1354 }
1355 }
1356 }
1357}
1358
1359static void Convert16To24Packed (int width, int height)
1360{
1361 if (GUI.pixel_format == 565)
1362 {
1363 for (int y = 0; y < height; y++)
1364 {
1365 uint16 *s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
1366 uint8 *d = (uint8 *) (GUI.image->data + y * GUI.image->bytes_per_line);
1367
1368 #ifdef LSB_FIRST
1369 if (GUI.red_shift < GUI.blue_shift)
1370 #else
1371 if (GUI.red_shift > GUI.blue_shift)
1372 #endif
1373 {
1374 for (int x = 0; x < width; x++)
1375 {
1376 uint32 pixel = *s++;
1377 *d++ = (pixel >> (11 - 3)) & 0xf8;
1378 *d++ = (pixel >> (6 - 3)) & 0xf8;
1379 *d++ = (pixel & 0x1f) << 3;
1380 }
1381 }
1382 else
1383 {
1384 for (int x = 0; x < width; x++)
1385 {
1386 uint32 pixel = *s++;
1387 *d++ = (pixel & 0x1f) << 3;
1388 *d++ = (pixel >> (6 - 3)) & 0xf8;
1389 *d++ = (pixel >> (11 - 3)) & 0xf8;
1390 }
1391 }
1392 }
1393 }
1394 else
1395 {
1396 for (int y = 0; y < height; y++)
1397 {
1398 uint16 *s = (uint16 *) (GUI.blit_screen + y * GUI.blit_screen_pitch);
1399 uint8 *d = (uint8 *) (GUI.image->data + y * GUI.image->bytes_per_line);
1400
1401 #ifdef LSB_FIRST
1402 if (GUI.red_shift < GUI.blue_shift)
1403 #else
1404 if (GUI.red_shift > GUI.blue_shift)
1405 #endif
1406 {
1407 for (int x = 0; x < width; x++)
1408 {
1409 uint32 pixel = *s++;
1410 *d++ = (pixel >> (10 - 3)) & 0xf8;
1411 *d++ = (pixel >> (5 - 3)) & 0xf8;
1412 *d++ = (pixel & 0x1f) << 3;
1413 }
1414 }
1415 else
1416 {
1417 for (int x = 0; x < width; x++)
1418 {
1419 uint32 pixel = *s++;
1420 *d++ = (pixel & 0x1f) << 3;
1421 *d++ = (pixel >> (5 - 3)) & 0xf8;
1422 *d++ = (pixel >> (10 - 3)) & 0xf8;
1423 }
1424 }
1425 }
1426 }
1427}
1428
1429static void Repaint (bool8 isFrameBoundry)
1430{
1431#ifdef USE_XVIDEO
1432 if (GUI.use_xvideo)
1433 {
1434#ifdef MITSHM
1435 if (GUI.use_shared_memory)
1436 {
1437 XvShmPutImage(GUI.display, GUI.xv_port, GUI.window, GUI.gc, GUI.image->xvimage,
1438 0, 0, SNES_WIDTH * 2, GUI.imageHeight,
1439 GUI.x_offset, GUI.y_offset, GUI.scale_w, GUI.scale_h, False);
1440 }
1441 else
1442#endif
1443 XvPutImage(GUI.display, GUI.xv_port, GUI.window, GUI.gc, GUI.image->xvimage,
1444 0, 0, SNES_WIDTH * 2, GUI.imageHeight,
1445 GUI.x_offset, GUI.y_offset, GUI.scale_w, GUI.scale_h);
1446 }
1447 else
1448#endif
1449#ifdef MITSHM
1450 if (GUI.use_shared_memory)
1451 {
1452 XShmPutImage(GUI.display, GUI.window, GUI.gc, GUI.image->ximage, 0, 0, GUI.x_offset, GUI.y_offset, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, False);
1453 XSync(GUI.display, False); // Is this double-sync? See XQueryPointer below...
1454 }
1455 else
1456#endif
1457 XPutImage(GUI.display, GUI.window, GUI.gc, GUI.image->ximage, 0, 0, GUI.x_offset, GUI.y_offset, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2);
1458
1459 Window root, child;
1460 int root_x, root_y, x, y;
1461 unsigned int mask;
1462
1463 // Use QueryPointer to sync X server and as a side effect also gets current pointer position for SNES mouse emulation.
1464 XQueryPointer(GUI.display, GUI.window, &root, &child, &root_x, &root_y, &x, &y, &mask);
1465
1466 if (x >= 0 && y >= 0 && x < SNES_WIDTH * 2 && y < SNES_HEIGHT_EXTENDED * 2)
1467 {
1468 GUI.mouse_x = x >> 1;
1469 GUI.mouse_y = y >> 1;
1470
1471 if (mask & Mod1Mask)
1472 {
1473 if (!GUI.mod1_pressed)
1474 {
1475 GUI.mod1_pressed = TRUE;
1476 XDefineCursor(GUI.display, GUI.window, GUI.cross_hair_cursor);
1477 }
1478 }
1479 else
1480 if (GUI.mod1_pressed)
1481 {
1482 GUI.mod1_pressed = FALSE;
1483 XDefineCursor(GUI.display, GUI.window, GUI.point_cursor);
1484 }
1485 }
1486
1487 if (Settings.DumpStreams && isFrameBoundry)
1488 S9xVideoLogger(GUI.image->data, SNES_WIDTH * 2, SNES_HEIGHT_EXTENDED * 2, GUI.bytes_per_pixel, GUI.image->bytes_per_line);
1489}
1490
1491void S9xTextMode (void)
1492{
1493 SetXRepeat(TRUE);
1494}
1495
1496void S9xGraphicsMode (void)
1497{
1498 SetXRepeat(FALSE);
1499}
1500
1501static bool8 CheckForPendingXEvents (Display *display)
1502{
1503#ifdef SELECT_BROKEN_FOR_SIGNALS
1504 int arg = 0;
1505
1506 return (XEventsQueued(display, QueuedAlready) || (ioctl(ConnectionNumber(display), FIONREAD, &arg) == 0 && arg));
1507#else
1508 return (XPending(display));
1509#endif
1510}
1511
1512void S9xLatchJSEvent ()
1513{
1514 // record that a JS event happened and was reported to the engine
1515 GUI.js_event_latch = TRUE;
1516}
1517
1518void S9xProcessEvents (bool8 block)
1519{
1520 // Kick the screensaver if a joystick event occurred
1521 if (GUI.js_event_latch == TRUE) {
1522 XWarpPointer(GUI.display, None, None, 0, 0, 0, 0, 0, 0);
1523 GUI.js_event_latch = FALSE;
1524 }
1525
1526 // Process all other X events
1527 while (block || CheckForPendingXEvents(GUI.display))
1528 {
1529 XEvent event;
1530
1531 XNextEvent(GUI.display, &event);
1532 block = FALSE;
1533
1534 switch (event.type)
1535 {
1536 case KeyPress:
1537 case KeyRelease:
1538 S9xReportButton(((event.xkey.state & (ShiftMask | Mod1Mask | ControlMask | Mod4Mask)) << 8) | event.xkey.keycode, event.type == KeyPress);
1539 #if 1
1540 {
1541 KeyCode kc = XKeysymToKeycode(GUI.display, XKeycodeToKeysym(GUI.display, event.xkey.keycode, 0));
1542 if (event.xkey.keycode != kc)
1543 S9xReportButton(((event.xkey.state & (ShiftMask | Mod1Mask | ControlMask | Mod4Mask)) << 8) | kc, event.type == KeyPress);
1544 }
1545 #endif
1546 break;
1547
1548 case FocusIn:
1549 SetXRepeat(FALSE);
1550 XFlush(GUI.display);
1551 break;
1552
1553 case FocusOut:
1554 SetXRepeat(TRUE);
1555 XFlush(GUI.display);
1556 break;
1557
1558 case ConfigureNotify:
1559 break;
1560
1561 case ButtonPress:
1562 case ButtonRelease:
1563 S9xReportButton(0x40000000 | (event.xbutton.button - 1), event.type == ButtonPress);
1564 break;
1565
1566 case Expose:
1567 Repaint(FALSE);
1568 break;
1569 }
1570 }
1571}
1572
1573const char * S9xSelectFilename (const char *def, const char *dir1, const char *ext1, const char *title)
1574{
1575 static char s[PATH_MAX + 1];
1576 char buffer[PATH_MAX + 1];
1577
1578 SetXRepeat(TRUE);
1579
1580 printf("\n%s (default: %s): ", title, def);
1581 fflush(stdout);
1582
1583 SetXRepeat(FALSE);
1584
1585 if (fgets(buffer, PATH_MAX + 1, stdin))
1586 {
1587 char drive[_MAX_DRIVE + 1], dir[_MAX_DIR + 1], fname[_MAX_FNAME + 1], ext[_MAX_EXT + 1];
1588
1589 char *p = buffer;
1590 while (isspace(*p))
1591 p++;
1592 if (!*p)
1593 {
1594 strncpy(buffer, def, PATH_MAX + 1);
1595 buffer[PATH_MAX] = 0;
1596 p = buffer;
1597 }
1598
1599 char *q = strrchr(p, '\n');
1600 if (q)
1601 *q = 0;
1602
1603 _splitpath(p, drive, dir, fname, ext);
1604 _makepath(s, drive, *dir ? dir : dir1, fname, *ext ? ext : ext1);
1605
1606 return (s);
1607 }
1608
1609 return (NULL);
1610}
1611
1612void S9xMessage (int type, int number, const char *message)
1613{
1614 const int max = 36 * 3;
1615 static char buffer[max + 1];
1616
1617 fprintf(stdout, "%s\n", message);
1618 strncpy(buffer, message, max + 1);
1619 buffer[max] = 0;
1620 S9xSetInfoString(buffer);
1621}
1622
1623const char * S9xStringInput (const char *message)
1624{
1625 static char buffer[256];
1626
1627 printf("%s: ", message);
1628 fflush(stdout);
1629
1630 if (fgets(buffer, sizeof(buffer) - 2, stdin))
1631 return (buffer);
1632
1633 return (NULL);
1634}
1635
1636void S9xSetTitle (const char *string)
1637{
1638 XStoreName(GUI.display, GUI.window, string);
1639 XFlush(GUI.display);
1640}
1641
1642static void SetXRepeat (bool8 state)
1643{
1644 if (state)
1645 XAutoRepeatOn(GUI.display);
1646 else
1647 XAutoRepeatOff(GUI.display);
1648}
1649
1650s9xcommand_t S9xGetDisplayCommandT (const char *n)
1651{
1652 s9xcommand_t cmd;
1653
1654 cmd.type = S9xBadMapping;
1655 cmd.multi_press = 0;
1656 cmd.button_norpt = 0;
1657 cmd.port[0] = 0xff;
1658 cmd.port[1] = 0;
1659 cmd.port[2] = 0;
1660 cmd.port[3] = 0;
1661
1662 return (cmd);
1663}
1664
1665char * S9xGetDisplayCommandName (s9xcommand_t cmd)
1666{
1667 return (strdup("None"));
1668}
1669
1670void S9xHandleDisplayCommand (s9xcommand_t cmd, int16 data1, int16 data2)
1671{
1672 return;
1673}
1674
1675bool8 S9xMapDisplayInput (const char *n, s9xcommand_t *cmd)
1676{
1677 int i, d;
1678
1679 if (!isdigit(n[1]) || !isdigit(n[2]) || n[3] != ':')
1680 goto unrecog;
1681
1682 d = ((n[1] - '0') * 10 + (n[2] - '0')) << 24;
1683
1684 switch (n[0])
1685 {
1686 case 'K':
1687 {
1688 KeyCode kc;
1689 KeySym ks;
1690
1691 d |= 0x00000000;
1692
1693 for (i = 4; n[i] != '\0' && n[i] != '+'; i++) ;
1694
1695 if (n[i] == '\0' || i == 4)
1696 i = 4;
1697 else
1698 {
1699 for (i = 4; n[i] != '+'; i++)
1700 {
1701 switch (n[i])
1702 {
1703 case 'S': d |= ShiftMask << 8; break;
1704 case 'C': d |= ControlMask << 8; break;
1705 case 'A': d |= Mod1Mask << 8; break;
1706 case 'M': d |= Mod4Mask << 8; break;
1707 default: goto unrecog;
1708 }
1709 }
1710
1711 i++;
1712 }
1713
1714 if ((ks = XStringToKeysym(n + i)) == NoSymbol)
1715 goto unrecog;
1716 if ((kc = XKeysymToKeycode(GUI.display, ks)) == 0)
1717 goto unrecog;
1718
1719 d |= kc & 0xff;
1720
1721 return (S9xMapButton(d, *cmd, false));
1722 }
1723
1724 case 'M':
1725 {
1726 char *c;
1727 int j;
1728
1729 d |= 0x40000000;
1730
1731 if (!strncmp(n + 4, "Pointer", 7))
1732 {
1733 d |= 0x8000;
1734
1735 if (n[11] == '\0')
1736 return (S9xMapPointer(d, *cmd, true));
1737
1738 i = 11;
1739 }
1740 else
1741 if (n[4] == 'B')
1742 i = 5;
1743 else
1744 goto unrecog;
1745
1746 d |= j = strtol(n + i, &c, 10);
1747
1748 if ((c != NULL && *c != '\0') || j > 0x7fff)
1749 goto unrecog;
1750
1751 if (d & 0x8000)
1752 return (S9xMapPointer(d, *cmd, true));
1753
1754 return (S9xMapButton(d, *cmd, false));
1755 }
1756
1757 default:
1758 break;
1759 }
1760
1761unrecog:
1762 char *err = new char[strlen(n) + 34];
1763
1764 sprintf(err, "Unrecognized input device name '%s'", n);
1765 perror(err);
1766 delete [] err;
1767
1768 return (false);
1769}
1770
1771bool S9xDisplayPollButton (uint32 id, bool *pressed)
1772{
1773 return (false);
1774}
1775
1776bool S9xDisplayPollAxis (uint32 id, int16 *value)
1777{
1778 return (false);
1779}
1780
1781bool S9xDisplayPollPointer (uint32 id, int16 *x, int16 *y)
1782{
1783 if ((id & 0xc0008000) != 0x40008000)
1784 return (false);
1785
1786 int d = (id >> 24) & 0x3f,
1787 n = id & 0x7fff;
1788
1789 if (d != 0 || n != 0)
1790 return (false);
1791
1792 *x = GUI.mouse_x;
1793 *y = GUI.mouse_y;
1794
1795 return (true);
1796}
1797
1798void S9xSetPalette (void)
1799{
1800 return;
1801}
1802