Subject: bug#21662: visible-bell causes display artifacts on OS X 10.11

From: Anders Lindgren <[email-masked]>
Date: Sat, 21 Nov 2015 21:05:07 +0100

Hi again,

I decided to reimplement Mustafas ideas in a 10.6.8 compatible manner, and
to address the problem with the image being covered by scroll bars I
mentioned before. In addition, my implementation is aware that several
bell:s may be active at once and won't remove the image until the last one
times out, which eliminated flickering.

Unless anyone objects, I will publish this in the beginning of next week.

Sincerely,
    Anders Lindgren

On Wed, Nov 18, 2015 at 9:27 PM, Anders Lindgren <[email-masked]> wrote:

> Hi,
>
> Today I tested suggested patch on an 10.11 and on 10.6.8.
>
> The good news is that, visually, the patch looks really good! It replaced
> the boring black square with the standard OS X icon "Caution" icon -- an
> exclamation mark on a triangle. It is less intrusive, which I consider a
> good thing.
>
> Unfortunately, the code doesn't compile under 10.6.8. The code use the
> "hidden" property of NSView (introduced in 10.10), and it the compiler
> complains about the "." in the "[BellView.instance ..." construct.
>
> I notices a minor problem under 10.11 as well. The icon is shadowed by
> scroll bars, if they happen to be displayed in the middle of the frame,
> like when having two side-by-side windows.
>
> I will make an attempt to rewrite to code to work under 10.6.8.
>
> Sincerely,
>     Anders Lindgren
>
>
diff --git a/src/nsterm.m b/src/nsterm.m
index 58ace48..5f22405 100644
--- a/src/nsterm.m
+++ b/src/nsterm.m
@@ -584,28 +584,6 @@ ns_load_path (void)
   return NULL;
 }
 
-static void
-ns_timeout (int usecs)
-/* --------------------------------------------------------------------------
-     Blocking timer utility used by ns_ring_bell
-   -------------------------------------------------------------------------- */
-{
-  struct timespec wakeup = timespec_add (current_timespec (),
-					 make_timespec (0, usecs * 1000));
-
-  /* Keep waiting until past the time wakeup.  */
-  while (1)
-    {
-      struct timespec timeout, now = current_timespec ();
-      if (timespec_cmp (wakeup, now) <= 0)
-	break;
-      timeout = timespec_sub (wakeup, now);
-
-      /* Try to wait that long--but we might wake up sooner.  */
-      pselect (0, NULL, NULL, NULL, &timeout, NULL);
-    }
-}
-
 
 void
 ns_release_object (void *obj)
@@ -1161,6 +1139,77 @@ ns_clip_to_row (struct window *w, struct glyph_row *row,
 }

+/* ==========================================================================
+
+    Visibel bell and beep.
+
+   ========================================================================== */
+
+
+@interface EmacsBell : NSImageView
+{
+  // Number of currently active bell:s.
+  unsigned int nestCount;
+}
+- (void)show:(NSView *)view;
+- (void)hide;
+@end
+
+@implementation EmacsBell
+
+- (id)init;
+{
+  if ((self = [super init]))
+    {
+      nestCount = 0;
+      self.image = [NSImage imageNamed:NSImageNameCaution];
+    }
+  return self;
+}
+
+- (void)show:(NSView *)view
+{
+  NSTRACE ("[EmacsBell show:]");
+  NSTRACE_MSG ("nestCount: %u", nestCount);
+
+  // Show the image, unless it's already shown.
+  if (nestCount == 0)
+    {
+      NSRect rect = [view bounds];
+      NSPoint pos;
+      pos.x = rect.origin.x + (rect.size.width  - self.image.size.width )/2;
+      pos.y = rect.origin.y + (rect.size.height - self.image.size.height)/2;
+
+      [self setFrameOrigin:pos];
+      [self setFrameSize:self.image.size];
+
+      [[[view window] contentView] addSubview:self
+                                   positioned:NSWindowAbove
+                                   relativeTo:nil];
+    }
+
+  ++nestCount;
+
+  [self performSelector:@selector(hide) withObject:self afterDelay:0.5];
+}
+
+
+- (void)hide
+{
+  // Note: Trace output from this method isn't shown, reason unknown.
+  // NSTRACE ("[EmacsBell hide]");
+
+  --nestCount;
+
+  // Remove the image once the last bell became inactive.
+  if (nestCount == 0)
+    {
+      [self removeFromSuperview];
+    }
+}
+
+@end
+
 static void
 ns_ring_bell (struct frame *f)
 /* --------------------------------------------------------------------------
@@ -1170,37 +1219,24 @@ ns_ring_bell (struct frame *f)
   NSTRACE ("ns_ring_bell");
   if (visible_bell)
     {
-      NSAutoreleasePool *pool;
       struct frame *frame = SELECTED_FRAME ();
       NSView *view;
 
+      static EmacsBell * bell_view = nil;
+      if (bell_view == nil)
+        {
+          bell_view = [[EmacsBell alloc] init];
+          [bell_view retain];
+        }
+
       block_input ();
-      pool = [[NSAutoreleasePool alloc] init];
 
       view = FRAME_NS_VIEW (frame);
       if (view != nil)
         {
-          NSRect r, surr;
-          NSPoint dim = NSMakePoint (128, 128);
-
-          r = [view bounds];
-          r.origin.x += (r.size.width - dim.x) / 2;
-          r.origin.y += (r.size.height - dim.y) / 2;
-          r.size.width = dim.x;
-          r.size.height = dim.y;
-          surr = NSInsetRect (r, -2, -2);
-          ns_focus (frame, &surr, 1);
-          [[view window] cacheImageInRect: [view convertRect: surr toView:nil]];
-          [ns_lookup_indexed_color (NS_FACE_FOREGROUND
-                                      (FRAME_DEFAULT_FACE (frame)), frame) set];
-          NSRectFill (r);
-          [[view window] flushWindow];
-          ns_timeout (150000);
-          [[view window] restoreCachedImage];
-          [[view window] flushWindow];
-          ns_unfocus (frame);
+          [bell_view show:view];
         }
-      [pool release];
+
       unblock_input ();
     }
   else
@@ -1209,6 +1245,7 @@ ns_ring_bell (struct frame *f)
     }
 }
 
+
 /* ==========================================================================
 
     Frame / window manager related functions