/* -*- 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., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301, 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: * . * * 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 #include #include "bell.h" #include "screen-private.h" #include "prefs.h" #include /** * 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) { int x=-1, y=-1, width=-1, height=-1, screen_width=-1, screen_height=-1; MetaScreen *screen; screen = meta_window_get_screen (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_setf (p, CA_PROP_WINDOW_X11_SCREEN, "%i", meta_screen_get_screen_number(screen)); 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); /* properties for positional sound based on window placement */ meta_window_get_geometry (window, &x, &y, &width, &height); ca_proplist_setf (p, CA_PROP_WINDOW_X, "%i", x); ca_proplist_setf (p, CA_PROP_WINDOW_Y, "%i", y); ca_proplist_setf (p, CA_PROP_WINDOW_WIDTH, "%i", width); ca_proplist_setf (p, CA_PROP_WINDOW_HEIGHT, "%i", height); meta_screen_get_size (screen, &screen_width, &screen_height); if (screen_width > 1) { x += width/2; x = CLAMP(x, 0, screen_width-1); /* From libcanberra-gtk. * We use these strange format strings here to avoid that libc * applies locale information on the formatting of floating * numbers. */ ca_proplist_setf (p, CA_PROP_WINDOW_HPOS, "%i.%03i", (int) (x/(screen_width-1)), (int) (1000.0*x/(screen_width-1)) % 1000); } if (screen_height > 1) { y += height/2; y = CLAMP(y, 0, screen_height-1); ca_proplist_setf (p, CA_PROP_WINDOW_VPOS, "%i.%03i", (int) (y/(screen_height-1)), (int) (1000.0*y/(screen_height-1)) % 1000); } } /* 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); }