diff options
-rw-r--r-- | chrome/browser/gtk/bookmark_bubble_gtk.cc | 19 | ||||
-rw-r--r-- | chrome/browser/gtk/bookmark_bubble_gtk.h | 6 | ||||
-rw-r--r-- | chrome/browser/gtk/browser_window_gtk.cc | 9 | ||||
-rw-r--r-- | chrome/browser/gtk/first_run_bubble.cc | 8 | ||||
-rw-r--r-- | chrome/browser/gtk/info_bubble_gtk.cc | 147 | ||||
-rw-r--r-- | chrome/browser/gtk/info_bubble_gtk.h | 30 | ||||
-rw-r--r-- | chrome/common/x11_util.cc | 29 | ||||
-rw-r--r-- | chrome/common/x11_util.h | 12 |
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); |