summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/gtk/bookmark_bubble_gtk.cc19
-rw-r--r--chrome/browser/gtk/bookmark_bubble_gtk.h6
-rw-r--r--chrome/browser/gtk/browser_window_gtk.cc9
-rw-r--r--chrome/browser/gtk/first_run_bubble.cc8
-rw-r--r--chrome/browser/gtk/info_bubble_gtk.cc147
-rw-r--r--chrome/browser/gtk/info_bubble_gtk.h30
-rw-r--r--chrome/common/x11_util.cc29
-rw-r--r--chrome/common/x11_util.h12
8 files changed, 162 insertions, 98 deletions
diff --git a/chrome/browser/gtk/bookmark_bubble_gtk.cc b/chrome/browser/gtk/bookmark_bubble_gtk.cc
index 8e93d77..ca4f40a 100644
--- a/chrome/browser/gtk/bookmark_bubble_gtk.cc
+++ b/chrome/browser/gtk/bookmark_bubble_gtk.cc
@@ -85,7 +85,7 @@ std::vector<const BookmarkNode*> PopulateFolderCombo(BookmarkModel* model,
} // namespace
// static
-void BookmarkBubbleGtk::Show(GtkWindow* transient_toplevel,
+void BookmarkBubbleGtk::Show(GtkWindow* toplevel_window,
const gfx::Rect& rect,
Profile* profile,
const GURL& url,
@@ -96,7 +96,7 @@ void BookmarkBubbleGtk::Show(GtkWindow* transient_toplevel,
// think that closing the previous bubble and opening the new one would make
// more sense, but I guess then you would commit the bubble's changes.
DCHECK(!g_bubble);
- g_bubble = new BookmarkBubbleGtk(transient_toplevel, rect, profile,
+ g_bubble = new BookmarkBubbleGtk(toplevel_window, rect, profile,
url, newly_bookmarked);
}
@@ -135,7 +135,7 @@ void BookmarkBubbleGtk::Observe(NotificationType type,
}
}
-BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* transient_toplevel,
+BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* toplevel_window,
const gfx::Rect& rect,
Profile* profile,
const GURL& url,
@@ -143,7 +143,7 @@ BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* transient_toplevel,
: url_(url),
profile_(profile),
theme_provider_(GtkThemeProvider::GetFrom(profile_)),
- transient_toplevel_(transient_toplevel),
+ toplevel_window_(toplevel_window),
content_(NULL),
name_entry_(NULL),
folder_combo_(NULL),
@@ -163,7 +163,7 @@ BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* transient_toplevel,
GtkWidget* close_button = gtk_button_new_with_label(
l10n_util::GetStringUTF8(IDS_CLOSE).c_str());
- // Our content is arrange in 3 rows. |top| contains a left justified
+ // Our content is arranged in 3 rows. |top| contains a left justified
// message, and a right justified remove link button. |table| is the middle
// portion with the name entry and the folder combo. |bottom| is the final
// row with a spacer, and the edit... and close buttons on the right.
@@ -213,8 +213,11 @@ BookmarkBubbleGtk::BookmarkBubbleGtk(GtkWindow* transient_toplevel,
// We want the focus to start on the entry, not on the remove button.
gtk_container_set_focus_child(GTK_CONTAINER(content), table);
- bubble_ = InfoBubbleGtk::Show(transient_toplevel_,
- rect, content, theme_provider_, this);
+ bubble_ = InfoBubbleGtk::Show(toplevel_window_,
+ rect,
+ content,
+ theme_provider_,
+ this); // delegate
if (!bubble_) {
NOTREACHED();
return;
@@ -347,7 +350,7 @@ void BookmarkBubbleGtk::ShowEditor() {
// Closing might delete us, so we'll cache what we want we need on the stack.
Profile* profile = profile_;
- GtkWidget* toplevel = GTK_WIDGET(transient_toplevel_);
+ GtkWidget* toplevel = GTK_WIDGET(toplevel_window_);
// Close the bubble, deleting the C++ objects, etc.
bubble_->Close();
diff --git a/chrome/browser/gtk/bookmark_bubble_gtk.h b/chrome/browser/gtk/bookmark_bubble_gtk.h
index a549f18..2011d63 100644
--- a/chrome/browser/gtk/bookmark_bubble_gtk.h
+++ b/chrome/browser/gtk/bookmark_bubble_gtk.h
@@ -34,7 +34,7 @@ class BookmarkBubbleGtk : public InfoBubbleGtkDelegate,
public NotificationObserver {
public:
// Shows the bookmark bubble, pointing at |rect|.
- static void Show(GtkWindow* transient_toplevel,
+ static void Show(GtkWindow* toplevel_window,
const gfx::Rect& rect,
Profile* profile,
const GURL& url,
@@ -52,7 +52,7 @@ class BookmarkBubbleGtk : public InfoBubbleGtkDelegate,
const NotificationDetails& details);
private:
- BookmarkBubbleGtk(GtkWindow* transient_toplevel,
+ BookmarkBubbleGtk(GtkWindow* toplevel_window,
const gfx::Rect& rect,
Profile* profile,
const GURL& url,
@@ -120,7 +120,7 @@ class BookmarkBubbleGtk : public InfoBubbleGtkDelegate,
GtkThemeProvider* theme_provider_;
// The toplevel window our dialogs should be transient for.
- GtkWindow* transient_toplevel_;
+ GtkWindow* toplevel_window_;
// We let the InfoBubble own our content, and then we delete ourself
// when the widget is destroyed (when the InfoBubble is destroyed).
diff --git a/chrome/browser/gtk/browser_window_gtk.cc b/chrome/browser/gtk/browser_window_gtk.cc
index b617829..f6e85f1 100644
--- a/chrome/browser/gtk/browser_window_gtk.cc
+++ b/chrome/browser/gtk/browser_window_gtk.cc
@@ -1230,14 +1230,7 @@ void BrowserWindowGtk::ActiveWindowChanged(GdkWindow* active_window) {
if (!window_)
return;
- // If we lose focus to an info bubble, we don't want to seem inactive.
- // However we can only control this when we are painting a custom
- // frame. So if we lose focus BUT it's to one of our info bubbles AND we
- // are painting a custom frame, then paint as if we are active.
- const GtkWindow* info_bubble_toplevel =
- InfoBubbleGtk::GetToplevelForInfoBubble(active_window);
- bool is_active = (GTK_WIDGET(window_)->window == active_window ||
- (window_ == info_bubble_toplevel && UseCustomFrame()));
+ bool is_active = (GTK_WIDGET(window_)->window == active_window);
bool changed = (is_active != is_active_);
if (is_active && changed) {
diff --git a/chrome/browser/gtk/first_run_bubble.cc b/chrome/browser/gtk/first_run_bubble.cc
index 12127bd..1d90194 100644
--- a/chrome/browser/gtk/first_run_bubble.cc
+++ b/chrome/browser/gtk/first_run_bubble.cc
@@ -137,9 +137,13 @@ FirstRunBubble::FirstRunBubble(Profile* profile,
gtk_box_pack_start(GTK_BOX(content_), bottom, FALSE, FALSE, 0);
// We want the focus to start on the keep entry, not on the change button.
- gtk_container_set_focus_child(GTK_CONTAINER(content_), keep_button);
+ gtk_widget_grab_focus(keep_button);
- bubble_ = InfoBubbleGtk::Show(parent_, rect, content_, theme_provider_, this);
+ bubble_ = InfoBubbleGtk::Show(parent_,
+ rect,
+ content_,
+ theme_provider_,
+ this); // delegate
if (!bubble_) {
NOTREACHED();
return;
diff --git a/chrome/browser/gtk/info_bubble_gtk.cc b/chrome/browser/gtk/info_bubble_gtk.cc
index f9de638..47733e7 100644
--- a/chrome/browser/gtk/info_bubble_gtk.cc
+++ b/chrome/browser/gtk/info_bubble_gtk.cc
@@ -37,8 +37,6 @@ const int kRightMargin = kCornerSize + 6;
const GdkColor kBackgroundColor = GDK_COLOR_RGB(0xff, 0xff, 0xff);
const GdkColor kFrameColor = GDK_COLOR_RGB(0x63, 0x63, 0x63);
-const gchar* kInfoBubbleToplevelKey = "__INFO_BUBBLE_TOPLEVEL__";
-
enum FrameType {
FRAME_MASK,
FRAME_STROKE,
@@ -123,13 +121,13 @@ gboolean HandleExpose(GtkWidget* widget,
} // namespace
// static
-InfoBubbleGtk* InfoBubbleGtk::Show(GtkWindow* transient_toplevel,
+InfoBubbleGtk* InfoBubbleGtk::Show(GtkWindow* toplevel_window,
const gfx::Rect& rect,
GtkWidget* content,
GtkThemeProvider* provider,
InfoBubbleGtkDelegate* delegate) {
InfoBubbleGtk* bubble = new InfoBubbleGtk(provider);
- bubble->Init(transient_toplevel, rect, content);
+ bubble->Init(toplevel_window, rect, content);
bubble->set_delegate(delegate);
return bubble;
}
@@ -140,29 +138,28 @@ InfoBubbleGtk::InfoBubbleGtk(GtkThemeProvider* provider)
theme_provider_(provider),
accel_group_(gtk_accel_group_new()),
screen_x_(0),
- screen_y_(0) {
+ screen_y_(0),
+ mask_region_(NULL) {
}
InfoBubbleGtk::~InfoBubbleGtk() {
g_object_unref(accel_group_);
+ if (mask_region_) {
+ gdk_region_destroy(mask_region_);
+ mask_region_ = NULL;
+ }
}
-void InfoBubbleGtk::Init(GtkWindow* transient_toplevel,
+void InfoBubbleGtk::Init(GtkWindow* toplevel_window,
const gfx::Rect& rect,
GtkWidget* content) {
DCHECK(!window_);
rect_ = rect;
- window_ = gtk_window_new(GTK_WINDOW_TOPLEVEL);
- gtk_window_set_transient_for(GTK_WINDOW(window_), transient_toplevel);
- gtk_window_set_skip_taskbar_hint(GTK_WINDOW(window_), TRUE);
- gtk_window_set_decorated(GTK_WINDOW(window_), FALSE);
- gtk_window_set_resizable(GTK_WINDOW(window_), FALSE);
+ window_ = gtk_window_new(GTK_WINDOW_POPUP);
gtk_widget_set_app_paintable(window_, TRUE);
// Have GTK double buffer around the expose signal.
gtk_widget_set_double_buffered(window_, TRUE);
- // Make sure that our window can be focused.
- GTK_WIDGET_SET_FLAGS(window_, GTK_CAN_FOCUS);
// Attach our accelerator group to the window with an escape accelerator.
gtk_accel_group_connect(accel_group_, GDK_Escape,
@@ -198,35 +195,74 @@ void InfoBubbleGtk::Init(GtkWindow* transient_toplevel,
G_CALLBACK(HandleExpose), NULL);
g_signal_connect(window_, "size-allocate",
G_CALLBACK(HandleSizeAllocateThunk), this);
- g_signal_connect(window_, "configure-event",
- G_CALLBACK(&HandleConfigureThunk), this);
g_signal_connect(window_, "button-press-event",
G_CALLBACK(&HandleButtonPressThunk), this);
g_signal_connect(window_, "destroy",
G_CALLBACK(&HandleDestroyThunk), this);
- // Set some data which helps the browser know whether it should appear
- // active.
- g_object_set_data(G_OBJECT(window_->window), kInfoBubbleToplevelKey,
- transient_toplevel);
-
gtk_widget_show_all(window_);
- // Make sure our window has focus, is brought to the top, etc.
- gtk_window_present(GTK_WINDOW(window_));
- // We add a GTK (application level) grab. This means we will get all
- // keyboard and mouse events for our application, even if they were delivered
- // on another window. This allows us to close when the user clicks outside
- // of the info bubble. We don't use an X grab since that would steal
- // keystrokes from your window manager, prevent you from interacting with
- // other applications, etc.
+
+ // Stack our window directly above the toplevel window. Our window is a
+ // direct child of the root window, so we need to find a similar ancestor
+ // for the toplevel window (which might have been reparented by a window
+ // manager).
+ XID toplevel_window_base = x11_util::GetHighestAncestorWindow(
+ x11_util::GetX11WindowFromGtkWidget(GTK_WIDGET(toplevel_window)),
+ x11_util::GetX11RootWindow());
+ if (toplevel_window_base) {
+ XID window_xid = x11_util::GetX11WindowFromGtkWidget(GTK_WIDGET(window_));
+ XID window_parent = x11_util::GetParentWindow(window_xid);
+ if (window_parent == x11_util::GetX11RootWindow()) {
+ x11_util::RestackWindow(window_xid, toplevel_window_base, true);
+ } else {
+ // The window manager shouldn't reparent override-redirect windows.
+ DLOG(ERROR) << "override-redirect window " << window_xid
+ << "'s parent is " << window_parent
+ << ", rather than root window "
+ << x11_util::GetX11RootWindow();
+ }
+ }
+
+ // We add a GTK (application-level) grab. This means we will get all
+ // mouse events for our application, even if they were delivered on another
+ // window. We don't need this to get button presses outside of the bubble's
+ // window so we'll know to close it (the pointer grab takes care of that), but
+ // it prevents other widgets from getting highlighted when the pointer moves
+ // over them.
//
- // Before adding the grab, we need to ensure that the bubble is added
- // to the window group of the top level window. This ensures that the
- // grab only affects the current browser window, and not all the open
- // browser windows in the application.
- gtk_window_group_add_window(gtk_window_get_group(transient_toplevel),
+ // (Ideally we wouldn't add the window to a group and it would just get all
+ // the mouse events, but gtk_grab_add() doesn't appear to do anything in that
+ // case. Adding it to the toplevel window's group first appears to block
+ // enter/leave events for that window and its subwindows, although other
+ // browser windows still receive them).
+ gtk_window_group_add_window(gtk_window_get_group(toplevel_window),
GTK_WINDOW(window_));
gtk_grab_add(window_);
+
+ // Do X pointer and keyboard grabs to make sure that we have the focus and get
+ // all mouse and keyboard events until we're closed.
+ GdkGrabStatus pointer_grab_status =
+ gdk_pointer_grab(window_->window,
+ TRUE, // owner_events
+ GDK_BUTTON_PRESS_MASK, // event_mask
+ NULL, // confine_to
+ NULL, // cursor
+ GDK_CURRENT_TIME);
+ if (pointer_grab_status != GDK_GRAB_SUCCESS) {
+ // This will fail if someone else already has the pointer grabbed, but
+ // there's not really anything we can do about that.
+ DLOG(ERROR) << "Unable to grab pointer for info bubble (status="
+ << pointer_grab_status << ")";
+ }
+ GdkGrabStatus keyboard_grab_status =
+ gdk_keyboard_grab(window_->window,
+ FALSE, // owner_events
+ GDK_CURRENT_TIME);
+ if (keyboard_grab_status != GDK_GRAB_SUCCESS) {
+ DLOG(ERROR) << "Unable to grab keyboard for info bubble (status="
+ << keyboard_grab_status << ")";
+ }
+
registrar_.Add(this, NotificationType::BROWSER_THEME_CHANGED,
NotificationService::AllSources());
theme_provider_->InitThemesFor(this);
@@ -254,22 +290,15 @@ void InfoBubbleGtk::Observe(NotificationType type,
}
}
-// static
-GtkWindow* InfoBubbleGtk::GetToplevelForInfoBubble(
- const GdkWindow* bubble_window) {
- if (!bubble_window)
- return NULL;
-
- return reinterpret_cast<GtkWindow*>(
- g_object_get_data(G_OBJECT(bubble_window), kInfoBubbleToplevelKey));
-}
-
void InfoBubbleGtk::Close(bool closed_by_escape) {
// Notify the delegate that we're about to close. This gives the chance
// to save state / etc from the hosted widget before it's destroyed.
if (delegate_)
delegate_->InfoBubbleClosing(this, closed_by_escape);
+ // We don't need to ungrab the pointer or keyboard here; the X server will
+ // automatically do that when we destroy our window.
+
DCHECK(window_);
gtk_widget_destroy(window_);
// |this| has been deleted, see HandleDestroy.
@@ -289,29 +318,27 @@ void InfoBubbleGtk::HandleSizeAllocate() {
}
DCHECK(window_->allocation.x == 0 && window_->allocation.y == 0);
+ if (mask_region_) {
+ gdk_region_destroy(mask_region_);
+ mask_region_ = NULL;
+ }
std::vector<GdkPoint> points = MakeFramePolygonPoints(
window_->allocation.width, window_->allocation.height, FRAME_MASK);
- GdkRegion* mask_region = gdk_region_polygon(&points[0],
- points.size(),
- GDK_EVEN_ODD_RULE);
- gdk_window_shape_combine_region(window_->window, mask_region, 0, 0);
- gdk_region_destroy(mask_region);
-}
-
-gboolean InfoBubbleGtk::HandleConfigure(GdkEventConfigure* event) {
- // If the window is moved someplace besides where we want it, move it back.
- // TODO(deanm): In the end, I will probably remove this code and just let
- // the user move around the bubble like a normal dialog. I want to try
- // this for now and see if it causes problems when any window managers.
- if (event->x != screen_x_ || event->y != screen_y_)
- gtk_window_move(GTK_WINDOW(window_), screen_x_, screen_y_);
- return FALSE;
+ mask_region_ = gdk_region_polygon(&points[0],
+ points.size(),
+ GDK_EVEN_ODD_RULE);
+ gdk_window_shape_combine_region(window_->window, mask_region_, 0, 0);
}
gboolean InfoBubbleGtk::HandleButtonPress(GdkEventButton* event) {
- // If we got a click in our own window, that's ok.
- if (event->window == window_->window)
+ // If we got a click in our own window, that's okay (we need to additionally
+ // check that it falls within our bounds, since we've grabbed the pointer and
+ // some events that actually occurred in other windows will be reported with
+ // respect to our window).
+ if (event->window == window_->window &&
+ (mask_region_ && gdk_region_point_in(mask_region_, event->x, event->y))) {
return FALSE; // Propagate.
+ }
// Otherwise we had a click outside of our window, close ourself.
Close();
diff --git a/chrome/browser/gtk/info_bubble_gtk.h b/chrome/browser/gtk/info_bubble_gtk.h
index 137b395..b5021df 100644
--- a/chrome/browser/gtk/info_bubble_gtk.h
+++ b/chrome/browser/gtk/info_bubble_gtk.h
@@ -16,6 +16,7 @@
#include <gtk/gtk.h>
#include "base/basictypes.h"
+#include "base/gfx/point.h"
#include "base/gfx/rect.h"
#include "chrome/common/notification_registrar.h"
@@ -39,10 +40,13 @@ class InfoBubbleGtkDelegate {
class InfoBubbleGtk : public NotificationObserver {
public:
// Show an InfoBubble, pointing at the area |rect| (in screen coordinates).
- // An infobubble will try to fit on the screen, so it can point to any edge
- // of |rect|. The bubble will host the |content| widget. The |delegate|
- // will be notified when things like closing are happening.
- static InfoBubbleGtk* Show(GtkWindow* transient_toplevel,
+ // An info bubble will try to fit on the screen, so it can point to any edge
+ // of |rect|. The bubble will host the |content| widget. The |delegate| will
+ // be notified when the bubble is closed. The bubble will perform an X grab
+ // of the pointer and keyboard, and will close itself if a click is received
+ // outside of the bubble.
+ // TODO(derat): This implementation doesn't try to position itself onscreen.
+ static InfoBubbleGtk* Show(GtkWindow* toplevel_window,
const gfx::Rect& rect,
GtkWidget* content,
GtkThemeProvider* provider,
@@ -57,17 +61,12 @@ class InfoBubbleGtk : public NotificationObserver {
const NotificationSource& source,
const NotificationDetails& details);
- // This returns the toplevel GtkWindow that is the transient parent of
- // |bubble_window|, or NULL if |bubble_window| isn't the GdkWindow
- // for an InfoBubbleGtk.
- static GtkWindow* GetToplevelForInfoBubble(const GdkWindow* bubble_window);
-
private:
explicit InfoBubbleGtk(GtkThemeProvider* provider);
virtual ~InfoBubbleGtk();
// Creates the InfoBubble.
- void Init(GtkWindow* transient_toplevel,
+ void Init(GtkWindow* toplevel_window,
const gfx::Rect& rect,
GtkWidget* content);
@@ -97,13 +96,6 @@ class InfoBubbleGtk : public NotificationObserver {
}
void HandleSizeAllocate();
- static gboolean HandleConfigureThunk(GtkWidget* widget,
- GdkEventConfigure* event,
- gpointer user_data) {
- return reinterpret_cast<InfoBubbleGtk*>(user_data)->HandleConfigure(event);
- }
- gboolean HandleConfigure(GdkEventConfigure* event);
-
static gboolean HandleButtonPressThunk(GtkWidget* widget,
GdkEventButton* event,
gpointer userdata) {
@@ -147,6 +139,10 @@ class InfoBubbleGtk : public NotificationObserver {
int screen_x_;
int screen_y_;
+ // The current shape of |window_| (used to test whether clicks fall in it or
+ // not).
+ GdkRegion* mask_region_;
+
NotificationRegistrar registrar_;
DISALLOW_COPY_AND_ASSIGN(InfoBubbleGtk);
diff --git a/chrome/common/x11_util.cc b/chrome/common/x11_util.cc
index eb8d1b4..1905d2c 100644
--- a/chrome/common/x11_util.cc
+++ b/chrome/common/x11_util.cc
@@ -263,6 +263,28 @@ bool GetStringProperty(
return true;
}
+XID GetParentWindow(XID window) {
+ XID root = None;
+ XID parent = None;
+ XID* children = NULL;
+ unsigned int num_children = 0;
+ XQueryTree(GetXDisplay(), window, &root, &parent, &children, &num_children);
+ if (children)
+ XFree(children);
+ return parent;
+}
+
+XID GetHighestAncestorWindow(XID window, XID root) {
+ while (true) {
+ XID parent = x11_util::GetParentWindow(window);
+ if (parent == None)
+ return None;
+ if (parent == root)
+ return window;
+ window = parent;
+ }
+}
+
// Returns true if |window| is a named window.
bool IsWindowNamed(XID window) {
XTextProperty prop;
@@ -358,6 +380,13 @@ bool GetXWindowStack(std::vector<XID>* windows) {
return result;
}
+void RestackWindow(XID window, XID sibling, bool above) {
+ XWindowChanges changes;
+ changes.sibling = sibling;
+ changes.stack_mode = above ? Above : Below;
+ XConfigureWindow(GetXDisplay(), window, CWSibling | CWStackMode, &changes);
+}
+
XRenderPictFormat* GetRenderVisualFormat(Display* dpy, Visual* visual) {
DCHECK(QueryRenderSupport(dpy));
diff --git a/chrome/common/x11_util.h b/chrome/common/x11_util.h
index a3dbb2a..f11739c 100644
--- a/chrome/common/x11_util.h
+++ b/chrome/common/x11_util.h
@@ -70,6 +70,12 @@ bool GetIntProperty(XID window, const std::string& property_name, int* value);
bool GetStringProperty(
XID window, const std::string& property_name, std::string* value);
+// Get |window|'s parent window, or None if |window| is the root window.
+XID GetParentWindow(XID window);
+
+// Walk up |window|'s hierarchy until we find a direct child of |root|.
+XID GetHighestAncestorWindow(XID window, XID root);
+
// Implementers of this interface receive a notification for every X window of
// the main display.
class EnumerateWindowsDelegate {
@@ -86,6 +92,12 @@ bool EnumerateAllWindows(EnumerateWindowsDelegate* delegate, int max_depth);
// Returns a list of top-level windows in top-to-bottom stacking order.
bool GetXWindowStack(std::vector<XID>* windows);
+// Restack a window in relation to one of its siblings. If |above| is true,
+// |window| will be stacked directly above |sibling|; otherwise it will stacked
+// directly below it. Both windows must be immediate children of the same
+// window.
+void RestackWindow(XID window, XID sibling, bool above);
+
// Return a handle to a server side pixmap. |shared_memory_key| is a SysV
// IPC key. The shared memory region must contain 32-bit pixels.
XID AttachSharedMemory(Display* display, int shared_memory_support);