Merge lp:~smspillaz/compiz-core/compiz-core.lim into lp:compiz-core

Proposed by Sam Spilsbury
Status: Superseded
Proposed branch: lp:~smspillaz/compiz-core/compiz-core.lim
Merge into: lp:compiz-core
Diff against target: 1087 lines (+808/-11)
15 files modified
gtk/window-decorator/CMakeLists.txt (+6/-0)
gtk/window-decorator/decorator.c (+32/-6)
gtk/window-decorator/events.c (+109/-3)
gtk/window-decorator/gtk-window-decorator.h (+7/-1)
gtk/window-decorator/local-menus/CMakeLists.txt (+60/-0)
gtk/window-decorator/local-menus/src/local-menus.c (+270/-0)
gtk/window-decorator/local-menus/src/local-menus.h (+91/-0)
gtk/window-decorator/local-menus/tests/CMakeLists.txt (+3/-0)
gtk/window-decorator/local-menus/tests/check_local_menu_on_off/CMakeLists.txt (+21/-0)
gtk/window-decorator/local-menus/tests/check_local_menu_on_off/test-local-menu-on-off.cpp (+20/-0)
gtk/window-decorator/local-menus/tests/force_local_menu_on/CMakeLists.txt (+21/-0)
gtk/window-decorator/local-menus/tests/force_local_menu_on/test-force-local-menu-on.cpp (+70/-0)
gtk/window-decorator/local-menus/tests/test-local-menu.h (+48/-0)
gtk/window-decorator/metacity.c (+48/-0)
gtk/window-decorator/wnck.c (+2/-1)
To merge this branch: bzr merge lp:~smspillaz/compiz-core/compiz-core.lim
Reviewer Review Type Date Requested Status
Daniel van Vugt Needs Fixing
Alan Griffiths Approve
Review via email: mp+91639@code.launchpad.net

This proposal supersedes a proposal from 2012-02-02.

This proposal has been superseded by a proposal from 2012-02-21.

Description of the change

Adds support for Ubuntu's "locally integrated menu bars" into gtk-window-decorator.

No tests as of yet, but I'll try and write some for

showing/hiding
forcing them on/off
positions etc

soonish.

To post a comment you must log in.
Revision history for this message
Alan Griffiths (alan-griffiths) wrote : Posted in a previous version of this proposal

8 +#define GWD_SHOW_LOCAL_MENU (WNCK_WINDOW_ACTION_BELOW << 1)

No plausible reason for using a macro. In C++ constants (enum or unsigned int) are better as they respect scope rules. Also, should this be inside "#ifdef META_HAS_LOCAL_MENUS" conditional?

296 button_layout->right_buttons[2] = META_BUTTON_FUNCTION_CLOSE;
297
298 - for (i = 3; i < MAX_BUTTONS_PER_CORNER; i++)
299 + for (i = 4; i < MAX_BUTTONS_PER_CORNER; i++)
300 button_layout->right_buttons[i] = META_BUTTON_FUNCTION_LAST;
301 }

I'm not sure what the magic number "3"->"4" is. But we now seem to miss 3 entirely. Is that correct?

Revision history for this message
Sam Spilsbury (smspillaz) wrote : Posted in a previous version of this proposal

> 8 +#define GWD_SHOW_LOCAL_MENU (WNCK_WINDOW_ACTION_BELOW << 1)
>
> No plausible reason for using a macro. In C++ constants (enum or unsigned
> int) are better as they respect scope rules. Also, should this be inside
> "#ifdef META_HAS_LOCAL_MENUS" conditional?
>

Its C

>
> 296 button_layout->right_buttons[2] = META_BUTTON_FUNCTION_CLOSE;
> 297
> 298 - for (i = 3; i < MAX_BUTTONS_PER_CORNER; i++)
> 299 + for (i = 4; i < MAX_BUTTONS_PER_CORNER; i++)
> 300 button_layout->right_buttons[i] = META_BUTTON_FUNCTION_LAST;
> 301 }
>
> I'm not sure what the magic number "3"->"4" is. But we now seem to miss 3
> entirely. Is that correct?

Its for initializing the array, though maybe thats wrong. Good find let me check

Revision history for this message
Daniel van Vugt (vanvugt) wrote : Posted in a previous version of this proposal

Please resubmit for target branch lp:compiz-core (0.9.7)

review: Needs Resubmitting
2987. By Sam Spilsbury

Show the flair in the right place

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

Looks better

review: Approve
2988. By Sam Spilsbury

Use the right key

2989. By Sam Spilsbury

Use the correct key

Revision history for this message
Sam Spilsbury (smspillaz) wrote :

(More work needed to adopt to trevino;s changes)

2990. By Sam Spilsbury

Merged from lp:compiz-core

Revision history for this message
Daniel van Vugt (vanvugt) wrote :

Fails to build ifndef META_HAS_LOCAL_MENUS

[ 0%] Building C object gtk/window-decorator/local-menus/CMakeFiles/gtk_window_decorator_local_menus.dir/src/local-menus.c.o
/home/dan/bzr/compiz-core/lim/gtk/window-decorator/local-menus/src/local-menus.c:36:1: error: ‘gwd_menu_mode_changed’ defined but not used [-Werror=unused-function]
/home/dan/bzr/compiz-core/lim/gtk/window-decorator/local-menus/src/local-menus.c:72:1: error: ‘on_local_menu_activated’ defined but not used [-Werror=unused-function]
cc1: all warnings being treated as errors

review: Needs Fixing
Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote : Posted in a previous version of this proposal

+ gboolean empty = g_strcmp0 (entry_id, "") == 0;

You can do the same with:
gboolean empty = (!entry_id || entry_id[0] == '\0');

Revision history for this message
Alan Griffiths (alan-griffiths) wrote :

If we're trying to be cryptic...

   gboolean empty = g_strcmp0 (entry_id, "") == 0;

can be written:

   gboolean empty = !entry_id || !*entry_id;

Revision history for this message
Marco Trevisan (Treviño) (3v1n0) wrote :

Ah, Sam remember to update the "EntryActivated" signal against the new signature s(iiuu).

Revision history for this message
Sam Spilsbury (smspillaz) wrote :

Yep, done

2991. By Sam Spilsbury

Merged from lp:compiz-core

2992. By Sam Spilsbury

Merged from lp:compiz-core

