1 : /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* ***** BEGIN LICENSE BLOCK *****
3 : * Version: MPL 1.1/GPL 2.0/LGPL 2.1
4 : *
5 : * The contents of this file are subject to the Mozilla Public License Version
6 : * 1.1 (the "License"); you may not use this file except in compliance with
7 : * the License. You may obtain a copy of the License at
8 : * http://www.mozilla.org/MPL/
9 : *
10 : * Software distributed under the License is distributed on an "AS IS" basis,
11 : * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
12 : * for the specific language governing rights and limitations under the
13 : * License.
14 : *
15 : * The Original Code is mozilla.org code.
16 : *
17 : * The Initial Developer of the Original Code is
18 : * Netscape Communications Corporation.
19 : * Portions created by the Initial Developer are Copyright (C) 2000
20 : * the Initial Developer. All Rights Reserved.
21 : *
22 : * Contributor(s):
23 : * Sylvain Pasche <sylvain.pasche@gmail.com>
24 : * L. David Baron <dbaron@dbaron.org>, Mozilla Corporation
25 : *
26 : * Alternatively, the contents of this file may be used under the terms of
27 : * either of the GNU General Public License Version 2 or later (the "GPL"),
28 : * or the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
29 : * in which case the provisions of the GPL or the LGPL are applicable instead
30 : * of those above. If you wish to allow use of your version of this file only
31 : * under the terms of either the GPL or the LGPL, and not to allow others to
32 : * use your version of this file under the terms of the MPL, indicate your
33 : * decision by deleting the provisions above and replace them with the notice
34 : * and other provisions required by the GPL or the LGPL. If you do not delete
35 : * the provisions above, a recipient may use your version of this file under
36 : * the terms of any one of the MPL, the GPL or the LGPL.
37 : *
38 : * ***** END LICENSE BLOCK ***** */
39 :
40 : #include "nsScreenManagerGtk.h"
41 : #include "nsScreenGtk.h"
42 : #include "nsIComponentManager.h"
43 : #include "nsRect.h"
44 : #include "nsAutoPtr.h"
45 :
46 : #define SCREEN_MANAGER_LIBRARY_LOAD_FAILED ((PRLibrary*)1)
47 :
48 : #ifdef MOZ_X11
49 : #include <gdk/gdkx.h>
50 : // prototypes from Xinerama.h
51 : typedef Bool (*_XnrmIsActive_fn)(Display *dpy);
52 : typedef XineramaScreenInfo* (*_XnrmQueryScreens_fn)(Display *dpy, int *number);
53 : #endif
54 :
55 : #include <gtk/gtk.h>
56 :
57 :
58 : static GdkFilterReturn
59 0 : root_window_event_filter(GdkXEvent *aGdkXEvent, GdkEvent *aGdkEvent,
60 : gpointer aClosure)
61 : {
62 0 : nsScreenManagerGtk *manager = static_cast<nsScreenManagerGtk*>(aClosure);
63 : #ifdef MOZ_X11
64 0 : XEvent *xevent = static_cast<XEvent*>(aGdkXEvent);
65 :
66 : // See comments in nsScreenGtk::Init below.
67 0 : switch (xevent->type) {
68 : case ConfigureNotify:
69 0 : manager->Init();
70 0 : break;
71 : case PropertyNotify:
72 : {
73 0 : XPropertyEvent *propertyEvent = &xevent->xproperty;
74 0 : if (propertyEvent->atom == manager->NetWorkareaAtom()) {
75 0 : manager->Init();
76 : }
77 : }
78 0 : break;
79 : default:
80 0 : break;
81 : }
82 : #endif
83 :
84 0 : return GDK_FILTER_CONTINUE;
85 : }
86 :
87 0 : nsScreenManagerGtk :: nsScreenManagerGtk ( )
88 : : mXineramalib(nsnull)
89 0 : , mRootWindow(nsnull)
90 : {
91 : // nothing else to do. I guess we could cache a bunch of information
92 : // here, but we want to ask the device at runtime in case anything
93 : // has changed.
94 0 : }
95 :
96 :
97 0 : nsScreenManagerGtk :: ~nsScreenManagerGtk()
98 : {
99 0 : if (mRootWindow) {
100 0 : gdk_window_remove_filter(mRootWindow, root_window_event_filter, this);
101 0 : g_object_unref(mRootWindow);
102 0 : mRootWindow = nsnull;
103 : }
104 :
105 : /* XineramaIsActive() registers a callback function close_display()
106 : * in X, which is to be called in XCloseDisplay(). This is the case
107 : * if Xinerama is active, even if only with one screen.
108 : *
109 : * We can't unload libXinerama.so.1 here because this will make
110 : * the address of close_display() registered in X to be invalid and
111 : * it will crash when XCloseDisplay() is called later. */
112 0 : }
113 :
114 :
115 : // addref, release, QI
116 0 : NS_IMPL_ISUPPORTS1(nsScreenManagerGtk, nsIScreenManager)
117 :
118 :
119 : // this function will make sure that everything has been initialized.
120 : nsresult
121 0 : nsScreenManagerGtk :: EnsureInit()
122 : {
123 0 : if (mCachedScreenArray.Count() > 0)
124 0 : return NS_OK;
125 :
126 : #if GTK_CHECK_VERSION(2,2,0)
127 0 : mRootWindow = gdk_get_default_root_window();
128 : #else
129 : mRootWindow = GDK_ROOT_PARENT();
130 : #endif // GTK_CHECK_VERSION(2,2,0)
131 0 : g_object_ref(mRootWindow);
132 :
133 : // GDK_STRUCTURE_MASK ==> StructureNotifyMask, for ConfigureNotify
134 : // GDK_PROPERTY_CHANGE_MASK ==> PropertyChangeMask, for PropertyNotify
135 : gdk_window_set_events(mRootWindow,
136 0 : GdkEventMask(gdk_window_get_events(mRootWindow) |
137 : GDK_STRUCTURE_MASK |
138 0 : GDK_PROPERTY_CHANGE_MASK));
139 0 : gdk_window_add_filter(mRootWindow, root_window_event_filter, this);
140 : #ifdef MOZ_X11
141 : mNetWorkareaAtom =
142 0 : XInternAtom(GDK_WINDOW_XDISPLAY(mRootWindow), "_NET_WORKAREA", False);
143 : #endif
144 :
145 0 : return Init();
146 : }
147 :
148 : nsresult
149 0 : nsScreenManagerGtk :: Init()
150 : {
151 : #ifdef MOZ_X11
152 0 : XineramaScreenInfo *screenInfo = NULL;
153 : int numScreens;
154 :
155 0 : if (!mXineramalib) {
156 0 : mXineramalib = PR_LoadLibrary("libXinerama.so.1");
157 0 : if (!mXineramalib) {
158 0 : mXineramalib = SCREEN_MANAGER_LIBRARY_LOAD_FAILED;
159 : }
160 : }
161 0 : if (mXineramalib && mXineramalib != SCREEN_MANAGER_LIBRARY_LOAD_FAILED) {
162 : _XnrmIsActive_fn _XnrmIsActive = (_XnrmIsActive_fn)
163 0 : PR_FindFunctionSymbol(mXineramalib, "XineramaIsActive");
164 :
165 : _XnrmQueryScreens_fn _XnrmQueryScreens = (_XnrmQueryScreens_fn)
166 0 : PR_FindFunctionSymbol(mXineramalib, "XineramaQueryScreens");
167 :
168 : // get the number of screens via xinerama
169 0 : if (_XnrmIsActive && _XnrmQueryScreens &&
170 0 : _XnrmIsActive(GDK_DISPLAY())) {
171 0 : screenInfo = _XnrmQueryScreens(GDK_DISPLAY(), &numScreens);
172 : }
173 : }
174 :
175 : // screenInfo == NULL if either Xinerama couldn't be loaded or
176 : // isn't running on the current display
177 0 : if (!screenInfo || numScreens == 1) {
178 0 : numScreens = 1;
179 : #endif
180 0 : nsRefPtr<nsScreenGtk> screen;
181 :
182 0 : if (mCachedScreenArray.Count() > 0) {
183 0 : screen = static_cast<nsScreenGtk*>(mCachedScreenArray[0]);
184 : } else {
185 0 : screen = new nsScreenGtk();
186 0 : if (!screen || !mCachedScreenArray.AppendObject(screen)) {
187 0 : return NS_ERROR_OUT_OF_MEMORY;
188 : }
189 : }
190 :
191 0 : screen->Init(mRootWindow);
192 : #ifdef MOZ_X11
193 : }
194 : // If Xinerama is enabled and there's more than one screen, fill
195 : // in the info for all of the screens. If that's not the case
196 : // then nsScreenGTK() defaults to the screen width + height
197 : else {
198 : #ifdef DEBUG
199 0 : printf("Xinerama superpowers activated for %d screens!\n", numScreens);
200 : #endif
201 0 : for (int i = 0; i < numScreens; ++i) {
202 0 : nsRefPtr<nsScreenGtk> screen;
203 0 : if (mCachedScreenArray.Count() > i) {
204 0 : screen = static_cast<nsScreenGtk*>(mCachedScreenArray[i]);
205 : } else {
206 0 : screen = new nsScreenGtk();
207 0 : if (!screen || !mCachedScreenArray.AppendObject(screen)) {
208 0 : return NS_ERROR_OUT_OF_MEMORY;
209 : }
210 : }
211 :
212 : // initialize this screen object
213 0 : screen->Init(&screenInfo[i]);
214 : }
215 : }
216 : // Remove any screens that are no longer present.
217 0 : while (mCachedScreenArray.Count() > numScreens) {
218 0 : mCachedScreenArray.RemoveObjectAt(mCachedScreenArray.Count() - 1);
219 : }
220 :
221 0 : if (screenInfo) {
222 0 : XFree(screenInfo);
223 : }
224 : #endif
225 :
226 0 : return NS_OK;
227 : }
228 :
229 :
230 : //
231 : // ScreenForRect
232 : //
233 : // Returns the screen that contains the rectangle. If the rect overlaps
234 : // multiple screens, it picks the screen with the greatest area of intersection.
235 : //
236 : // The coordinates are in pixels (not app units) and in screen coordinates.
237 : //
238 : NS_IMETHODIMP
239 0 : nsScreenManagerGtk :: ScreenForRect ( PRInt32 aX, PRInt32 aY,
240 : PRInt32 aWidth, PRInt32 aHeight,
241 : nsIScreen **aOutScreen )
242 : {
243 : nsresult rv;
244 0 : rv = EnsureInit();
245 0 : if (NS_FAILED(rv)) {
246 0 : NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForRect");
247 0 : return rv;
248 : }
249 : // which screen ( index from zero ) should we return?
250 0 : PRUint32 which = 0;
251 : // Optimize for the common case. If the number of screens is only
252 : // one then this will fall through with which == 0 and will get the
253 : // primary screen.
254 0 : if (mCachedScreenArray.Count() > 1) {
255 : // walk the list of screens and find the one that has the most
256 : // surface area.
257 0 : PRUint32 area = 0;
258 0 : nsIntRect windowRect(aX, aY, aWidth, aHeight);
259 0 : for (PRInt32 i = 0, i_end = mCachedScreenArray.Count(); i < i_end; ++i) {
260 : PRInt32 x, y, width, height;
261 0 : x = y = width = height = 0;
262 0 : mCachedScreenArray[i]->GetRect(&x, &y, &width, &height);
263 : // calculate the surface area
264 0 : nsIntRect screenRect(x, y, width, height);
265 0 : screenRect.IntersectRect(screenRect, windowRect);
266 0 : PRUint32 tempArea = screenRect.width * screenRect.height;
267 0 : if (tempArea >= area) {
268 0 : which = i;
269 0 : area = tempArea;
270 : }
271 : }
272 : }
273 0 : *aOutScreen = mCachedScreenArray.SafeObjectAt(which);
274 0 : NS_IF_ADDREF(*aOutScreen);
275 0 : return NS_OK;
276 :
277 : } // ScreenForRect
278 :
279 :
280 : //
281 : // GetPrimaryScreen
282 : //
283 : // The screen with the menubar/taskbar. This shouldn't be needed very
284 : // often.
285 : //
286 : NS_IMETHODIMP
287 0 : nsScreenManagerGtk :: GetPrimaryScreen(nsIScreen * *aPrimaryScreen)
288 : {
289 : nsresult rv;
290 0 : rv = EnsureInit();
291 0 : if (NS_FAILED(rv)) {
292 0 : NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from GetPrimaryScreen");
293 0 : return rv;
294 : }
295 0 : *aPrimaryScreen = mCachedScreenArray.SafeObjectAt(0);
296 0 : NS_IF_ADDREF(*aPrimaryScreen);
297 0 : return NS_OK;
298 :
299 : } // GetPrimaryScreen
300 :
301 :
302 : //
303 : // GetNumberOfScreens
304 : //
305 : // Returns how many physical screens are available.
306 : //
307 : NS_IMETHODIMP
308 0 : nsScreenManagerGtk :: GetNumberOfScreens(PRUint32 *aNumberOfScreens)
309 : {
310 : nsresult rv;
311 0 : rv = EnsureInit();
312 0 : if (NS_FAILED(rv)) {
313 0 : NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from GetNumberOfScreens");
314 0 : return rv;
315 : }
316 0 : *aNumberOfScreens = mCachedScreenArray.Count();
317 0 : return NS_OK;
318 :
319 : } // GetNumberOfScreens
320 :
321 : NS_IMETHODIMP
322 0 : nsScreenManagerGtk :: ScreenForNativeWidget (void *aWidget, nsIScreen **outScreen)
323 : {
324 : nsresult rv;
325 0 : rv = EnsureInit();
326 0 : if (NS_FAILED(rv)) {
327 0 : NS_ERROR("nsScreenManagerGtk::EnsureInit() failed from ScreenForNativeWidget");
328 0 : return rv;
329 : }
330 :
331 0 : if (mCachedScreenArray.Count() > 1) {
332 : // I don't know how to go from GtkWindow to nsIScreen, especially
333 : // given xinerama and stuff, so let's just do this
334 : gint x, y, width, height, depth;
335 0 : x = y = width = height = 0;
336 :
337 0 : gdk_window_get_geometry(GDK_WINDOW(aWidget), &x, &y, &width, &height,
338 0 : &depth);
339 0 : gdk_window_get_origin(GDK_WINDOW(aWidget), &x, &y);
340 0 : rv = ScreenForRect(x, y, width, height, outScreen);
341 : } else {
342 0 : rv = GetPrimaryScreen(outScreen);
343 : }
344 :
345 0 : return rv;
346 : }
|