1 /* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */
2 
3 /* Metacity visual bell */
4 
5 /*
6  * Copyright (C) 2002 Sun Microsystems Inc.
7  * Copyright (C) 2005, 2006 Elijah Newren
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of the
12  * License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful, but
15  * WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17  * General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program; if not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 /**
24  * \file bell.c Ring the bell or flash the screen
25  *
26  * Sometimes, X programs "ring the bell", whatever that means. Metacity lets
27  * the user configure the bell to be audible or visible (aka visual), and
28  * if it's visual it can be configured to be frame-flash or fullscreen-flash.
29  * We never get told about audible bells; X handles them just fine by itself.
30  *
31  * Visual bells come in at meta_bell_notify(), which checks we are actually
32  * in visual mode and calls through to bell_visual_notify(). That
33  * function then checks what kind of visual flash you like, and calls either
34  * bell_flash_fullscreen()-- which calls bell_flash_screen() to do
35  * its work-- or bell_flash_frame(), which flashes the focussed window
36  * using bell_flash_window_frame(), unless there is no such window, in
37  * which case it flashes the screen instead. bell_flash_window_frame()
38  * flashes the frame and calls bell_unflash_frame() as a timeout to
39  * remove the flash.
40  *
41  * The visual bell was the result of a discussion in Bugzilla here:
42  * <http://bugzilla.gnome.org/show_bug.cgi?id=99886>.
43  *
44  * Several of the functions in this file are ifdeffed out entirely if we are
45  * found not to have the XKB extension, which is required to do these clever
46  * things with bells; some others are entirely no-ops in that case.
47  */
48 
49 #include <config.h>
50 #include "bell.h"
51 #include "screen-private.h"
52 #include "prefs.h"
53 
54 #ifdef HAVE_CANBERRA
55 #include <canberra-gtk.h>
56 #endif
57 
58 /**
59  * Flashes one entire screen.  This is done by making a window the size of the
60  * whole screen (or reusing the old one, if it's still around), mapping it,
61  * painting it white and then black, and then unmapping it. We set saveunder so
62  * that all the windows behind it come back immediately.
63  *
64  * Unlike frame flashes, we don't do fullscreen flashes with a timeout; rather,
65  * we do them in one go, because we don't have to rely on the theme code
66  * redrawing the frame for us in order to do the flash.
67  *
68  * \param display  The display which owns the screen (rather redundant)
69  * \param screen   The screen to flash
70  *
71  * \bug The way I read it, this appears not to do the flash
72  * the first time we flash a particular display. Am I wrong?
73  *
74  * \bug This appears to destroy our current XSync status.
75  */
76 static void
bell_flash_screen(MetaDisplay * display,MetaScreen * screen)77 bell_flash_screen (MetaDisplay *display,
78                    MetaScreen  *screen)
79 {
80   Window root = screen->xroot;
81   int width = screen->rect.width;
82   int height = screen->rect.height;
83 
84   if (screen->flash_window == None)
85     {
86       Visual *visual = (Visual *)CopyFromParent;
87       XSetWindowAttributes xswa;
88       int depth = CopyFromParent;
89       xswa.save_under = True;
90       xswa.override_redirect = True;
91       /*
92        * TODO: use XGetVisualInfo and determine which is an
93        * overlay, if one is present, and use the Overlay visual
94        * for this window (for performance reasons).
95        * Not sure how to tell this yet...
96        */
97       screen->flash_window = XCreateWindow (display->xdisplay, root,
98                                             0, 0, width, height,
99                                             0, depth,
100                                             InputOutput,
101                                             visual,
102                                             /* note: XSun doesn't like SaveUnder here */
103                                             CWSaveUnder | CWOverrideRedirect,
104                                             &xswa);
105       XSelectInput (display->xdisplay, screen->flash_window, ExposureMask);
106       XMapWindow (display->xdisplay, screen->flash_window);
107       XSync (display->xdisplay, False);
108       XFlush (display->xdisplay);
109       XUnmapWindow (display->xdisplay, screen->flash_window);
110     }
111   else
112     {
113       /* just draw something in the window */
114       GC gc = XCreateGC (display->xdisplay, screen->flash_window, 0, NULL);
115       XMapWindow (display->xdisplay, screen->flash_window);
116       XSetForeground (display->xdisplay, gc,
117                       WhitePixel (display->xdisplay,
118                                   XScreenNumberOfScreen (screen->xscreen)));
119       XFillRectangle (display->xdisplay, screen->flash_window, gc,
120                       0, 0, width, height);
121       XSetForeground (display->xdisplay, gc,
122                       BlackPixel (display->xdisplay,
123                                   XScreenNumberOfScreen (screen->xscreen)));
124       XFillRectangle (display->xdisplay, screen->flash_window, gc,
125                       0, 0, width, height);
126       XFlush (display->xdisplay);
127       XSync (display->xdisplay, False);
128       XUnmapWindow (display->xdisplay, screen->flash_window);
129       XFreeGC (display->xdisplay, gc);
130     }
131 
132   if (meta_prefs_get_focus_mode () != G_DESKTOP_FOCUS_MODE_CLICK &&
133       !display->mouse_mode)
134     meta_display_increment_focus_sentinel (display);
135   XFlush (display->xdisplay);
136 }
137 
138 /**
139  * Flashes one screen, or all screens, in response to a bell event.
140  * If the event is on a particular window, flash the screen that
141  * window is on. Otherwise, flash every screen on this display.
142  *
143  * If the configure script found we had no XKB, this does not exist.
144  *
145  * \param display  The display the event came in on
146  * \param xkb_ev   The bell event
147  */
148 #ifdef HAVE_XKB
149 static void
bell_flash_fullscreen(MetaDisplay * display,XkbAnyEvent * xkb_ev)150 bell_flash_fullscreen (MetaDisplay *display,
151                        XkbAnyEvent *xkb_ev)
152 {
153   g_assert (xkb_ev->xkb_type == XkbBellNotify);
154 
155   bell_flash_screen (display, display->screen);
156 }
157 
158 /**
159  * Makes a frame be not flashed; this is the timeout half of
160  * bell_flash_window_frame(). This is done simply by clearing the
161  * flash flag and queuing a redraw of the frame.
162  *
163  * If the configure script found we had no XKB, this does not exist.
164  *
165  * \param data  The frame to unflash, cast to a gpointer so it can go into
166  *              a callback function.
167  * \return Always FALSE, so we don't get called again.
168  *
169  * \bug This is the parallel to bell_flash_window_frame(), so it should
170  * really be called meta_bell_unflash_window_frame().
171  */
172 static gboolean
bell_unflash_frame(gpointer data)173 bell_unflash_frame (gpointer data)
174 {
175   MetaFrame *frame = (MetaFrame *) data;
176   frame->is_flashing = 0;
177   meta_frame_queue_draw (frame);
178   return FALSE;
179 }
180 
181 /**
182  * Makes a frame flash and then return to normal shortly afterwards.
183  * This is done by setting a flag so that the theme
184  * code will temporarily draw the frame as focussed if it's unfocussed and
185  * vice versa, and then queueing a redraw. Lastly, we create a timeout so
186  * that the flag can be unset and the frame re-redrawn.
187  *
188  * If the configure script found we had no XKB, this does not exist.
189  *
190  * \param window  The window to flash
191  */
192 static void
bell_flash_window_frame(MetaWindow * window)193 bell_flash_window_frame (MetaWindow *window)
194 {
195   g_assert (window->frame != NULL);
196   window->frame->is_flashing = 1;
197   meta_frame_queue_draw (window->frame);
198   g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 100,
199       bell_unflash_frame, window->frame, NULL);
200 }
201 
202 /**
203  * Flashes the frame of the focussed window. If there is no focussed window,
204  * flashes the screen.
205  *
206  * \param display  The display the bell event came in on
207  * \param xkb_ev   The bell event we just received
208  */
209 static void
bell_flash_frame(MetaDisplay * display,XkbAnyEvent * xkb_ev)210 bell_flash_frame (MetaDisplay *display,
211                   XkbAnyEvent *xkb_ev)
212 {
213   XkbBellNotifyEvent *xkb_bell_event = (XkbBellNotifyEvent *) xkb_ev;
214   MetaWindow *window;
215 
216   g_assert (xkb_ev->xkb_type == XkbBellNotify);
217   window = meta_display_lookup_x_window (display, xkb_bell_event->window);
218   if (!window && (display->focus_window))
219     {
220       window = display->focus_window;
221     }
222   if (window && window->frame)
223     {
224       bell_flash_window_frame (window);
225     }
226   else /* revert to fullscreen flash if there's no focussed window */
227     {
228       bell_flash_fullscreen (display, xkb_ev);
229     }
230 }
231 
232 /**
233  * Gives the user some kind of visual bell substitute, in response to a
234  * bell event. What this is depends on the "visual bell type" pref.
235  *
236  * If the configure script found we had no XKB, this does not exist.
237  *
238  * \param display  The display the bell event came in on
239  * \param xkb_ev   The bell event we just received
240  *
241  * \bug This should be merged with meta_bell_notify().
242  */
243 static void
bell_visual_notify(MetaDisplay * display,XkbAnyEvent * xkb_ev)244 bell_visual_notify (MetaDisplay *display,
245                     XkbAnyEvent *xkb_ev)
246 {
247   switch (meta_prefs_get_visual_bell_type ())
248     {
249     case G_DESKTOP_VISUAL_BELL_FULLSCREEN_FLASH:
250       bell_flash_fullscreen (display, xkb_ev);
251       break;
252     case G_DESKTOP_VISUAL_BELL_FRAME_FLASH:
253       bell_flash_frame (display, xkb_ev); /* does nothing yet */
254       break;
255     default:
256       break;
257     }
258 }
259 
260 void
meta_bell_notify(MetaDisplay * display,XkbAnyEvent * xkb_ev)261 meta_bell_notify (MetaDisplay *display,
262                   XkbAnyEvent *xkb_ev)
263 {
264   /* flash something */
265   if (meta_prefs_get_visual_bell ())
266     bell_visual_notify (display, xkb_ev);
267 
268   if (meta_prefs_bell_is_audible ())
269     {
270       XkbBellNotifyEvent *xkb_bell_event = (XkbBellNotifyEvent*) xkb_ev;
271 
272 #ifdef HAVE_CANBERRA
273       ca_proplist *p;
274       MetaWindow *window;
275       int res;
276 
277       ca_proplist_create (&p);
278       ca_proplist_sets (p, CA_PROP_EVENT_ID, "bell-window-system");
279       ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION, _("Bell event"));
280       ca_proplist_sets (p, CA_PROP_CANBERRA_CACHE_CONTROL, "permanent");
281 
282       window = meta_display_lookup_x_window (display, xkb_bell_event->window);
283       if (!window && (display->focus_window) && (display->focus_window->frame))
284         window = display->focus_window;
285 
286       if (window)
287         {
288           pid_t client_pid;
289 
290           client_pid = meta_window_get_client_pid (window);
291 
292           ca_proplist_sets (p, CA_PROP_WINDOW_NAME, window->title);
293           ca_proplist_setf (p, CA_PROP_WINDOW_X11_XID, "%lu", (unsigned long)window->xwindow);
294           ca_proplist_sets (p, CA_PROP_APPLICATION_NAME, window->res_name);
295           ca_proplist_setf (p, CA_PROP_APPLICATION_PROCESS_ID, "%d", client_pid);
296         }
297 
298       /* First, we try to play a real sound ... */
299       res = ca_context_play_full (ca_gtk_context_get (), 1, p, NULL, NULL);
300 
301       ca_proplist_destroy (p);
302 
303       if (res != CA_SUCCESS && res != CA_ERROR_DISABLED)
304 #else
305       if (1)
306 #endif /* HAVE_CANBERRA */
307         {
308           /* ...and in case that failed we use the classic X11 bell. */
309           XkbForceDeviceBell (display->xdisplay,
310                               xkb_bell_event->device,
311                               xkb_bell_event->bell_class,
312                               xkb_bell_event->bell_id,
313                               xkb_bell_event->percent);
314         }
315     }
316 }
317 #endif /* HAVE_XKB */
318 
319 void
meta_bell_set_audible(MetaDisplay * display,gboolean audible)320 meta_bell_set_audible (MetaDisplay *display, gboolean audible)
321 {
322 }
323 
324 gboolean
meta_bell_init(MetaDisplay * display)325 meta_bell_init (MetaDisplay *display)
326 {
327 #ifdef HAVE_XKB
328   int xkb_base_error_type, xkb_opcode;
329 
330   if (!XkbQueryExtension (display->xdisplay,
331                           &xkb_opcode,
332                           &display->xkb_base_event_type,
333                           &xkb_base_error_type,
334                           NULL,
335                           NULL))
336     {
337       display->xkb_base_event_type = -1;
338       g_message ("could not find XKB extension.");
339       return FALSE;
340     }
341   else
342     {
343       XkbSelectEvents (display->xdisplay,
344                        XkbUseCoreKbd,
345                        XkbBellNotifyMask,
346                        XkbBellNotifyMask);
347       XkbChangeEnabledControls (display->xdisplay,
348                                 XkbUseCoreKbd,
349                                 XkbAudibleBellMask,
350                                 0);
351 
352       return TRUE;
353     }
354 #endif
355   return FALSE;
356 }
357 
358 /**
359  * Deals with a frame being destroyed. This is important because if we're
360  * using a visual bell, we might be flashing the edges of the frame, and
361  * so we'd have a timeout function waiting ready to un-flash them. If the
362  * frame's going away, we can tell the timeout not to bother.
363  *
364  * \param frame  The frame which is being destroyed
365  */
366 void
meta_bell_notify_frame_destroy(MetaFrame * frame)367 meta_bell_notify_frame_destroy (MetaFrame *frame)
368 {
369   if (frame->is_flashing)
370     g_source_remove_by_funcs_user_data (&g_timeout_funcs, frame);
371 }
372