diff options
Diffstat (limited to 'src/core/bell.c')
-rw-r--r-- | src/core/bell.c | 397 |
1 files changed, 397 insertions, 0 deletions
diff --git a/src/core/bell.c b/src/core/bell.c new file mode 100644 index 00000000..560b3569 --- /dev/null +++ b/src/core/bell.c @@ -0,0 +1,397 @@ +/* -*- mode: C; c-file-style: "gnu"; indent-tabs-mode: nil; -*- */ + +/* Marco visual bell */ + +/* + * Copyright (C) 2002 Sun Microsystems Inc. + * Copyright (C) 2005, 2006 Elijah Newren + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License as + * published by the Free Software Foundation; either version 2 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA + * 02111-1307, USA. + */ + +/** + * \file bell.c Ring the bell or flash the screen + * + * Sometimes, X programs "ring the bell", whatever that means. Marco lets + * the user configure the bell to be audible or visible (aka visual), and + * if it's visual it can be configured to be frame-flash or fullscreen-flash. + * We never get told about audible bells; X handles them just fine by itself. + * + * Visual bells come in at meta_bell_notify(), which checks we are actually + * in visual mode and calls through to bell_visual_notify(). That + * function then checks what kind of visual flash you like, and calls either + * bell_flash_fullscreen()-- which calls bell_flash_screen() to do + * its work-- or bell_flash_frame(), which flashes the focussed window + * using bell_flash_window_frame(), unless there is no such window, in + * which case it flashes the screen instead. bell_flash_window_frame() + * flashes the frame and calls bell_unflash_frame() as a timeout to + * remove the flash. + * + * The visual bell was the result of a discussion in Bugzilla here: + * <http://bugzilla.gnome.org/show_bug.cgi?id=99886>. + * + * Several of the functions in this file are ifdeffed out entirely if we are + * found not to have the XKB extension, which is required to do these clever + * things with bells; some others are entirely no-ops in that case. + */ + +#include <config.h> +#include "bell.h" +#include "screen-private.h" +#include "prefs.h" +#include <canberra-gtk.h> + +/** + * Flashes one entire screen. This is done by making a window the size of the + * whole screen (or reusing the old one, if it's still around), mapping it, + * painting it white and then black, and then unmapping it. We set saveunder so + * that all the windows behind it come back immediately. + * + * Unlike frame flashes, we don't do fullscreen flashes with a timeout; rather, + * we do them in one go, because we don't have to rely on the theme code + * redrawing the frame for us in order to do the flash. + * + * \param display The display which owns the screen (rather redundant) + * \param screen The screen to flash + * + * \bug The way I read it, this appears not to do the flash + * the first time we flash a particular display. Am I wrong? + * + * \bug This appears to destroy our current XSync status. + */ +static void +bell_flash_screen (MetaDisplay *display, + MetaScreen *screen) +{ + Window root = screen->xroot; + int width = screen->rect.width; + int height = screen->rect.height; + + if (screen->flash_window == None) + { + Visual *visual = (Visual *)CopyFromParent; + XSetWindowAttributes xswa; + int depth = CopyFromParent; + xswa.save_under = True; + xswa.override_redirect = True; + /* + * TODO: use XGetVisualInfo and determine which is an + * overlay, if one is present, and use the Overlay visual + * for this window (for performance reasons). + * Not sure how to tell this yet... + */ + screen->flash_window = XCreateWindow (display->xdisplay, root, + 0, 0, width, height, + 0, depth, + InputOutput, + visual, + /* note: XSun doesn't like SaveUnder here */ + CWSaveUnder | CWOverrideRedirect, + &xswa); + XSelectInput (display->xdisplay, screen->flash_window, ExposureMask); + XMapWindow (display->xdisplay, screen->flash_window); + XSync (display->xdisplay, False); + XFlush (display->xdisplay); + XUnmapWindow (display->xdisplay, screen->flash_window); + } + else + { + /* just draw something in the window */ + GC gc = XCreateGC (display->xdisplay, screen->flash_window, 0, NULL); + XMapWindow (display->xdisplay, screen->flash_window); + XSetForeground (display->xdisplay, gc, + WhitePixel (display->xdisplay, + XScreenNumberOfScreen (screen->xscreen))); + XFillRectangle (display->xdisplay, screen->flash_window, gc, + 0, 0, width, height); + XSetForeground (display->xdisplay, gc, + BlackPixel (display->xdisplay, + XScreenNumberOfScreen (screen->xscreen))); + XFillRectangle (display->xdisplay, screen->flash_window, gc, + 0, 0, width, height); + XFlush (display->xdisplay); + XSync (display->xdisplay, False); + XUnmapWindow (display->xdisplay, screen->flash_window); + XFreeGC (display->xdisplay, gc); + } + + if (meta_prefs_get_focus_mode () != META_FOCUS_MODE_CLICK && + !display->mouse_mode) + meta_display_increment_focus_sentinel (display); + XFlush (display->xdisplay); +} + +/** + * Flashes one screen, or all screens, in response to a bell event. + * If the event is on a particular window, flash the screen that + * window is on. Otherwise, flash every screen on this display. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param display The display the event came in on + * \param xkb_ev The bell event + */ +#ifdef HAVE_XKB +static void +bell_flash_fullscreen (MetaDisplay *display, + XkbAnyEvent *xkb_ev) +{ + XkbBellNotifyEvent *xkb_bell_ev = (XkbBellNotifyEvent *) xkb_ev; + MetaScreen *screen; + + g_assert (xkb_ev->xkb_type == XkbBellNotify); + if (xkb_bell_ev->window != None) + { + screen = meta_display_screen_for_xwindow (display, xkb_bell_ev->window); + if (screen) + bell_flash_screen (display, screen); + } + else + { + GSList *screen_list = display->screens; + while (screen_list) + { + screen = (MetaScreen *) screen_list->data; + bell_flash_screen (display, screen); + screen_list = screen_list->next; + } + } +} + +/** + * Makes a frame be not flashed; this is the timeout half of + * bell_flash_window_frame(). This is done simply by clearing the + * flash flag and queuing a redraw of the frame. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param data The frame to unflash, cast to a gpointer so it can go into + * a callback function. + * \return Always FALSE, so we don't get called again. + * + * \bug This is the parallel to bell_flash_window_frame(), so it should + * really be called meta_bell_unflash_window_frame(). + */ +static gboolean +bell_unflash_frame (gpointer data) +{ + MetaFrame *frame = (MetaFrame *) data; + frame->is_flashing = 0; + meta_frame_queue_draw (frame); + return FALSE; +} + +/** + * Makes a frame flash and then return to normal shortly afterwards. + * This is done by setting a flag so that the theme + * code will temporarily draw the frame as focussed if it's unfocussed and + * vice versa, and then queueing a redraw. Lastly, we create a timeout so + * that the flag can be unset and the frame re-redrawn. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param window The window to flash + */ +static void +bell_flash_window_frame (MetaWindow *window) +{ + g_assert (window->frame != NULL); + window->frame->is_flashing = 1; + meta_frame_queue_draw (window->frame); + g_timeout_add_full (G_PRIORITY_DEFAULT_IDLE, 100, + bell_unflash_frame, window->frame, NULL); +} + +/** + * Flashes the frame of the focussed window. If there is no focussed window, + * flashes the screen. + * + * \param display The display the bell event came in on + * \param xkb_ev The bell event we just received + */ +static void +bell_flash_frame (MetaDisplay *display, + XkbAnyEvent *xkb_ev) +{ + XkbBellNotifyEvent *xkb_bell_event = (XkbBellNotifyEvent *) xkb_ev; + MetaWindow *window; + + g_assert (xkb_ev->xkb_type == XkbBellNotify); + window = meta_display_lookup_x_window (display, xkb_bell_event->window); + if (!window && (display->focus_window)) + { + window = display->focus_window; + } + if (window && window->frame) + { + bell_flash_window_frame (window); + } + else /* revert to fullscreen flash if there's no focussed window */ + { + bell_flash_fullscreen (display, xkb_ev); + } +} + +/** + * Gives the user some kind of visual bell substitute, in response to a + * bell event. What this is depends on the "visual bell type" pref. + * + * If the configure script found we had no XKB, this does not exist. + * + * \param display The display the bell event came in on + * \param xkb_ev The bell event we just received + * + * \bug This should be merged with meta_bell_notify(). + */ +static void +bell_visual_notify (MetaDisplay *display, + XkbAnyEvent *xkb_ev) +{ + switch (meta_prefs_get_visual_bell_type ()) + { + case META_VISUAL_BELL_FULLSCREEN_FLASH: + bell_flash_fullscreen (display, xkb_ev); + break; + case META_VISUAL_BELL_FRAME_FLASH: + bell_flash_frame (display, xkb_ev); /* does nothing yet */ + break; + case META_VISUAL_BELL_INVALID: + /* do nothing */ + break; + } +} + +void +meta_bell_notify (MetaDisplay *display, + XkbAnyEvent *xkb_ev) +{ + /* flash something */ + if (meta_prefs_get_visual_bell ()) + bell_visual_notify (display, xkb_ev); + + if (meta_prefs_bell_is_audible ()) + { + ca_proplist *p; + XkbBellNotifyEvent *xkb_bell_event = (XkbBellNotifyEvent*) xkb_ev; + MetaWindow *window; + int res; + + ca_proplist_create (&p); + ca_proplist_sets (p, CA_PROP_EVENT_ID, "bell-window-system"); + ca_proplist_sets (p, CA_PROP_EVENT_DESCRIPTION, _("Bell event")); + ca_proplist_sets (p, CA_PROP_CANBERRA_CACHE_CONTROL, "permanent"); + + window = meta_display_lookup_x_window (display, xkb_bell_event->window); + if (!window && (display->focus_window) && (display->focus_window->frame)) + window = display->focus_window; + + if (window) + { + ca_proplist_sets (p, CA_PROP_WINDOW_NAME, window->title); + ca_proplist_setf (p, CA_PROP_WINDOW_X11_XID, "%lu", (unsigned long)window->xwindow); + ca_proplist_sets (p, CA_PROP_APPLICATION_NAME, window->res_name); + ca_proplist_setf (p, CA_PROP_APPLICATION_PROCESS_ID, "%d", window->net_wm_pid); + } + + /* First, we try to play a real sound ... */ + res = ca_context_play_full (ca_gtk_context_get (), 1, p, NULL, NULL); + + ca_proplist_destroy (p); + + if (res != CA_SUCCESS && res != CA_ERROR_DISABLED) + { + /* ...and in case that failed we use the classic X11 bell. */ + XkbForceDeviceBell (display->xdisplay, + xkb_bell_event->device, + xkb_bell_event->bell_class, + xkb_bell_event->bell_id, + xkb_bell_event->percent); + } + } +} +#endif /* HAVE_XKB */ + +void +meta_bell_set_audible (MetaDisplay *display, gboolean audible) +{ +} + +gboolean +meta_bell_init (MetaDisplay *display) +{ +#ifdef HAVE_XKB + int xkb_base_error_type, xkb_opcode; + + if (!XkbQueryExtension (display->xdisplay, &xkb_opcode, + &display->xkb_base_event_type, + &xkb_base_error_type, + NULL, NULL)) + { + display->xkb_base_event_type = -1; + g_message ("could not find XKB extension."); + return FALSE; + } + else + { + unsigned int mask = XkbBellNotifyMask; + gboolean visual_bell_auto_reset = FALSE; + /* TRUE if and when non-broken version is available */ + XkbSelectEvents (display->xdisplay, + XkbUseCoreKbd, + XkbBellNotifyMask, + XkbBellNotifyMask); + XkbChangeEnabledControls (display->xdisplay, + XkbUseCoreKbd, + XkbAudibleBellMask, + 0); + if (visual_bell_auto_reset) { + XkbSetAutoResetControls (display->xdisplay, + XkbAudibleBellMask, + &mask, + &mask); + } + return TRUE; + } +#endif + return FALSE; +} + +void +meta_bell_shutdown (MetaDisplay *display) +{ +#ifdef HAVE_XKB + /* TODO: persist initial bell state in display, reset here */ + XkbChangeEnabledControls (display->xdisplay, + XkbUseCoreKbd, + XkbAudibleBellMask, + XkbAudibleBellMask); +#endif +} + +/** + * Deals with a frame being destroyed. This is important because if we're + * using a visual bell, we might be flashing the edges of the frame, and + * so we'd have a timeout function waiting ready to un-flash them. If the + * frame's going away, we can tell the timeout not to bother. + * + * \param frame The frame which is being destroyed + */ +void +meta_bell_notify_frame_destroy (MetaFrame *frame) +{ + if (frame->is_flashing) + g_source_remove_by_funcs_user_data (&g_timeout_funcs, frame); +} |