mutter/clutter/win32/clutter-stage-win32.c

645 lines
19 KiB
C
Raw Normal View History

/* Clutter.
* An OpenGL based 'interactive canvas' library.
* Authored By Matthew Allum <mallum@openedhand.com>
* Copyright (C) 2006-2007 OpenedHand
*
* 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 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., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "clutter-backend-win32.h"
#include "clutter-stage-win32.h"
#include "clutter-win32.h"
#include "../clutter-main.h"
#include "../clutter-feature.h"
#include "../clutter-color.h"
#include "../clutter-util.h"
#include "../clutter-event.h"
#include "../clutter-enum-types.h"
#include "../clutter-private.h"
#include "../clutter-debug.h"
#include "../clutter-units.h"
#include "../clutter-stage.h"
#include "cogl.h"
#include <windows.h>
G_DEFINE_TYPE (ClutterStageWin32, clutter_stage_win32, CLUTTER_TYPE_STAGE);
static void
clutter_stage_win32_show (ClutterActor *actor)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (actor);
if (stage_win32->hwnd)
ShowWindow (stage_win32->hwnd, SW_SHOW);
/* chain up */
CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)->show (actor);
}
static void
clutter_stage_win32_hide (ClutterActor *actor)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (actor);
if (stage_win32->hwnd)
ShowWindow (stage_win32->hwnd, SW_HIDE);
/* chain up */
CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)->hide (actor);
}
static void
clutter_stage_win32_query_coords (ClutterActor *self,
ClutterActorBox *box)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (self);
/* If we're in fullscreen mode then return the size of the screen
instead */
if ((stage_win32->state & CLUTTER_STAGE_STATE_FULLSCREEN))
{
box->x1 = CLUTTER_UNITS_FROM_INT (stage_win32->fullscreen_rect.left);
box->y1 = CLUTTER_UNITS_FROM_INT (stage_win32->fullscreen_rect.top);
box->x2 = CLUTTER_UNITS_FROM_INT (stage_win32->fullscreen_rect.right);
box->y2 = CLUTTER_UNITS_FROM_INT (stage_win32->fullscreen_rect.bottom);
}
else
{
box->x1 = CLUTTER_UNITS_FROM_INT (stage_win32->win_xpos);
box->y1 = CLUTTER_UNITS_FROM_INT (stage_win32->win_ypos);
box->x2 = box->x1 + CLUTTER_UNITS_FROM_INT (stage_win32->win_width);
box->y2 = box->y1 + CLUTTER_UNITS_FROM_INT (stage_win32->win_height);
}
}
static void
get_fullscreen_rect (ClutterStageWin32 *stage_win32)
{
HMONITOR monitor;
MONITORINFO monitor_info;
/* If we already have a window then try to use the same monitor that
is already on */
if (stage_win32->hwnd)
monitor = MonitorFromWindow (stage_win32->hwnd, MONITOR_DEFAULTTONEAREST);
else
{
/* Otherwise just guess that they will want the monitor where
the cursor is */
POINT cursor;
GetCursorPos (&cursor);
monitor = MonitorFromPoint (cursor, MONITOR_DEFAULTTONEAREST);
}
monitor_info.cbSize = sizeof (monitor_info);
GetMonitorInfoW (monitor, &monitor_info);
stage_win32->fullscreen_rect = monitor_info.rcMonitor;
}
static void
get_full_window_pos (ClutterStageWin32 *stage_win32,
int xpos_in, int ypos_in,
int *xpos_out, int *ypos_out)
{
gboolean resizable
= clutter_stage_get_user_resizable (CLUTTER_STAGE (stage_win32));
/* The window position passed to CreateWindow includes the window
decorations */
*xpos_out = xpos_in - GetSystemMetrics (resizable ? SM_CXSIZEFRAME
: SM_CXFIXEDFRAME);
*ypos_out = ypos_in - GetSystemMetrics (resizable ? SM_CYSIZEFRAME
: SM_CYFIXEDFRAME)
- GetSystemMetrics (SM_CYCAPTION);
}
static void
get_full_window_size (ClutterStageWin32 *stage_win32,
int width_in, int height_in,
int *width_out, int *height_out)
{
gboolean resizable
= clutter_stage_get_user_resizable (CLUTTER_STAGE (stage_win32));
/* The window size passed to CreateWindow includes the window
decorations */
*width_out = width_in + GetSystemMetrics (resizable ? SM_CXSIZEFRAME
: SM_CXFIXEDFRAME) * 2;
*height_out = height_in + GetSystemMetrics (resizable ? SM_CYSIZEFRAME
: SM_CYFIXEDFRAME) * 2
+ GetSystemMetrics (SM_CYCAPTION);
}
void
_clutter_stage_win32_get_min_max_info (ClutterStageWin32 *stage_win32,
MINMAXINFO *min_max_info)
{
/* If the window isn't resizable then set the max and min size to
the current size */
if (!clutter_stage_get_user_resizable (CLUTTER_STAGE (stage_win32)))
{
int full_width, full_height;
get_full_window_size (stage_win32,
stage_win32->win_width, stage_win32->win_height,
&full_width, &full_height);
min_max_info->ptMaxTrackSize.x = full_width;
min_max_info->ptMinTrackSize.x = full_width;
min_max_info->ptMaxTrackSize.y = full_height;
min_max_info->ptMinTrackSize.y = full_height;
}
}
static void
clutter_stage_win32_request_coords (ClutterActor *self,
ClutterActorBox *box)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (self);
gint new_xpos, new_ypos, new_width, new_height;
int change_flags = SWP_NOZORDER | SWP_NOSIZE | SWP_NOMOVE;
new_xpos = CLUTTER_UNITS_TO_INT (MIN (box->x1, box->x2));
new_ypos = CLUTTER_UNITS_TO_INT (MIN (box->y1, box->y2));
new_width = ABS (CLUTTER_UNITS_TO_INT (box->x2 - box->x1));
new_height = ABS (CLUTTER_UNITS_TO_INT (box->y2 - box->y1));
if (new_width != stage_win32->win_width
|| new_height != stage_win32->win_height)
change_flags &= ~SWP_NOSIZE;
if (new_xpos != stage_win32->win_xpos
|| new_ypos != stage_win32->win_ypos)
change_flags &= ~SWP_NOMOVE;
if ((change_flags & (SWP_NOSIZE | SWP_NOMOVE)) != (SWP_NOSIZE | SWP_NOMOVE)
/* Ignore size requests if we are in full screen mode */
&& (stage_win32->state & CLUTTER_STAGE_STATE_FULLSCREEN) == 0)
{
stage_win32->win_xpos = new_xpos;
stage_win32->win_ypos = new_ypos;
stage_win32->win_width = new_width;
stage_win32->win_height = new_height;
if (stage_win32->hwnd != NULL)
{
int full_xpos, full_ypos, full_width, full_height;
get_full_window_pos (stage_win32,
new_xpos, new_ypos,
&full_xpos, &full_ypos);
get_full_window_size (stage_win32,
new_width, new_height,
&full_width, &full_height);
SetWindowPos (stage_win32->hwnd, NULL,
full_xpos, full_ypos,
full_width, full_height,
change_flags);
}
CLUTTER_SET_PRIVATE_FLAGS (self, CLUTTER_ACTOR_SYNC_MATRICES);
}
CLUTTER_ACTOR_CLASS (clutter_stage_win32_parent_class)
->request_coords (self, box);
}
static void
clutter_stage_win32_set_title (ClutterStage *stage,
const gchar *title)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (stage);
wchar_t *wtitle;
/* Empty window titles not allowed, so set it to just a period. */
if (title == NULL || !title[0])
title = ".";
wtitle = g_utf8_to_utf16 (title, -1, NULL, NULL, NULL);
SetWindowTextW (stage_win32->hwnd, wtitle);
g_free (wtitle);
}
static LONG
get_window_style (ClutterStageWin32 *stage_win32)
{
/* Fullscreen mode shouldn't have any borders */
if ((stage_win32->state & CLUTTER_STAGE_STATE_FULLSCREEN))
return WS_POPUP;
/* Otherwise it's an overlapped window but if it isn't resizable
then it shouldn't have a thick frame */
else if (clutter_stage_get_user_resizable (CLUTTER_STAGE (stage_win32)))
return WS_OVERLAPPEDWINDOW;
else
return WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME;
}
static void
clutter_stage_win32_set_user_resize (ClutterStage *stage,
gboolean value)
{
HWND hwnd = CLUTTER_STAGE_WIN32 (stage)->hwnd;
LONG old_style = GetWindowLongW (hwnd, GWL_STYLE);
/* Update the window style but preserve the visibility */
SetWindowLongW (hwnd, GWL_STYLE,
get_window_style (CLUTTER_STAGE_WIN32 (stage))
| (old_style & WS_VISIBLE));
}
static void
clutter_stage_win32_set_fullscreen (ClutterStage *stage,
gboolean value)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (stage);
HWND hwnd = CLUTTER_STAGE_WIN32 (stage)->hwnd;
LONG old_style = GetWindowLongW (hwnd, GWL_STYLE);
ClutterStageStateEvent event;
if (value)
stage_win32->state |= CLUTTER_STAGE_STATE_FULLSCREEN;
else
stage_win32->state &= ~CLUTTER_STAGE_STATE_FULLSCREEN;
if (hwnd)
{
/* Update the window style but preserve the visibility */
SetWindowLongW (hwnd, GWL_STYLE,
get_window_style (stage_win32)
| (old_style & WS_VISIBLE));
/* Update the window size */
if (value)
{
get_fullscreen_rect (stage_win32);
SetWindowPos (hwnd, HWND_TOP,
stage_win32->fullscreen_rect.left,
stage_win32->fullscreen_rect.top,
stage_win32->fullscreen_rect.right
- stage_win32->fullscreen_rect.left,
stage_win32->fullscreen_rect.bottom
- stage_win32->fullscreen_rect.top,
0);
}
else
{
int full_xpos, full_ypos, full_width, full_height;
get_full_window_pos (stage_win32,
stage_win32->win_xpos,
stage_win32->win_ypos,
&full_xpos, &full_ypos);
get_full_window_size (stage_win32,
stage_win32->win_width,
stage_win32->win_height,
&full_width, &full_height);
SetWindowPos (stage_win32->hwnd, NULL,
full_xpos, full_ypos,
full_width, full_height,
SWP_NOZORDER);
}
CLUTTER_SET_PRIVATE_FLAGS (stage, CLUTTER_ACTOR_SYNC_MATRICES);
}
/* Report the state change */
memset (&event, 0, sizeof (event));
event.type = CLUTTER_STAGE_STATE;
event.stage = CLUTTER_STAGE (stage_win32);
event.new_state = stage_win32->state;
event.changed_mask = CLUTTER_STAGE_STATE_FULLSCREEN;
clutter_event_put ((ClutterEvent *) &event);
}
static ATOM
clutter_stage_win32_get_window_class ()
{
static ATOM klass = 0;
if (klass == 0)
{
WNDCLASSW wndclass;
memset (&wndclass, 0, sizeof (wndclass));
wndclass.style = CS_DBLCLKS | CS_HREDRAW | CS_VREDRAW;
wndclass.lpfnWndProc = _clutter_stage_win32_window_proc;
wndclass.cbWndExtra = sizeof (LONG_PTR);
wndclass.hInstance = GetModuleHandleW (NULL);
wndclass.hIcon = LoadIconW (NULL, (LPWSTR) IDI_APPLICATION);
wndclass.hCursor = LoadCursorW (NULL, (LPWSTR) IDC_ARROW);
wndclass.hbrBackground = NULL;
wndclass.lpszMenuName = NULL;
wndclass.lpszClassName = L"ClutterStageWin32";
klass = RegisterClassW (&wndclass);
}
return klass;
}
static gboolean
clutter_stage_win32_check_gl_version ()
{
const char *version_string, *major_end, *minor_end;
int major = 0, minor = 0;
/* Get the OpenGL version number */
if ((version_string = (const char *) glGetString (GL_VERSION)) == NULL)
return FALSE;
/* Extract the major number */
for (major_end = version_string; *major_end >= '0'
&& *major_end <= '9'; major_end++)
major = (major * 10) + *major_end - '0';
/* If there were no digits or the major number isn't followed by a
dot then it is invalid */
if (major_end == version_string || *major_end != '.')
return FALSE;
/* Extract the minor number */
for (minor_end = major_end + 1; *minor_end >= '0'
&& *minor_end <= '9'; minor_end++)
minor = (minor * 10) + *minor_end - '0';
/* If there were no digits or there is an unexpected character then
it is invalid */
if (minor_end == major_end + 1
|| (*minor_end && *minor_end != ' ' && *minor_end != '.'))
return FALSE;
/* Accept OpenGL 1.2 or later */
return major > 1 || (major == 1 && minor >= 2);
}
static void
clutter_stage_win32_realize (ClutterActor *actor)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (actor);
ClutterBackendWin32 *backend_win32;
PIXELFORMATDESCRIPTOR pfd;
int pf;
CLUTTER_NOTE (MISC, "Realizing main stage");
backend_win32 = CLUTTER_BACKEND_WIN32 (clutter_get_default_backend ());
if (stage_win32->hwnd == NULL)
{
ATOM window_class = clutter_stage_win32_get_window_class ();
int win_xpos, win_ypos, win_width, win_height;
RECT win_rect;
POINT actual_pos;
if (window_class == 0)
{
g_critical ("Unable to register window class");
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
return;
}
/* If we're in fullscreen mode then use the fullscreen rect
instead */
if ((stage_win32->state & CLUTTER_STAGE_STATE_FULLSCREEN))
{
get_fullscreen_rect (stage_win32);
win_xpos = stage_win32->fullscreen_rect.left;
win_ypos = stage_win32->fullscreen_rect.top;
win_width = stage_win32->fullscreen_rect.right - win_xpos;
win_height = stage_win32->fullscreen_rect.left - win_ypos;
}
else
{
if (stage_win32->win_xpos == 0 && stage_win32->win_ypos == 0)
win_xpos = win_ypos = CW_USEDEFAULT;
else
get_full_window_pos (stage_win32,
stage_win32->win_xpos, stage_win32->win_ypos,
&win_xpos, &win_ypos);
get_full_window_size (stage_win32,
stage_win32->win_width,
stage_win32->win_height,
&win_width, &win_height);
}
stage_win32->hwnd = CreateWindowW ((LPWSTR) MAKEINTATOM (window_class),
L".",
get_window_style (stage_win32),
win_xpos,
win_ypos,
win_width,
win_height,
NULL, NULL,
GetModuleHandle (NULL),
NULL);
if (stage_win32->hwnd == NULL)
{
g_critical ("Unable to create stage window");
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
return;
}
/* Get the position in case CW_USEDEFAULT was specified */
if ((stage_win32->state & CLUTTER_STAGE_STATE_FULLSCREEN) == 0)
{
GetClientRect (stage_win32->hwnd, &win_rect);
actual_pos.x = win_rect.left;
actual_pos.y = win_rect.top;
ClientToScreen (stage_win32->hwnd, &actual_pos);
stage_win32->win_xpos = actual_pos.x;
stage_win32->win_ypos = actual_pos.y;
}
/* Store a pointer to the actor in the extra bytes of the window
so we can quickly access it in the window procedure */
SetWindowLongPtrW (stage_win32->hwnd, 0, (LONG_PTR) stage_win32);
}
if (stage_win32->client_dc)
ReleaseDC (stage_win32->hwnd, stage_win32->client_dc);
stage_win32->client_dc = GetDC (stage_win32->hwnd);
memset (&pfd, 0, sizeof (pfd));
pfd.nSize = sizeof (pfd);
pfd.nVersion = 1;
pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL
| PFD_DOUBLEBUFFER | PFD_GENERIC_ACCELERATED;
pfd.iPixelType = PFD_TYPE_RGBA;
pfd.cColorBits = 24;
pfd.cAlphaBits = 8;
pfd.cDepthBits = 32;
pfd.cStencilBits = 8;
pfd.iLayerType = PFD_MAIN_PLANE;
if ((pf = ChoosePixelFormat (stage_win32->client_dc, &pfd)) == 0
|| !SetPixelFormat (stage_win32->client_dc, pf, &pfd))
{
g_critical ("Unable to find suitable GL pixel format");
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
return;
}
if (backend_win32->gl_context == NULL)
{
backend_win32->gl_context = wglCreateContext (stage_win32->client_dc);
if (backend_win32->gl_context == NULL)
{
g_critical ("Unable to create suitable GL context");
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
return;
}
}
CLUTTER_NOTE (GL, "wglMakeCurrent");
clutter_stage_ensure_current (CLUTTER_STAGE (stage_win32));
if (!clutter_stage_win32_check_gl_version ())
{
g_critical ("OpenGL version number is too low");
CLUTTER_ACTOR_UNSET_FLAGS (actor, CLUTTER_ACTOR_REALIZED);
return;
}
/* Make sure the viewport gets set up correctly */
CLUTTER_SET_PRIVATE_FLAGS (actor, CLUTTER_ACTOR_SYNC_MATRICES);
}
static void
clutter_stage_win32_unrealize (ClutterActor *actor)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (actor);
wglMakeCurrent (NULL, NULL);
if (stage_win32->client_dc)
{
ReleaseDC (stage_win32->hwnd, stage_win32->client_dc);
stage_win32->client_dc = NULL;
}
if (stage_win32->hwnd)
{
DestroyWindow (stage_win32->hwnd);
stage_win32->hwnd = NULL;
}
}
static void
clutter_stage_win32_dispose (GObject *gobject)
{
ClutterStageWin32 *stage_win32 = CLUTTER_STAGE_WIN32 (gobject);
if (stage_win32->hwnd)
clutter_actor_unrealize (CLUTTER_ACTOR (gobject));
G_OBJECT_CLASS (clutter_stage_win32_parent_class)->dispose (gobject);
}
static void
clutter_stage_win32_class_init (ClutterStageWin32Class *klass)
{
GObjectClass *gobject_class = G_OBJECT_CLASS (klass);
ClutterActorClass *actor_class = CLUTTER_ACTOR_CLASS (klass);
ClutterStageClass *stage_class = CLUTTER_STAGE_CLASS (klass);
gobject_class->dispose = clutter_stage_win32_dispose;
actor_class->show = clutter_stage_win32_show;
actor_class->hide = clutter_stage_win32_hide;
actor_class->request_coords = clutter_stage_win32_request_coords;
actor_class->query_coords = clutter_stage_win32_query_coords;
actor_class->realize = clutter_stage_win32_realize;
actor_class->unrealize = clutter_stage_win32_unrealize;
stage_class->set_title = clutter_stage_win32_set_title;
stage_class->set_user_resize = clutter_stage_win32_set_user_resize;
stage_class->set_fullscreen = clutter_stage_win32_set_fullscreen;
}
static void
clutter_stage_win32_init (ClutterStageWin32 *stage)
{
stage->hwnd = NULL;
stage->client_dc = NULL;
stage->win_xpos = 0;
stage->win_ypos = 0;
stage->win_width = 640;
stage->win_height = 480;
stage->backend = NULL;
stage->scroll_pos = 0;
}
/**
* clutter_win32_get_stage_window:
* @stage: a #ClutterStage
*
* Gets the stage's window handle
*
* Return value: An HWND for the stage window.
*
* Since: 0.8
*/
HWND
clutter_win32_get_stage_window (ClutterStage *stage)
{
g_return_val_if_fail (CLUTTER_IS_STAGE_WIN32 (stage), NULL);
return CLUTTER_STAGE_WIN32 (stage)->hwnd;
}
/**
* clutter_win32_get_stage_from_window:
* @hwnd: a window handle
*
* Gets the stage for a particular window.
*
* Return value: The stage or NULL if a stage does not exist for the
* window.
*
* Since: 0.8
*/
ClutterStage *
clutter_win32_get_stage_from_window (HWND hwnd)
{
/* Check whether the window handle is an instance of the stage
window class */
if ((ATOM) GetClassLongPtrW (hwnd, GCW_ATOM)
== clutter_stage_win32_get_window_class ())
/* If it is there should be a pointer to the stage in the window
extra data */
return (ClutterStage *) GetWindowLongPtrW (hwnd, 0);
else
return NULL;
}
void
clutter_stage_win32_map (ClutterStageWin32 *stage_win32)
{
CLUTTER_ACTOR_SET_FLAGS (stage_win32, CLUTTER_ACTOR_MAPPED);
clutter_actor_queue_redraw (CLUTTER_ACTOR (stage_win32));
}
void
clutter_stage_win32_unmap (ClutterStageWin32 *stage_win32)
{
CLUTTER_ACTOR_UNSET_FLAGS (stage_win32, CLUTTER_ACTOR_MAPPED);
}