2993. By Sam Spilsbury

Merge lp:compiz-core

2994. By Sam Spilsbury

Fix incorrect variant and initial button layout

2995. By Sam Spilsbury

Fix build without META_HAS_LOCAL_MENUS

2996. By Sam Spilsbury

Also ifdef the local menus tests

2997. By Sam Spilsbury

Don't run global setup or teardown functions if no local menus

2998. By Sam Spilsbury

Merge lp:compiz-core and allow alt-accelerator to open menus as well as reading the xprop
to determine if local menus are applicable

2999. By Sam Spilsbury

Fix memory error and menu button not showing

3000. By Sam Spilsbury

Fix placement of menus on alt-accelerator, remove debug messages

3001. By Sam Spilsbury

Fix build failure without META_HAS_LOCAL_MENUS

3002. By Sam Spilsbury

Fix a crash when trying to show the window menu on a non decorated window

3003. By Sam Spilsbury

Tell us if we have them

3004. By Sam Spilsbury

Merged lp:compiz-core

3005. By Sam Spilsbury

We also need to test the property too

3006. By Sam Spilsbury

Grab buttons too so that we can process motion events on the titlebar and
move the window instead.

Unmerged revisions

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'gtk/window-decorator/CMakeLists.txt'
2--- gtk/window-decorator/CMakeLists.txt 2011-02-16 17:39:56 +0000
3+++ gtk/window-decorator/CMakeLists.txt 2012-02-07 07:03:18 +0000
4@@ -4,7 +4,10 @@
5 set (CMAKE_INSTALL_RPATH ${libdir})
6 endif (COMPIZ_BUILD_WITH_RPATH)
7
8+ add_subdirectory (local-menus)
9+
10 include_directories (
11+ ${CMAKE_CURRENT_SOURCE_DIR}/local-menus/src
12 ${compiz_SOURCE_DIR}/include
13 ${CMAKE_BINARY_DIR}/gtk
14 ${GTK_WINDOW_DECORATOR_INCLUDE_DIRS}
15@@ -63,6 +66,9 @@
16 target_link_libraries (
17 gtk-window-decorator
18 decoration
19+
20+ gtk_window_decorator_local_menus
21+
22 ${GTK_WINDOW_DECORATOR_LIBRARIES}
23 ${GCONF_LIBRARIES}
24 ${DBUS_GLIB_LIBRARIES}
25
26=== modified file 'gtk/window-decorator/decorator.c'
27--- gtk/window-decorator/decorator.c 2011-10-13 11:31:37 +0000
28+++ gtk/window-decorator/decorator.c 2012-02-07 07:03:18 +0000
29@@ -24,6 +24,7 @@
30 */
31
32 #include "gtk-window-decorator.h"
33+#include "local-menus.h"
34
35 decor_frame_t *
36 create_normal_frame (const gchar *type)
37@@ -230,12 +231,16 @@
38 void
39 update_event_windows (WnckWindow *win)
40 {
41+#define GWD_SHOW_LOCAL_MENU (WNCK_WINDOW_ACTION_BELOW << 1)
42 Display *xdisplay;
43 decor_t *d = g_object_get_data (G_OBJECT (win), "decor");
44 gint x0, y0, width, height, x, y, w, h;
45 gint i, j, k, l;
46 gint actions = d->actions;
47
48+ if (gwd_window_should_have_local_menu (win))
49+ d->actions |= GWD_SHOW_LOCAL_MENU;
50+
51 xdisplay = GDK_DISPLAY_XDISPLAY (gdk_display_get_default ());
52
53 /* Get the geometry of the client */
54@@ -298,11 +303,19 @@
55 XMapWindow (xdisplay, d->event_windows[i][j].window);
56 XMoveResizeWindow (xdisplay, d->event_windows[i][j].window,
57 x, y, w, h);
58+
59+ BoxPtr box = &d->event_windows[i][j].pos;
60+ box->x1 = x;
61+ box->x2 = x + w;
62+ box->y1 = y;
63+ box->y2 = y + h;
64 }
65 /* No parent and no geometry - unmap all event windows */
66 else if (!d->frame_window)
67 {
68 XUnmapWindow (xdisplay, d->event_windows[i][j].window);
69+
70+ memset (&d->event_windows[i][j].pos, 0, sizeof (Box));
71 }
72 }
73 }
74@@ -326,12 +339,18 @@
75 WNCK_WINDOW_ACTION_STICK,
76 WNCK_WINDOW_ACTION_UNSHADE,
77 WNCK_WINDOW_ACTION_ABOVE,
78- WNCK_WINDOW_ACTION_UNSTICK
79-#else
80- 0,
81- 0,
82- 0,
83- 0,
84+ WNCK_WINDOW_ACTION_UNSTICK,
85+#else
86+ 0,
87+ 0,
88+ 0,
89+ 0,
90+ 0,
91+#endif
92+
93+#ifdef META_HAS_LOCAL_MENUS
94+ GWD_SHOW_LOCAL_MENU
95+#else
96 0
97 #endif
98
99@@ -371,10 +390,17 @@
100 Window win = d->button_windows[i].window;
101 XMapWindow (xdisplay, win);
102 XMoveResizeWindow (xdisplay, win, x, y, w, h);
103+
104+ BoxPtr box = &d->button_windows[i].pos;
105+ box->x1 = x;
106+ box->x2 = x + w;
107+ box->y1 = y;
108+ box->y2 = y + h;
109 }
110 else if (!d->frame_window)
111 {
112 XUnmapWindow (xdisplay, d->button_windows[i].window);
113+ memset (&d->button_windows[i].pos, 0, sizeof (Box));
114 }
115 }
116
117
118=== modified file 'gtk/window-decorator/events.c'
119--- gtk/window-decorator/events.c 2011-10-13 12:22:14 +0000
120+++ gtk/window-decorator/events.c 2012-02-07 07:03:18 +0000
121@@ -24,6 +24,16 @@
122 */
123
124 #include "gtk-window-decorator.h"
125+#include "local-menus.h"
126+
127+typedef struct _delayed_move_info
128+{
129+ WnckWindow *win;
130+ int x_root;
131+ int y_root;
132+ unsigned int button;
133+ unsigned int time;
134+} delayed_move_info;
135
136 void
137 move_resize_window (WnckWindow *win,
138@@ -65,8 +75,9 @@
139 ev.xclient.data.l[3] = gtkwd_event->button;
140 ev.xclient.data.l[4] = 1;
141
142- XUngrabPointer (xdisplay, gtkwd_event->time);
143- XUngrabKeyboard (xdisplay, gtkwd_event->time);
144+ XAllowEvents (xdisplay, AsyncKeyboard | AsyncPointer, CurrentTime);
145+ XUngrabPointer (xdisplay, CurrentTime);
146+ XUngrabKeyboard (xdisplay, CurrentTime);
147
148 XSendEvent (xdisplay, xroot, FALSE,
149 SubstructureRedirectMask | SubstructureNotifyMask,
150@@ -75,6 +86,24 @@
151 XSync (xdisplay, FALSE);
152 }
153
154+static gboolean
155+move_resize_window_on_timeout (gpointer user_data)
156+{
157+ delayed_move_info *info = (delayed_move_info *) user_data;
158+
159+ decor_event event;
160+ event.x = 0;
161+ event.y = 0;
162+ event.x_root = info->x_root;
163+ event.y_root = info->y_root;
164+ event.button = info->button;
165+ event.window = wnck_window_get_xid (info->win);
166+
167+ move_resize_window (info->win, WM_MOVERESIZE_MOVE, &event);
168+
169+ return FALSE;
170+}
171+
172 void
173 common_button_event (WnckWindow *win,
174 decor_event *gtkwd_event,
175@@ -380,6 +409,73 @@
176 }
177
178 void
179+on_local_menu_hidden (gpointer user_data)
180+{
181+ decor_t *d = (decor_t *) user_data;
182+
183+ d->button_states[BUTTON_WINDOW_MENU] &= ~PRESSED_EVENT_WINDOW;
184+
185+ queue_decor_draw (d);
186+}
187+
188+void
189+window_menu_button_event (WnckWindow *win,
190+ decor_event *gtkwd_event,
191+ decor_event_type gtkwd_type)
192+{
193+ decor_t *d = g_object_get_data (G_OBJECT (win), "decor");
194+ guint state = d->button_states[BUTTON_WINDOW_MENU];
195+
196+ common_button_event (win, gtkwd_event, gtkwd_type,
197+ BUTTON_WINDOW_MENU, 1, _("Window Menu"));
198+
199+ switch (gtkwd_type) {
200+ case GButtonPress:
201+ if (gtkwd_event->button == 1)
202+ {
203+ if (d->button_states[BUTTON_WINDOW_MENU] & BUTTON_EVENT_ACTION_STATE)
204+ {
205+ delayed_move_info *info = g_new0 (delayed_move_info, 1);
206+
207+ info->button = gtkwd_event->button;
208+ info->time = gtkwd_event->time;
209+ info->win = win;
210+ info->x_root = gtkwd_event->x_root;
211+ info->y_root = gtkwd_event->y_root;
212+
213+ gwd_prepare_show_local_menu ((start_move_window_cb) move_resize_window_on_timeout, (gpointer) info);
214+ }
215+ }
216+ break;
217+ case GButtonRelease:
218+ if (gtkwd_event->button == 1)
219+ if (state)
220+ {
221+ int win_x, win_y;
222+ int box_x = d->button_windows[BUTTON_WINDOW_MENU].pos.x1;
223+
224+ wnck_window_get_geometry (win, &win_x, &win_y, NULL, NULL);
225+
226+ int x = win_x + box_x;
227+ int y = win_y + d->context->extents.top;
228+
229+ gwd_show_local_menu (gdk_x11_display_get_xdisplay (gdk_display_get_default ()),
230+ wnck_window_get_xid (win),
231+ x, y,
232+ box_x,
233+ d->context->extents.top,
234+ gtkwd_event->button,
235+ gtkwd_event->time,
236+ (show_window_menu_hidden_cb) on_local_menu_hidden,
237+ (gpointer) d);
238+ }
239+ break;
240+ default:
241+ break;
242+ }
243+}
244+
245+void
246 handle_title_button_event (WnckWindow *win,
247 int action,
248 decor_event *gtkwd_event)
249@@ -485,7 +581,17 @@
250
251 restack_window (win, Above);
252
253- move_resize_window (win, WM_MOVERESIZE_MOVE, gtkwd_event);
254+ delayed_move_info *info = g_new0 (delayed_move_info, 1);
255+
256+ info->x_root = gtkwd_event->x_root;
257+ info->y_root = gtkwd_event->y_root;
258+ info->button = gtkwd_event->button;
259+ info->time = gtkwd_event->time;
260+ info->win = win;
261+
262+ move_resize_window_on_timeout ((gpointer) info);
263+
264+ g_free (info);
265 }
266 }
267 else if (gtkwd_event->button == 2)
268
269=== modified file 'gtk/window-decorator/gtk-window-decorator.h'
270--- gtk/window-decorator/gtk-window-decorator.h 2011-10-13 12:22:14 +0000
271+++ gtk/window-decorator/gtk-window-decorator.h 2012-02-07 07:03:18 +0000
272@@ -328,7 +328,8 @@
273 #define BUTTON_UNSHADE 7
274 #define BUTTON_UNABOVE 8
275 #define BUTTON_UNSTICK 9
276-#define BUTTON_NUM 10
277+#define BUTTON_WINDOW_MENU 10
278+#define BUTTON_NUM 11
279
280 struct _pos {
281 int x, y, w, h;
282@@ -1013,6 +1014,11 @@
283 decor_event_type gtkwd_type);
284
285 void
286+window_menu_button_event (WnckWindow *win,
287+ decor_event *gtkwd_event,
288+ decor_event_type gtkwd_type);
289+
290+void
291 handle_title_button_event (WnckWindow *win,
292 int action,
293 decor_event *gtkwd_event);
294
295=== added directory 'gtk/window-decorator/local-menus'
296=== added file 'gtk/window-decorator/local-menus/CMakeLists.txt'
297--- gtk/window-decorator/local-menus/CMakeLists.txt 1970-01-01 00:00:00 +0000
298+++ gtk/window-decorator/local-menus/CMakeLists.txt 2012-02-07 07:03:18 +0000
299@@ -0,0 +1,60 @@
300+pkg_check_modules(
301+ LOCAL_MENUS
302+ REQUIRED
303+ glib-2.0 gio-2.0 libwnck-1.0 gtk+-2.0>=2.18.0
304+)
305+
306+INCLUDE_DIRECTORIES(
307+ ${CMAKE_CURRENT_SOURCE_DIR}/include
308+ ${CMAKE_CURRENT_SOURCE_DIR}/src
309+
310+ ${compiz_SOURCE_DIR}/gtk/window-decorator
311+
312+ ${Boost_INCLUDE_DIRS}
313+
314+ ${LOCAL_MENUS_INCLUDE_DIRS}
315+ ${METACITY_INCLUDE_DIRS}
316+)
317+
318+LINK_DIRECTORIES (${LOCAL_MENUS_LIBRARY_DIRS})
319+
320+SET(
321+ PUBLIC_HEADERS
322+)
323+
324+SET(
325+ PRIVATE_HEADERS
326+ ${CMAKE_CURRENT_SOURCE_DIR}/src/local-menus.h
327+)
328+
329+SET(
330+ SRCS
331+ ${CMAKE_CURRENT_SOURCE_DIR}/src/local-menus.c
332+)
333+
334+ADD_LIBRARY(
335+ gtk_window_decorator_local_menus STATIC
336+
337+ ${SRCS}
338+
339+ ${PUBLIC_HEADERS}
340+ ${PRIVATE_HEADERS}
341+)
342+
343+IF (COMPIZ_BUILD_TESTING)
344+ADD_SUBDIRECTORY( ${CMAKE_CURRENT_SOURCE_DIR}/tests )
345+ENDIF (COMPIZ_BUILD_TESTING)
346+
347+SET_TARGET_PROPERTIES(
348+ gtk_window_decorator_local_menus PROPERTIES
349+ PUBLIC_HEADER "${PUBLIC_HEADERS}"
350+)
351+
352+install (FILES ${PUBLIC_HEADERS} DESTINATION ${COMPIZ_CORE_INCLUDE_DIR})
353+
354+TARGET_LINK_LIBRARIES(
355+ gtk_window_decorator_local_menus
356+
357+ ${LOCAL_MENUS_LIBRARIES}
358+)
359+
360
361=== added directory 'gtk/window-decorator/local-menus/include'
362=== added directory 'gtk/window-decorator/local-menus/src'
363=== added file 'gtk/window-decorator/local-menus/src/local-menus.c'
364--- gtk/window-decorator/local-menus/src/local-menus.c 1970-01-01 00:00:00 +0000
365+++ gtk/window-decorator/local-menus/src/local-menus.c 2012-02-07 07:03:18 +0000
366@@ -0,0 +1,270 @@
367+/*
368+ * Copyright © 2006 Novell, Inc.
369+ *
370+ * This library is free software; you can redistribute it and/or
371+ * modify it under the terms of the GNU Lesser General Public
372+ * License as published by the Free Software Foundation; either
373+ * version 2 of the License, or (at your option) any later version.
374+ *
375+ * This library is distributed in the hope that it will be useful,
376+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
377+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
378+ * Lesser General Public License for more details.
379+ *
380+ * You should have received a copy of the GNU Lesser General Public
381+ * License along with this library; if not, write to the
382+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
383+ * Boston, MA 02111-1307, USA.
384+ *
385+ * Author: David Reveman <davidr@novell.com>
386+ *
387+ * 2D Mode: Copyright © 2010 Sam Spilsbury <smspillaz@gmail.com>
388+ * Frames Management: Copright © 2011 Canonical Ltd.
389+ * Authored By: Sam Spilsbury <sam.spilsbury@canonical.com>
390+ */
391+
392+#include "local-menus.h"
393+#include <gdk/gdk.h>
394+#include <gdk/gdkx.h>
395+
396+#define GLOBAL 0
397+#define LOCAL 1
398+
399+gint menu_mode = GLOBAL;
400+
401+static void
402+gwd_menu_mode_changed (GSettings *settings,
403+ gchar *key,
404+ gpointer user_data)
405+{
406+ menu_mode = g_settings_get_enum (settings, "menu-mode");
407+}
408+
409+active_local_menu *active_menu;
410+pending_local_menu *pending_menu;
411+
412+gboolean
413+gwd_window_should_have_local_menu (WnckWindow *win)
414+{
415+#ifdef META_HAS_LOCAL_MENUS
416+ const gchar * const *schemas = g_settings_list_schemas ();
417+ static GSettings *lim_settings = NULL;
418+ while (*schemas != NULL && !lim_settings)
419+ {
420+ if (g_str_equal (*schemas, "com.canonical.indicator.appmenu"))
421+ {
422+ lim_settings = g_settings_new ("com.canonical.indicator.appmenu");
423+ menu_mode = g_settings_get_enum (lim_settings, "menu-mode");
424+ g_signal_connect (lim_settings, "changed::menu-mode", G_CALLBACK (gwd_menu_mode_changed), NULL);
425+ break;
426+ }
427+ ++schemas;
428+ }
429+
430+ if (lim_settings)
431+ return menu_mode;
432+#endif
433+
434+ return FALSE;
435+}
436+
437+static void
438+on_local_menu_activated (GDBusProxy *proxy,
439+ gchar *sender_name,
440+ gchar *signal_name,
441+ GVariant *parameters,
442+ gpointer user_data)
443+{
444+#ifdef META_HAS_LOCAL_MENUS
445+ if (g_strcmp0 (signal_name, "EntryActivated") == 0)
446+ {
447+ gchar *entry_id = NULL;
448+
449+ g_variant_get (parameters, "(s)", &entry_id, NULL);
450+
451+ gboolean empty = g_strcmp0 (entry_id, "") == 0;
452+
453+ if (empty)
454+ {
455+ show_local_menu_data *d = (show_local_menu_data *) user_data;
456+
457+ (*d->cb) (d->user_data);
458+
459+ g_signal_handlers_disconnect_by_func (d->proxy, on_local_menu_activated, d);
460+
461+ g_object_unref (d->proxy);
462+ g_object_unref (d->conn);
463+
464+ if (active_menu)
465+ {
466+ g_free (active_menu);
467+ active_menu = NULL;
468+ }
469+ }
470+ }
471+#endif
472+}
473+
474+gboolean
475+gwd_move_window_instead (gpointer user_data)
476+{
477+ (*pending_menu->cb) (pending_menu->user_data);
478+ g_free (pending_menu->user_data);
479+ g_free (pending_menu);
480+ pending_menu = NULL;
481+ return FALSE;
482+}
483+
484+void
485+gwd_prepare_show_local_menu (start_move_window_cb start_move_window,
486+ gpointer user_data_start_move_window)
487+{
488+ if (pending_menu)
489+ {
490+ g_source_remove (pending_menu->move_timeout_id);
491+ g_free (pending_menu->user_data);
492+ g_free (pending_menu);
493+ pending_menu = NULL;
494+ }
495+
496+ pending_menu = g_new0 (pending_local_menu, 1);
497+ pending_menu->cb = start_move_window;
498+ pending_menu->user_data = user_data_start_move_window;
499+ pending_menu->move_timeout_id = g_timeout_add (150, gwd_move_window_instead, pending_menu);
500+}
501+
502+void
503+gwd_show_local_menu (Display *xdisplay,
504+ Window frame_xwindow,
505+ int x,
506+ int y,
507+ int x_win,
508+ int y_win,
509+ int button,
510+ guint32 timestamp,
511+ show_window_menu_hidden_cb cb,
512+ gpointer user_data_show_window_menu)
513+{
514+ if (pending_menu)
515+ {
516+ g_source_remove (pending_menu->move_timeout_id);
517+ g_free (pending_menu->user_data);
518+ g_free (pending_menu);
519+ pending_menu = NULL;
520+ }
521+
522+#ifdef META_HAS_LOCAL_MENUS
523+ GError *error = NULL;
524+ GDBusConnection *conn = g_bus_get_sync (G_BUS_TYPE_SESSION, NULL, &error);
525+ gint x_out, y_out;
526+ guint width, height;
527+
528+ XUngrabPointer (gdk_x11_display_get_xdisplay (gdk_display_get_default ()), CurrentTime);
529+ XUngrabKeyboard (gdk_x11_display_get_xdisplay (gdk_display_get_default ()), CurrentTime);
530+ XSync (gdk_x11_display_get_xdisplay (gdk_display_get_default ()), FALSE);
531+
532+ if (!conn)
533+ {
534+ g_print ("error getting connection: %s\n", error->message);
535+ return;
536+ }
537+
538+ GDBusProxy *proxy = g_dbus_proxy_new_sync (conn, 0, NULL, "com.canonical.Unity.Panel.Service",
539+ "/com/canonical/Unity/Panel/Service",
540+ "com.canonical.Unity.Panel.Service",
541+ NULL, &error);
542+
543+ if (proxy)
544+ {
545+ GVariant *message = g_variant_new ("(uiiu)", frame_xwindow, x, y, time);
546+ GVariant *reply = g_dbus_proxy_call_sync (proxy, "ShowAppMenu", message, 0, 500, NULL, &error);
547+ if (error)
548+ {
549+ g_print ("error calling ShowAppMenu: %s\n", error->message);
550+ g_object_unref (proxy);
551+ g_object_unref (conn);
552+ return;
553+ }
554+
555+ show_local_menu_data *data = g_new0 (show_local_menu_data, 1);
556+ g_variant_get (reply, "(iiuu)", &x_out, &y_out, &width, &height, NULL);
557+
558+ if (active_menu)
559+ g_free (active_menu);
560+
561+ active_menu = g_new0 (active_local_menu, 1);
562+
563+ active_menu->rect.x = x_out - (x - x_win);
564+ active_menu->rect.y = y_out - (x - y_win);
565+ active_menu->rect.width = width;
566+ active_menu->rect.height = height;
567+
568+ data->conn = g_object_ref (conn);
569+ data->proxy = g_object_ref (proxy);
570+ data->cb = cb;
571+ data->user_data = user_data_show_window_menu;
572+
573+ g_signal_connect (proxy, "g-signal", G_CALLBACK (on_local_menu_activated), data);
574+
575+ g_object_unref (conn);
576+ g_object_unref (proxy);
577+
578+ return;
579+ }
580+ else
581+ {
582+ g_print ("error getting proxy: %s\n", error->message);
583+ }
584+
585+ g_object_unref (conn);
586+#endif
587+}
588+
589+void
590+force_local_menus_on (WnckWindow *win,
591+ MetaButtonLayout *button_layout)
592+{
593+#ifdef META_HAS_LOCAL_MENUS
594+ if (gwd_window_should_have_local_menu (win))
595+ {
596+ if (button_layout->left_buttons[0] != META_BUTTON_FUNCTION_LAST)
597+ {
598+ unsigned int i = 0;
599+ for (; i < MAX_BUTTONS_PER_CORNER; i++)
600+ {
601+ if (button_layout->left_buttons[i] == META_BUTTON_FUNCTION_WINDOW_MENU)
602+ break;
603+ else if (button_layout->left_buttons[i] == META_BUTTON_FUNCTION_LAST)
604+ {
605+ if ((i + 1) < MAX_BUTTONS_PER_CORNER)
606+ {
607+ button_layout->left_buttons[i + 1] = META_BUTTON_FUNCTION_LAST;
608+ button_layout->left_buttons[i] = META_BUTTON_FUNCTION_WINDOW_MENU;
609+ break;
610+ }
611+ }
612+ }
613+ }
614+ if (button_layout->right_buttons[0] != META_BUTTON_FUNCTION_LAST)
615+ {
616+ unsigned int i = 0;
617+ for (; i < MAX_BUTTONS_PER_CORNER; i++)
618+ {
619+ if (button_layout->right_buttons[i] == META_BUTTON_FUNCTION_WINDOW_MENU)
620+ break;
621+ else if (button_layout->right_buttons[i] == META_BUTTON_FUNCTION_LAST)
622+ {
623+ if ((i + 1) < MAX_BUTTONS_PER_CORNER)
624+ {
625+ button_layout->right_buttons[i + 1] = META_BUTTON_FUNCTION_LAST;
626+ button_layout->right_buttons[i] = META_BUTTON_FUNCTION_WINDOW_MENU;
627+ break;
628+ }
629+ }
630+ }
631+ }
632+ }
633+#endif
634+}
635+
636+
637
638=== added file 'gtk/window-decorator/local-menus/src/local-menus.h'
639--- gtk/window-decorator/local-menus/src/local-menus.h 1970-01-01 00:00:00 +0000
640+++ gtk/window-decorator/local-menus/src/local-menus.h 2012-02-07 07:03:18 +0000
641@@ -0,0 +1,91 @@
642+/*
643+ * Copyright © 2006 Novell, Inc.
644+ *
645+ * This library is free software; you can redistribute it and/or
646+ * modify it under the terms of the GNU Lesser General Public
647+ * License as published by the Free Software Foundation; either
648+ * version 2 of the License, or (at your option) any later version.
649+ *
650+ * This library is distributed in the hope that it will be useful,
651+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
652+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
653+ * Lesser General Public License for more details.
654+ *
655+ * You should have received a copy of the GNU Lesser General Public
656+ * License along with this library; if not, write to the
657+ * Free Software Foundation, Inc., 59 Temple Place - Suite 330,
658+ * Boston, MA 02111-1307, USA.
659+ *
660+ * Author: David Reveman <davidr@novell.com>
661+ *
662+ * 2D Mode: Copyright © 2010 Sam Spilsbury <smspillaz@gmail.com>
663+ * Frames Management: Copright © 2011 Canonical Ltd.
664+ * Authored By: Sam Spilsbury <sam.spilsbury@canonical.com>
665+ */
666+
667+#ifdef __cplusplus
668+extern "C"
669+{
670+#endif
671+
672+#include <glib.h>
673+#include <gio/gio.h>
674+#ifndef WNCK_I_KNOW_THIS_IS_UNSTABLE
675+#define WNCK_I_KNOW_THIS_IS_UNSTABLE
676+#endif
677+#include <libwnck/libwnck.h>
678+#include <libwnck/window-action-menu.h>
679+#include <metacity-private/theme.h>
680+
681+typedef void (*show_window_menu_hidden_cb) (gpointer);
682+typedef void (*start_move_window_cb) (gpointer);
683+
684+typedef struct _pending_local_menu
685+{
686+ gint move_timeout_id;
687+ gpointer user_data;
688+ start_move_window_cb cb;
689+} pending_local_menu;
690+
691+typedef struct _active_local_menu
692+{
693+ GdkRectangle rect;
694+} active_local_menu;
695+
696+extern active_local_menu *active_menu;
697+
698+typedef struct _show_local_menu_data
699+{
700+ GDBusConnection *conn;
701+ GDBusProxy *proxy;
702+ show_window_menu_hidden_cb cb;
703+ gpointer user_data;
704+} show_local_menu_data;
705+
706+gboolean
707+gwd_window_should_have_local_menu (WnckWindow *win);
708+
709+void
710+force_local_menus_on (WnckWindow *win,
711+ MetaButtonLayout *layout);
712+
713+/* Button Down */
714+void
715+gwd_prepare_show_local_menu (start_move_window_cb start_move_window,
716+ gpointer user_data_start_move_window);
717+
718+/* Button Up */
719+void
720+gwd_show_local_menu (Display *xdisplay,
721+ Window frame_xwindow,
722+ int x,
723+ int y,
724+ int x_win,
725+ int y_win,
726+ int button,
727+ guint32 timestamp,
728+ show_window_menu_hidden_cb cb,
729+ gpointer user_data_show_window_menu);
730+#ifdef __cplusplus
731+}
732+#endif
733
734=== added directory 'gtk/window-decorator/local-menus/tests'
735=== added file 'gtk/window-decorator/local-menus/tests/CMakeLists.txt'
736--- gtk/window-decorator/local-menus/tests/CMakeLists.txt 1970-01-01 00:00:00 +0000
737+++ gtk/window-decorator/local-menus/tests/CMakeLists.txt 2012-02-07 07:03:18 +0000
738@@ -0,0 +1,3 @@
739+include_directories (${CMAKE_CURRENT_SOURCE_DIR})
740+add_subdirectory (check_local_menu_on_off)
741+add_subdirectory (force_local_menu_on)
742
743=== added directory 'gtk/window-decorator/local-menus/tests/check_local_menu_on_off'
744=== added file 'gtk/window-decorator/local-menus/tests/check_local_menu_on_off/CMakeLists.txt'
745--- gtk/window-decorator/local-menus/tests/check_local_menu_on_off/CMakeLists.txt 1970-01-01 00:00:00 +0000
746+++ gtk/window-decorator/local-menus/tests/check_local_menu_on_off/CMakeLists.txt 2012-02-07 07:03:18 +0000
747@@ -0,0 +1,21 @@
748+include_directories (${CMAKE_CURRENT_SOURCE_DIR}
749+ ${compiz_SOURCE_DIR}/gtk/window-decorator/local-menus/tests
750+ ${compiz_SOURCE_DIR}/gtk/window-decorator
751+ ${compiz_SOURCE_DIR}/gtk/window-decorator/local-menus/src)
752+
753+add_executable(
754+ gtk_window_decorator_check_local_menu_on_off_test
755+
756+ ${CMAKE_CURRENT_SOURCE_DIR}/test-local-menu-on-off.cpp
757+)
758+
759+target_link_libraries(
760+ gtk_window_decorator_check_local_menu_on_off_test
761+
762+ gtk_window_decorator_local_menus
763+
764+ ${GTEST_BOTH_LIBRARIES}
765+ ${CMAKE_THREAD_LIBS_INIT} # Link in pthread.
766+)
767+
768+add_test (gtk_window_decorator_local_menus_on_off gtk_window_decorator_check_local_menu_on_off_test)
769
770=== added directory 'gtk/window-decorator/local-menus/tests/check_local_menu_on_off/check_local_menu_on_off'
771=== added directory 'gtk/window-decorator/local-menus/tests/check_local_menu_on_off/check_local_menu_on_off/CMakeFiles'
772=== added file 'gtk/window-decorator/local-menus/tests/check_local_menu_on_off/test-local-menu-on-off.cpp'
773--- gtk/window-decorator/local-menus/tests/check_local_menu_on_off/test-local-menu-on-off.cpp 1970-01-01 00:00:00 +0000
774+++ gtk/window-decorator/local-menus/tests/check_local_menu_on_off/test-local-menu-on-off.cpp 2012-02-07 07:03:18 +0000
775@@ -0,0 +1,20 @@
776+#include "test-local-menu.h"
777+
778+#define GLOBAL 0
779+#define LOCAL 1
780+
781+TEST_F (GtkWindowDecoratorTestLocalMenu, TestOn)
782+{
783+ g_settings_set_enum (getSettings (), "menu-mode", LOCAL);
784+ gboolean result = gwd_window_should_have_local_menu (getWindow ());
785+
786+ EXPECT_TRUE (result);
787+}
788+
789+TEST_F (GtkWindowDecoratorTestLocalMenu, TestOff)
790+{
791+ g_settings_set_enum (getSettings (), "menu-mode", GLOBAL);
792+ gboolean result = gwd_window_should_have_local_menu (getWindow ());
793+
794+ EXPECT_FALSE (result);
795+}
796
797=== added directory 'gtk/window-decorator/local-menus/tests/force_local_menu_on'
798=== added file 'gtk/window-decorator/local-menus/tests/force_local_menu_on/CMakeLists.txt'
799--- gtk/window-decorator/local-menus/tests/force_local_menu_on/CMakeLists.txt 1970-01-01 00:00:00 +0000
800+++ gtk/window-decorator/local-menus/tests/force_local_menu_on/CMakeLists.txt 2012-02-07 07:03:18 +0000
801@@ -0,0 +1,21 @@
802+include_directories (${CMAKE_CURRENT_SOURCE_DIR}
803+ ${compiz_SOURCE_DIR}/gtk/window-decorator/local-menus/tests
804+ ${compiz_SOURCE_DIR}/gtk/window-decorator
805+ ${compiz_SOURCE_DIR}/gtk/window-decorator/local-menus/src)
806+
807+add_executable(
808+ gtk_window_decorator_force_local_menu_on_test
809+
810+ ${CMAKE_CURRENT_SOURCE_DIR}/test-force-local-menu-on.cpp
811+)
812+
813+target_link_libraries(
814+ gtk_window_decorator_force_local_menu_on_test
815+
816+ gtk_window_decorator_local_menus
817+
818+ ${GTEST_BOTH_LIBRARIES}
819+ ${CMAKE_THREAD_LIBS_INIT} # Link in pthread.
820+)
821+
822+add_test (gtk_window_decorator_force_local_menu_on gtk_window_decorator_force_local_menu_on_test)
823
824=== added file 'gtk/window-decorator/local-menus/tests/force_local_menu_on/test-force-local-menu-on.cpp'
825--- gtk/window-decorator/local-menus/tests/force_local_menu_on/test-force-local-menu-on.cpp 1970-01-01 00:00:00 +0000
826+++ gtk/window-decorator/local-menus/tests/force_local_menu_on/test-force-local-menu-on.cpp 2012-02-07 07:03:18 +0000
827@@ -0,0 +1,70 @@
828+#include "test-local-menu.h"
829+#include <cstring>
830+
831+#define GLOBAL 0
832+#define LOCAL 1
833+
834+namespace
835+{
836+ void initializeMetaButtonLayout (MetaButtonLayout *layout)
837+ {
838+ memset (layout, 0, sizeof (MetaButtonLayout));
839+
840+ unsigned int i;
841+
842+ for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++)
843+ {
844+ layout->right_buttons[i] = META_BUTTON_FUNCTION_LAST;
845+ layout->left_buttons[i] = META_BUTTON_FUNCTION_LAST;
846+ }
847+ }
848+}
849+
850+class GtkWindowDecoratorTestLocalMenuLayout :
851+ public GtkWindowDecoratorTestLocalMenu
852+{
853+ public:
854+
855+ MetaButtonLayout * getLayout () { return &mLayout; }
856+
857+ virtual void SetUp ()
858+ {
859+ GtkWindowDecoratorTestLocalMenu::SetUp ();
860+ ::initializeMetaButtonLayout (&mLayout);
861+ g_settings_set_enum (getSettings (), "menu-mode", LOCAL);
862+ }
863+
864+ private:
865+
866+ MetaButtonLayout mLayout;
867+};
868+
869+TEST_F (GtkWindowDecoratorTestLocalMenuLayout, TestForceNoButtonsSet)
870+{
871+ force_local_menus_on (getWindow (), getLayout ());
872+
873+ EXPECT_EQ (getLayout ()->right_buttons[0], META_BUTTON_FUNCTION_LAST);
874+ EXPECT_EQ (getLayout ()->left_buttons[0], META_BUTTON_FUNCTION_LAST);
875+}
876+
877+TEST_F (GtkWindowDecoratorTestLocalMenuLayout, TestForceNoCloseButtonSet)
878+{
879+ getLayout ()->right_buttons[0] = META_BUTTON_FUNCTION_CLOSE;
880+
881+ force_local_menus_on (getWindow (), getLayout ());
882+
883+ EXPECT_EQ (getLayout ()->right_buttons[1], META_BUTTON_FUNCTION_WINDOW_MENU);
884+ EXPECT_EQ (getLayout ()->left_buttons[0], META_BUTTON_FUNCTION_LAST);
885+}
886+
887+TEST_F (GtkWindowDecoratorTestLocalMenuLayout, TestForceNoCloseMinimizeMaximizeButtonSet)
888+{
889+ getLayout ()->left_buttons[0] = META_BUTTON_FUNCTION_CLOSE;
890+ getLayout ()->left_buttons[1] = META_BUTTON_FUNCTION_CLOSE;
891+ getLayout ()->left_buttons[2] = META_BUTTON_FUNCTION_CLOSE;
892+
893+ force_local_menus_on (getWindow (), getLayout ());
894+
895+ EXPECT_EQ (getLayout ()->right_buttons[0], META_BUTTON_FUNCTION_LAST);
896+ EXPECT_EQ (getLayout ()->left_buttons[3], META_BUTTON_FUNCTION_WINDOW_MENU);
897+}
898
899=== added directory 'gtk/window-decorator/local-menus/tests/show_hide_menu'
900=== added file 'gtk/window-decorator/local-menus/tests/test-local-menu.h'
901--- gtk/window-decorator/local-menus/tests/test-local-menu.h 1970-01-01 00:00:00 +0000
902+++ gtk/window-decorator/local-menus/tests/test-local-menu.h 2012-02-07 07:03:18 +0000
903@@ -0,0 +1,48 @@
904+#include <gtest/gtest.h>
905+#include <gmock/gmock.h>
906+#include "local-menus.h"
907+#include <X11/Xlib.h>
908+#include <gio/gio.h>
909+
910+class GtkWindowDecoratorTestLocalMenu :
911+ public ::testing::Test
912+{
913+ public:
914+
915+ WnckWindow * getWindow () { return mWindow; }
916+ GSettings * getSettings () { return mSettings; }
917+
918+ virtual void SetUp ()
919+ {
920+ gtk_init (NULL, NULL);
921+
922+ mXDisplay = XOpenDisplay (NULL);
923+ mXWindow = XCreateSimpleWindow (mXDisplay, DefaultRootWindow (mXDisplay), 0, 0, 100, 100, 0, 0, 0);
924+
925+ XMapRaised (mXDisplay, mXWindow);
926+
927+ XFlush (mXDisplay);
928+
929+ while (g_main_context_iteration (g_main_context_default (), FALSE));
930+
931+ mWindow = wnck_window_get (mXWindow);
932+
933+ g_setenv("GSETTINGS_BACKEND", "memory", true);
934+ mSettings = g_settings_new ("com.canonical.indicator.appmenu");
935+ }
936+
937+ virtual void TearDown ()
938+ {
939+ XDestroyWindow (mXDisplay, mXWindow);
940+ XCloseDisplay (mXDisplay);
941+
942+ g_object_unref (mSettings);
943+ }
944+
945+ private:
946+
947+ WnckWindow *mWindow;
948+ Window mXWindow;
949+ Display *mXDisplay;
950+ GSettings *mSettings;
951+};
952
953=== modified file 'gtk/window-decorator/metacity.c'
954--- gtk/window-decorator/metacity.c 2012-01-12 06:48:58 +0000
955+++ gtk/window-decorator/metacity.c 2012-02-07 07:03:18 +0000
956@@ -24,6 +24,7 @@
957 */
958
959 #include "gtk-window-decorator.h"
960+#include "local-menus.h"
961
962 #ifdef USE_METACITY
963
964@@ -488,6 +489,8 @@
965 button_layout->right_buttons[i] = META_BUTTON_FUNCTION_LAST;
966 }
967
968+ force_local_menus_on (d->win, button_layout);
969+
970 *flags = 0;
971
972 if (d->actions & WNCK_WINDOW_ACTION_CLOSE)
973@@ -558,6 +561,15 @@
974 else
975 clip->height = d->border_layout.left.y2 - d->border_layout.left.y1;
976
977+ memset (fgeom, 0, sizeof (MetaFrameGeometry));
978+
979+#ifdef META_HAS_LOCAL_MENUS
980+
981+ if (d->layout)
982+ fgeom->text_layout = g_object_ref (d->layout);
983+
984+#endif
985+
986 meta_theme_calc_geometry (theme,
987 frame_type,
988 d->frame->text_height,
989@@ -567,6 +579,15 @@
990 button_layout,
991 fgeom);
992
993+#ifdef META_HAS_LOCAL_MENUS
994+
995+ if (d->layout)
996+ g_object_unref (fgeom->text_layout);
997+
998+ fgeom->text_layout = NULL;
999+
1000+#endif
1001+
1002 clip->width += left_width + right_width;
1003 clip->height += top_height + bottom_height;
1004 }
1005@@ -601,6 +622,11 @@
1006 GdkColor bg_color;
1007 double bg_alpha;
1008
1009+ memset (&button_layout, 0, sizeof (MetaButtonLayout));
1010+
1011+ for (i = 0; i < MAX_BUTTONS_PER_CORNER; i++)
1012+ button_layout.left_buttons[i] = button_layout.right_buttons[i] = META_BUTTON_FUNCTION_LAST;
1013+
1014 if (!d->pixmap || !d->picture)
1015 return;
1016
1017@@ -675,6 +701,9 @@
1018
1019 size = MAX (fgeom.top_height, fgeom.bottom_height);
1020
1021+ if (active_menu)
1022+ g_object_set_data (G_OBJECT (style_window), "local_menu_rect", &active_menu->rect);
1023+
1024 if (rect.width && size)
1025 {
1026 XRenderPictFormat *format;
1027@@ -899,6 +928,9 @@
1028 XRenderFreePicture (xdisplay, src);
1029 }
1030
1031+ if (active_menu)
1032+ g_object_set_data (G_OBJECT (style_window), "local_menu_rect", NULL);
1033+
1034 copy_to_front_buffer (d);
1035
1036 if (d->frame_window)
1037@@ -1078,6 +1110,15 @@
1038 break;
1039 #endif
1040
1041+#ifdef META_HAS_LOCAL_MENUS
1042+ case BUTTON_WINDOW_MENU:
1043+ if (!meta_button_present (&button_layout, META_BUTTON_FUNCTION_WINDOW_MENU))
1044+ return FALSE;
1045+
1046+ space = &fgeom.window_menu_rect;
1047+ break;
1048+#endif
1049+
1050 default:
1051 return FALSE;
1052 }
1053@@ -1266,6 +1307,8 @@
1054 MetaTheme *theme;
1055 GdkRectangle clip;
1056
1057+ memset (&fgeom, 0, sizeof (MetaFrameGeometry));
1058+
1059 theme = meta_theme_get_current ();
1060
1061 meta_get_decoration_geometry (d, theme, &flags, &fgeom, &button_layout,
1062@@ -1490,6 +1533,11 @@
1063 return META_BUTTON_FUNCTION_UNSTICK;
1064 #endif
1065
1066+#ifdef META_HAS_LOCAL_MENUS
1067+ else if (strcmp (str, "window_menu") == 0)
1068+ return META_BUTTON_FUNCTION_WINDOW_MENU;
1069+#endif
1070+
1071 else
1072 return META_BUTTON_FUNCTION_LAST;
1073 }
1074
1075=== modified file 'gtk/window-decorator/wnck.c'
1076--- gtk/window-decorator/wnck.c 2011-10-13 09:53:38 +0000
1077+++ gtk/window-decorator/wnck.c 2012-02-07 07:03:18 +0000
1078@@ -727,7 +727,8 @@
1079 stick_button_event,
1080 unshade_button_event,
1081 unabove_button_event,
1082- unstick_button_event
1083+ unstick_button_event,
1084+ window_menu_button_event
1085 };
1086
1087 d = calloc (1, sizeof (decor_t));

Subscribers

People subscribed via source and target branches