blob: bf32689bbbb5b4d534a23e7beadfce6762e2131a [file] [log] [blame]
/*
SDL - Simple DirectMedia Layer
Copyright (C) 1997-2006 Sam Lantinga
This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
This library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
Sam Lantinga
slouken@libsdl.org
*/
#include "SDL_config.h"
/*
* GTK+ SDL video driver implementation; this is a little like a pared-down
* version of the X11 driver. You almost certainly do NOT want this target
* on a desktop machine. This was written for the One Laptop Per Child
* project so they wouldn't need to use the SDL_WINDOWID hack with the X11
* driver and compete for the event queue.
*
* Initial work by Ryan C. Gordon (icculus@icculus.org). A good portion
* of this was cut-and-pasted from the dummy video target just to have a
* starting point for the bare minimum to fill in, and some was lifted from
* the x11 target.
*/
#include "SDL_video.h"
#include "SDL_mouse.h"
#include "../SDL_sysvideo.h"
#include "../SDL_pixels_c.h"
#include "../../events/SDL_events_c.h"
#include "SDL_gtkvideo.h"
#include "SDL_gtkevents_c.h"
#include "SDL_gtkmouse_c.h"
#define GTKPLUSVID_DRIVER_NAME "gtk"
/* Initialization/Query functions */
static int GTKPLUS_VideoInit(_THIS, SDL_PixelFormat *vformat);
static SDL_Rect **GTKPLUS_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags);
static SDL_Surface *GTKPLUS_SetVideoMode(_THIS, SDL_Surface *current, int width, int height, int bpp, Uint32 flags);
static int GTKPLUS_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors);
static void GTKPLUS_VideoQuit(_THIS);
/* Hardware surface functions */
static int GTKPLUS_AllocHWSurface(_THIS, SDL_Surface *surface);
static int GTKPLUS_LockHWSurface(_THIS, SDL_Surface *surface);
static void GTKPLUS_UnlockHWSurface(_THIS, SDL_Surface *surface);
static void GTKPLUS_FreeHWSurface(_THIS, SDL_Surface *surface);
static void GTKPLUS_SetCaption(_THIS, const char *title, const char *icon);
/* etc. */
static void GTKPLUS_UpdateRects(_THIS, int numrects, SDL_Rect *rects);
/* GTKPLUS driver bootstrap functions */
static int do_gtk_init(void)
{
static int initted = 0;
if (!initted) { /* !!! FIXME: I can't see a way to deinit gtk... */
int tmpargc = 0;
char *args[] = { NULL, NULL };
char **tmpargv = args;
initted = (gtk_init_check(&tmpargc, &tmpargv));
}
return initted;
}
static int GTKPLUS_Available(void)
{
return 1; /* !!! FIXME */
}
static void GTKPLUS_DeleteDevice(SDL_VideoDevice *device)
{
SDL_free(device->hidden);
SDL_free(device);
}
static SDL_VideoDevice *GTKPLUS_CreateDevice(int devindex)
{
SDL_VideoDevice *device;
/* Initialize all variables that we clean on shutdown */
device = (SDL_VideoDevice *)SDL_malloc(sizeof(SDL_VideoDevice));
if ( device ) {
SDL_memset(device, 0, (sizeof *device));
device->hidden = (struct SDL_PrivateVideoData *)
SDL_malloc((sizeof *device->hidden));
}
if ( (device == NULL) || (device->hidden == NULL) ) {
SDL_OutOfMemory();
if ( device ) {
SDL_free(device);
}
return(0);
}
SDL_memset(device->hidden, 0, (sizeof *device->hidden));
device->handles_any_size = 1;
/* Set the function pointers */
device->VideoInit = GTKPLUS_VideoInit;
device->ListModes = GTKPLUS_ListModes;
device->SetVideoMode = GTKPLUS_SetVideoMode;
device->CreateYUVOverlay = NULL;
device->SetColors = GTKPLUS_SetColors;
device->UpdateRects = GTKPLUS_UpdateRects;
device->VideoQuit = GTKPLUS_VideoQuit;
device->AllocHWSurface = GTKPLUS_AllocHWSurface;
device->CheckHWBlit = NULL;
device->FillHWRect = NULL;
device->SetHWColorKey = NULL;
device->SetHWAlpha = NULL;
device->LockHWSurface = GTKPLUS_LockHWSurface;
device->UnlockHWSurface = GTKPLUS_UnlockHWSurface;
device->FlipHWSurface = NULL;
device->FreeHWSurface = GTKPLUS_FreeHWSurface;
device->SetCaption = GTKPLUS_SetCaption;
device->SetIcon = NULL;
device->IconifyWindow = NULL;
device->GrabInput = NULL;
device->GetWMInfo = NULL;
device->InitOSKeymap = GTKPLUS_InitOSKeymap;
device->PumpEvents = GTKPLUS_PumpEvents;
device->free = GTKPLUS_DeleteDevice;
return device;
}
VideoBootStrap GTKPLUS_bootstrap = {
GTKPLUSVID_DRIVER_NAME, "SDL GTK+ video driver",
GTKPLUS_Available, GTKPLUS_CreateDevice
};
static int add_visual(_THIS, int depth, GdkVisualType vistype)
{
GdkVisual *vi = gdk_visual_get_best_with_both(depth, vistype);
if(vi != NULL) {
g_object_ref(vi);
this->hidden->visuals[this->hidden->nvisuals++] = vi;
}
return(this->hidden->nvisuals);
}
static int GTKPLUS_GetVideoModes(_THIS)
{
const gint screen_w = gdk_screen_width();
const gint screen_h = gdk_screen_height();
int i, n;
{
/* It's interesting to note that if we allow 32 bit depths (on X11),
we get a visual with an alpha mask on composite servers.
static int depth_list[] = { 32, 24, 16, 15, 8 };
*/
static int depth_list[] = { 24, 16, 15, 8 };
int use_directcolor = 1;
/* Search for the visuals in deepest-first order, so that the first
will be the richest one */
if ( SDL_getenv("SDL_VIDEO_GTK_NODIRECTCOLOR") ) {
use_directcolor = 0;
}
this->hidden->nvisuals = 0;
for ( i=0; i<SDL_arraysize(depth_list); ++i ) {
if ( depth_list[i] > 8 ) {
if ( use_directcolor ) {
add_visual(this, depth_list[i], GDK_VISUAL_DIRECT_COLOR);
}
add_visual(this, depth_list[i], GDK_VISUAL_TRUE_COLOR);
} else {
add_visual(this, depth_list[i], GDK_VISUAL_PSEUDO_COLOR);
add_visual(this, depth_list[i], GDK_VISUAL_STATIC_COLOR);
}
}
if ( this->hidden->nvisuals == 0 ) {
SDL_SetError("Found no sufficiently capable GTK+ visuals");
return -1;
}
}
return 0;
}
int GTKPLUS_VideoInit(_THIS, SDL_PixelFormat *vformat)
{
GdkVisual *sysvis = NULL;
int i;
if (!do_gtk_init()) {
return -1;
}
/* Get the available video modes */
if(GTKPLUS_GetVideoModes(this) < 0)
return -1;
/* Determine the current screen size */
this->info.current_w = gdk_screen_width();
this->info.current_h = gdk_screen_height();
/* Determine the default screen depth:
Use the default visual (or at least one with the same depth) */
this->hidden->display_colormap = gdk_colormap_get_system(); /* !!! FIXME: refcount? */
sysvis = gdk_visual_get_system(); /* !!! FIXME: refcount? */
for(i = 0; i < this->hidden->nvisuals; i++) {
if(this->hidden->visuals[i]->depth == sysvis->depth)
break;
}
if(i == this->hidden->nvisuals) {
/* default visual was useless, take the deepest one instead */
i = 0;
}
this->hidden->visual = this->hidden->visuals[i];
if ( this->hidden->visual == sysvis ) { /* !!! FIXME: same pointer? */
this->hidden->colormap = this->hidden->display_colormap;
g_object_ref(this->hidden->colormap);
} else {
this->hidden->colormap = gdk_colormap_new(this->hidden->visual, FALSE);
}
// !!! FIXME: this is not a public GDK symbol!!
vformat->BitsPerPixel = _gdk_windowing_get_bits_for_depth(
gdk_display_get_default(),
this->hidden->visuals[i]->depth);
this->hidden->depth = vformat->BitsPerPixel;
if ( vformat->BitsPerPixel > 8 ) {
vformat->Rmask = this->hidden->visual->red_mask;
vformat->Gmask = this->hidden->visual->green_mask;
vformat->Bmask = this->hidden->visual->blue_mask;
}
if ( this->hidden->depth == 32 ) {
vformat->Amask = (0xFFFFFFFF & ~(vformat->Rmask|vformat->Gmask|vformat->Bmask));
}
#if 0
/* Create the fullscreen and managed windows */
create_aux_windows(this);
/* Create the blank cursor */
SDL_BlankCursor = this->CreateWMCursor(this, blank_cdata, blank_cmask,
BLANK_CWIDTH, BLANK_CHEIGHT,
BLANK_CHOTX, BLANK_CHOTY);
/* Allow environment override of screensaver disable. */
env = SDL_getenv("SDL_VIDEO_ALLOW_SCREENSAVER");
this->hidden->allow_screensaver = ( (env && SDL_atoi(env)) ? 1 : 0 );
#endif
/* We're done! */
gdk_flush(); /* just in case. */
/* Fill in some window manager capabilities */
this->info.wm_available = 1;
return(0);
}
static GdkVisual *find_visual(_THIS, int bpp)
{
GdkDisplay *display = gdk_display_get_default();
int i;
for ( i = 0; i < this->hidden->nvisuals; i++ ) {
const int videpth = this->hidden->visuals[i]->depth;
// !!! FIXME: this is not a public GDK symbol!!
const int depth = _gdk_windowing_get_bits_for_depth(display, videpth);
if ( depth == bpp )
break;
}
if ( i == this->hidden->nvisuals ) {
SDL_SetError("No matching visual for requested depth");
return NULL; /* should never happen */
}
return this->hidden->visuals[i];
}
SDL_Rect **GTKPLUS_ListModes(_THIS, SDL_PixelFormat *format, Uint32 flags)
{
if ((flags & SDL_OPENGL) == 0) {
if (find_visual(this, format->BitsPerPixel) != NULL) {
return (SDL_Rect **) -1; /* !!! FIXME: maybe not right. */
}
}
return NULL; /* unsupported. */
}
SDL_Surface *GTKPLUS_SetVideoMode(_THIS, SDL_Surface *current,
int width, int height, int bpp, Uint32 flags)
{
Uint32 Amask = 0;
int vis_change = 0;
GtkWindow *win = NULL;
GdkImage *img = NULL;
GdkVisual *sysvis = gdk_visual_get_system(); /* !!! FIXME: refcount? */
GdkVisual *vis = find_visual(this, bpp);
if (vis == NULL) {
return(NULL);
}
if (flags & SDL_OPENGL) {
SDL_SetError("No OpenGL support in the GTK+ target");
return(NULL);
}
/* These are the only flags we allow here... */
flags &= /*SDL_FULLSCREEN |*/ SDL_RESIZABLE | SDL_NOFRAME | SDL_HWPALETTE;
vis_change = (vis != this->hidden->visual);
this->hidden->visual = vis;
this->hidden->depth = vis->depth;
/* Allocate the new pixel format for this video mode */
if ( this->hidden->depth == 32 ) {
Amask = (0xFFFFFFFF & ~(vis->red_mask|vis->green_mask|vis->blue_mask));
} else {
Amask = 0;
}
if ( ! SDL_ReallocFormat(current, bpp,
vis->red_mask, vis->green_mask, vis->blue_mask, Amask) ) {
return NULL;
}
/* Create the appropriate colormap */
g_object_unref(this->hidden->colormap);
this->hidden->colormap = NULL;
if ( this->hidden->visual->type == GDK_VISUAL_PSEUDO_COLOR ) {
int ncolors;
/* Allocate the pixel flags */
ncolors = this->hidden->visual->colormap_size;
#if 0
SDL_XPixels = SDL_malloc(ncolors * sizeof(int));
if(SDL_XPixels == NULL) {
SDL_OutOfMemory();
return -1;
}
SDL_memset(SDL_XPixels, 0, ncolors * sizeof(*SDL_XPixels));
#endif
/* always allocate a private colormap on non-default visuals */
if ( this->hidden->visual != sysvis ) {
flags |= SDL_HWPALETTE;
}
if ( flags & SDL_HWPALETTE ) {
current->flags |= SDL_HWPALETTE;
this->hidden->colormap = gdk_colormap_new(this->hidden->visual, TRUE);
} else {
this->hidden->colormap = this->hidden->display_colormap;
g_object_ref(this->hidden->colormap);
}
} else if ( this->hidden->visual->type == GDK_VISUAL_DIRECT_COLOR ) {
/* Create a colormap which we can manipulate for gamma */
this->hidden->colormap = gdk_colormap_new(this->hidden->visual, TRUE);
gdk_flush();
/* Initialize the colormap to the identity mapping */
SDL_GetGammaRamp(0, 0, 0);
this->screen = current;
#if 0 // !!! FIXME
GTKPLUS_SetGammaRamp(this, this->gamma);
#endif
this->screen = NULL;
} else {
/* Create a read-only colormap for our window */
this->hidden->colormap = gdk_colormap_new(this->hidden->visual, FALSE);
}
#if 0 // !!! FIXME
/* Recreate the auxiliary windows, if needed (required for GL) */
if ( vis_change )
create_aux_windows(this);
if(current->flags & SDL_HWPALETTE) {
/* Since the full-screen window might have got a nonzero background
colour (0 is white on some displays), we should reset the
background to 0 here since that is what the user expects
with a private colormap */
XSetWindowBackground(SDL_Display, FSwindow, 0);
XClearWindow(SDL_Display, FSwindow);
}
/* resize the (possibly new) window manager window */
if( !SDL_windowid ) {
X11_SetSizeHints(this, w, h, flags);
window_w = w;
window_h = h;
XResizeWindow(SDL_Display, WMwindow, w, h);
}
#endif
if ( this->hidden->gdkimage ) {
g_object_unref(this->hidden->gdkimage);
this->hidden->gdkimage = NULL;
}
img = this->hidden->gdkimage = gdk_image_new(GDK_IMAGE_FASTEST,
vis, width, height);
if (img == NULL) {
SDL_SetError("Couldn't allocate buffer for requested mode");
return(NULL);
}
gdk_image_set_colormap(this->hidden->gdkimage, this->hidden->colormap);
SDL_memset(img->mem, 0, height * img->bpl);
if ( this->hidden->gtkwindow == NULL ) {
this->hidden->gtkdrawingarea = gtk_drawing_area_new();
if ( this->hidden->gtkdrawingarea == NULL ) {
SDL_SetError("Couldn't create drawing area for requested mode");
g_object_unref(this->hidden->gdkimage);
this->hidden->gdkimage = NULL;
return(NULL);
}
this->hidden->gtkwindow = gtk_window_new(GTK_WINDOW_TOPLEVEL);
if ( this->hidden->gtkwindow == NULL ) {
SDL_SetError("Couldn't create window for requested mode");
g_object_unref(this->hidden->gdkimage);
g_object_unref(this->hidden->gtkdrawingarea);
this->hidden->gdkimage = NULL;
this->hidden->gtkdrawingarea = NULL;
return(NULL);
}
gtk_window_set_title(GTK_WINDOW(this->hidden->gtkwindow), "");
gtk_widget_set_app_paintable(this->hidden->gtkwindow, TRUE);
gtk_widget_set_app_paintable(this->hidden->gtkdrawingarea, TRUE);
gtk_widget_set_double_buffered(this->hidden->gtkwindow, FALSE);
gtk_widget_set_double_buffered(this->hidden->gtkdrawingarea, FALSE);
GTKPLUS_ConnectSignals(this);
gtk_container_add(GTK_CONTAINER(this->hidden->gtkwindow),
this->hidden->gtkdrawingarea);
}
win = GTK_WINDOW(this->hidden->gtkwindow);
gtk_widget_set_colormap(this->hidden->gtkdrawingarea, this->hidden->colormap);
// !!! FIXME
#if 0
/* Cache the window in the server, when possible */
{
Screen *xscreen;
XSetWindowAttributes a;
xscreen = ScreenOfDisplay(SDL_Display, SDL_Screen);
a.backing_store = DoesBackingStore(xscreen);
if ( a.backing_store != NotUseful ) {
XChangeWindowAttributes(SDL_Display, SDL_Window,
CWBackingStore, &a);
}
}
/* Update the internal keyboard state */
X11_SetKeyboardState(SDL_Display, NULL);
/* When the window is first mapped, ignore non-modifier keys */
{
Uint8 *keys = SDL_GetKeyState(NULL);
for ( i = 0; i < SDLK_LAST; ++i ) {
switch (i) {
case SDLK_NUMLOCK:
case SDLK_CAPSLOCK:
case SDLK_LCTRL:
case SDLK_RCTRL:
case SDLK_LSHIFT:
case SDLK_RSHIFT:
case SDLK_LALT:
case SDLK_RALT:
case SDLK_LMETA:
case SDLK_RMETA:
case SDLK_MODE:
break;
default:
keys[i] = SDL_RELEASED;
break;
}
}
}
/* Map them both and go fullscreen, if requested */
if ( ! SDL_windowid ) {
XMapWindow(SDL_Display, SDL_Window);
XMapWindow(SDL_Display, WMwindow);
X11_WaitMapped(this, WMwindow);
if ( flags & SDL_FULLSCREEN ) {
current->flags |= SDL_FULLSCREEN;
X11_EnterFullScreen(this);
} else {
current->flags &= ~SDL_FULLSCREEN;
}
}
#endif
if ((flags & SDL_FULLSCREEN) == 0) {
gtk_window_unfullscreen(win);
} else {
gtk_window_fullscreen(win);
flags &= ~SDL_RESIZABLE;
flags |= SDL_NOFRAME;
}
gtk_window_set_resizable(win, (flags & SDL_RESIZABLE) ? TRUE : FALSE);
gtk_window_set_decorated(win, (flags & SDL_NOFRAME) ? FALSE : TRUE);
gtk_window_resize(win, width, height);
gtk_widget_set_size_request(this->hidden->gtkdrawingarea, width, height);
gtk_widget_show(this->hidden->gtkdrawingarea);
gtk_widget_show(this->hidden->gtkwindow);
/* Set up the new mode framebuffer */
current->w = width;
current->h = height;
//current->format->depth = vis->bits_per_rgb;
current->flags = flags | SDL_PREALLOC;
current->pitch = img->bpl;
current->pixels = this->hidden->gdkimage->mem;
/* We're done */
return(current);
}
static void GTKPLUS_SetCaption(_THIS, const char *title, const char *icon)
{
gtk_window_set_title(GTK_WINDOW(this->hidden->gtkwindow),
(const gchar *) title);
}
/* We don't actually allow hardware surfaces. */
static int GTKPLUS_AllocHWSurface(_THIS, SDL_Surface *surface)
{
return(-1);
}
static void GTKPLUS_FreeHWSurface(_THIS, SDL_Surface *surface)
{
}
/* We need to wait for vertical retrace on page flipped displays */
static int GTKPLUS_LockHWSurface(_THIS, SDL_Surface *surface)
{
return(0);
}
static void GTKPLUS_UnlockHWSurface(_THIS, SDL_Surface *surface)
{
}
static void GTKPLUS_UpdateRects(_THIS, int numrects, SDL_Rect *rects)
{
if ( (this->hidden->gtkdrawingarea != NULL) &&
(GTK_WIDGET_DRAWABLE(this->hidden->gtkdrawingarea)) &&
(numrects > 0) ) {
GdkDrawable *draw = GDK_DRAWABLE(this->hidden->gtkdrawingarea->window);
if (this->hidden->gc == NULL) {
this->hidden->gc = gdk_gc_new(draw);
}
if (this->hidden->gc != NULL) {
GdkImage *img = this->hidden->gdkimage;
const SDL_Rect *r = rects;
int i;
for (i = 0; i < numrects; i++, r++) {
const gint x = r->x;
const gint y = r->y;
gdk_draw_image(draw, this->hidden->gc, img,
x, y, x, y, r->w, r->h);
}
gdk_flush(); /* transfer the GdkImage so we can make changes. */
}
}
}
int GTKPLUS_SetColors(_THIS, int firstcolor, int ncolors, SDL_Color *colors)
{
/* !!! FIXME */
return(0);
}
/* Note: If we are terminated, this could be called in the middle of
another SDL video routine -- notably UpdateRects.
*/
void GTKPLUS_VideoQuit(_THIS)
{
int i;
gdk_flush();
if (this->hidden->gc != NULL) {
g_object_unref(this->hidden->gc);
this->hidden->gc = NULL;
}
if ( this->hidden->gtkwindow ) {
/* this deletes the drawing area widget, too. */
gtk_widget_destroy(this->hidden->gtkwindow);
this->hidden->gtkwindow = NULL;
}
if ( this->hidden->gdkimage ) {
g_object_unref(this->hidden->gdkimage);
this->hidden->gdkimage = NULL;
}
for (i = 0; i < this->hidden->nvisuals; i++) {
g_object_unref(this->hidden->visuals[i]);
this->hidden->visuals[i] = NULL;
}
this->hidden->nvisuals = 0;
g_object_unref(this->hidden->colormap);
this->hidden->colormap = NULL;
gdk_flush();
}