diff --git a/osu.Desktop.VisualTests/Tests/TestCaseOnScreenDisplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseOnScreenDisplay.cs
new file mode 100644
index 0000000000..3cefb8a3d2
--- /dev/null
+++ b/osu.Desktop.VisualTests/Tests/TestCaseOnScreenDisplay.cs
@@ -0,0 +1,47 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Testing;
+using osu.Game.Overlays;
+
+namespace osu.Desktop.VisualTests.Tests
+{
+    internal class TestCaseOnScreenDisplay : TestCase
+    {
+        private FrameworkConfigManager config;
+        private Bindable<FrameSync> frameSyncMode;
+
+        public override string Description => @"Make it easier to see setting changes";
+
+        public override void Reset()
+        {
+            base.Reset();
+
+            Add(new OnScreenDisplay());
+
+            frameSyncMode = config.GetBindable<FrameSync>(FrameworkSetting.FrameSync);
+
+            FrameSync initial = frameSyncMode.Value;
+
+            AddRepeatStep(@"Change frame limiter", setNextMode, 3);
+
+            AddStep(@"Restore frame limiter", () => frameSyncMode.Value = initial);
+        }
+
+        private void setNextMode()
+        {
+            var nextMode = frameSyncMode.Value + 1;
+            if (nextMode > FrameSync.Unlimited)
+                nextMode = FrameSync.VSync;
+            frameSyncMode.Value = nextMode;
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(FrameworkConfigManager config)
+        {
+            this.config = config;
+        }
+    }
+}
diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
index 9b07ebf90b..dbb1750b72 100644
--- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
+++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj
@@ -195,6 +195,7 @@
     <Compile Include="Tests\TestCaseMenuOverlays.cs" />
     <Compile Include="Tests\TestCaseMusicController.cs" />
     <Compile Include="Tests\TestCaseNotificationManager.cs" />
+    <Compile Include="Tests\TestCaseOnScreenDisplay.cs" />
     <Compile Include="Tests\TestCasePlayer.cs" />
     <Compile Include="Tests\TestCaseHitObjects.cs" />
     <Compile Include="Tests\TestCaseKeyCounter.cs" />
diff --git a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs
index 601970e36d..237aaa44a6 100644
--- a/osu.Game/Graphics/UserInterface/TwoLayerButton.cs
+++ b/osu.Game/Graphics/UserInterface/TwoLayerButton.cs
@@ -216,6 +216,11 @@ namespace osu.Game.Graphics.UserInterface
                 });
         }
 
+        protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
+        {
+            return true;
+        }
+
         protected override bool OnClick(InputState state)
         {
             var flash = new Box
diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs
index 5a3b47eb23..4cf436a8e4 100644
--- a/osu.Game/OsuGame.cs
+++ b/osu.Game/OsuGame.cs
@@ -145,6 +145,7 @@ namespace osu.Game
                 },
                 volume = new VolumeControl(),
                 overlayContent = new Container{ RelativeSizeAxes = Axes.Both },
+                new OnScreenDisplay(),
                 new GlobalHotkeys //exists because UserInputManager is at a level below us.
                 {
                     Handler = globalHotkeyPressed
diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs
index e6dffbfe63..4748eb7de6 100644
--- a/osu.Game/Overlays/ChatOverlay.cs
+++ b/osu.Game/Overlays/ChatOverlay.cs
@@ -284,10 +284,21 @@ namespace osu.Game.Overlays
         {
             if (channel == null) return;
 
-            careChannels.Add(channel);
-            channelTabs.AddItem(channel);
+            var existing = careChannels.Find(c => c.Id == channel.Id);
 
-            // we need to get a good number of messages initially for each channel we care about.
+            if (existing != null)
+            {
+                // if we already have this channel loaded, we don't want to make a second one.
+                channel = existing;
+            }
+            else
+            {
+
+                careChannels.Add(channel);
+                channelTabs.AddItem(channel);
+            }
+
+            // let's fetch a small number of messages to bring us up-to-date with the backlog.
             fetchInitialMessages(channel);
 
             if (CurrentChannel == null)
diff --git a/osu.Game/Overlays/OnScreenDisplay.cs b/osu.Game/Overlays/OnScreenDisplay.cs
new file mode 100644
index 0000000000..fb1aed3657
--- /dev/null
+++ b/osu.Game/Overlays/OnScreenDisplay.cs
@@ -0,0 +1,261 @@
+// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
+// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Configuration;
+using osu.Framework.Extensions;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Sprites;
+using osu.Game.Graphics;
+using OpenTK;
+using OpenTK.Graphics;
+using osu.Framework.Extensions.Color4Extensions;
+
+namespace osu.Game.Overlays
+{
+    public class OnScreenDisplay : Container
+    {
+        private readonly Container box;
+
+        public override bool HandleInput => false;
+
+        private readonly SpriteText textLine1;
+        private readonly SpriteText textLine2;
+        private readonly SpriteText textLine3;
+
+        private const float height = 110;
+        private const float height_contracted = height * 0.9f;
+
+        private readonly FillFlowContainer<OptionLight> optionLights;
+
+        public OnScreenDisplay()
+        {
+            RelativeSizeAxes = Axes.Both;
+
+            Children = new Drawable[]
+            {
+                box = new Container
+                {
+                    Origin = Anchor.Centre,
+                    RelativePositionAxes = Axes.Both,
+                    Position = new Vector2(0.5f, 0.75f),
+                    Masking = true,
+                    AutoSizeAxes = Axes.X,
+                    Height = height_contracted,
+                    Alpha = 0,
+                    CornerRadius = 20,
+                    Children = new Drawable[]
+                    {
+                        new Box
+                        {
+                            RelativeSizeAxes = Axes.Both,
+                            Colour = Color4.Black,
+                            Alpha = 0.7f,
+                        },
+                        new Container // purely to add a minimum width
+                        {
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.Centre,
+                            Width = 240,
+                            RelativeSizeAxes = Axes.Y,
+                        },
+                        textLine1 = new SpriteText
+                        {
+                            Padding = new MarginPadding(10),
+                            Font = @"Exo2.0-Black",
+                            Spacing = new Vector2(1, 0),
+                            TextSize = 14,
+                            Anchor = Anchor.TopCentre,
+                            Origin = Anchor.TopCentre,
+                        },
+                        textLine2 = new SpriteText
+                        {
+                            TextSize = 24,
+                            Font = @"Exo2.0-Light",
+                            Anchor = Anchor.Centre,
+                            Origin = Anchor.BottomCentre,
+                        },
+                        new FillFlowContainer
+                        {
+                            Anchor = Anchor.BottomCentre,
+                            Origin = Anchor.BottomCentre,
+                            AutoSizeAxes = Axes.Both,
+                            Direction = FillDirection.Vertical,
+                            Children = new Drawable[]
+                            {
+                                optionLights = new FillFlowContainer<OptionLight>
+                                {
+                                    Padding = new MarginPadding { Top = 20, Bottom = 5 },
+                                    Spacing = new Vector2(5, 0),
+                                    Direction = FillDirection.Horizontal,
+                                    Anchor = Anchor.TopCentre,
+                                    Origin = Anchor.TopCentre,
+                                    AutoSizeAxes = Axes.Both
+                                },
+                                textLine3 = new SpriteText
+                                {
+                                    Padding = new MarginPadding { Bottom = 15 },
+                                    Font = @"Exo2.0-Bold",
+                                    TextSize = 12,
+                                    Alpha = 0.3f,
+                                    Anchor = Anchor.TopCentre,
+                                    Origin = Anchor.TopCentre,
+                                },
+                            }
+                        }
+                    }
+                },
+            };
+        }
+
+        [BackgroundDependencyLoader]
+        private void load(FrameworkConfigManager frameworkConfig)
+        {
+            trackSetting(frameworkConfig.GetBindable<FrameSync>(FrameworkSetting.FrameSync), v => display(v, "Frame Limiter", v.GetDescription(), "Ctrl+F7"));
+            trackSetting(frameworkConfig.GetBindable<string>(FrameworkSetting.AudioDevice), v => display(v, "Audio Device", v, v));
+            trackSetting(frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay), v => display(v, "Debug Logs", v ? "visible" : "hidden", "Ctrl+F10"));
+
+            Action displayResolution = delegate { display(null, "Screen Resolution", frameworkConfig.Get<int>(FrameworkSetting.Width) + "x" + frameworkConfig.Get<int>(FrameworkSetting.Height)); };
+
+            trackSetting(frameworkConfig.GetBindable<int>(FrameworkSetting.Width), v => displayResolution());
+            trackSetting(frameworkConfig.GetBindable<int>(FrameworkSetting.Height), v => displayResolution());
+
+            trackSetting(frameworkConfig.GetBindable<WindowMode>(FrameworkSetting.WindowMode), v => display(v, "Screen Mode", v.ToString(), "Alt+Enter"));
+        }
+
+        private readonly List<IBindable> references = new List<IBindable>();
+
+        private void trackSetting<T>(Bindable<T> bindable, Bindable<T>.BindableValueChanged<T> action)
+        {
+            // we need to keep references as we bind
+            references.Add(bindable);
+
+            bindable.ValueChanged += action;
+        }
+
+        private void display(object rawValue, string settingName, string settingValue, string shortcut = @"")
+        {
+            Schedule(() =>
+            {
+                textLine1.Text = settingName.ToUpper();
+                textLine2.Text = settingValue;
+                textLine3.Text = shortcut.ToUpper();
+
+                box.FadeIn(500, EasingTypes.OutQuint);
+                box.ResizeHeightTo(height, 500, EasingTypes.OutQuint);
+
+                using (box.BeginDelayedSequence(500))
+                {
+                    box.FadeOutFromOne(1500, EasingTypes.InQuint);
+                    box.ResizeHeightTo(height_contracted, 1500, EasingTypes.InQuint);
+                }
+
+                int optionCount = 0;
+                int selectedOption = -1;
+
+                if (rawValue is bool)
+                {
+                    optionCount = 1;
+                    if ((bool)rawValue) selectedOption = 0;
+                }
+                else if (rawValue is Enum)
+                {
+                    var values = Enum.GetValues(rawValue.GetType());
+                    optionCount = values.Length;
+                    selectedOption = Convert.ToInt32(rawValue);
+                }
+
+                textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre;
+                textLine2.Y = optionCount > 0 ? 0 : 5;
+
+                if (optionLights.Children.Count() != optionCount)
+                {
+                    optionLights.Clear();
+                    for (int i = 0; i < optionCount; i++)
+                        optionLights.Add(new OptionLight());
+                }
+
+                for (int i = 0; i < optionCount; i++)
+                    optionLights.Children.Skip(i).First().Glowing = i == selectedOption;
+            });
+        }
+
+        private class OptionLight : Container
+        {
+            private Color4 glowingColour, idleColour;
+
+            private const float transition_speed = 300;
+
+            private const float glow_strength = 0.4f;
+
+            private readonly Box fill;
+
+            public OptionLight()
+            {
+                Children = new[]
+                {
+                    fill = new Box
+                    {
+                        RelativeSizeAxes = Axes.Both,
+                        Alpha = 1,
+                    },
+                };
+            }
+
+            private bool glowing;
+
+            public bool Glowing
+            {
+                set
+                {
+                    glowing = value;
+                    if (!IsLoaded) return;
+
+                    updateGlow();
+                }
+            }
+
+            private void updateGlow()
+            {
+                if (glowing)
+                {
+                    fill.FadeColour(glowingColour, transition_speed, EasingTypes.OutQuint);
+                    FadeEdgeEffectTo(glow_strength, transition_speed, EasingTypes.OutQuint);
+                }
+                else
+                {
+                    FadeEdgeEffectTo(0, transition_speed, EasingTypes.OutQuint);
+                    fill.FadeColour(idleColour, transition_speed, EasingTypes.OutQuint);
+                }
+            }
+
+            [BackgroundDependencyLoader]
+            private void load(OsuColour colours)
+            {
+                fill.Colour = idleColour = Color4.White.Opacity(0.4f);
+                glowingColour = Color4.White;
+
+                Size = new Vector2(25, 5);
+
+                Masking = true;
+                CornerRadius = 3;
+
+                EdgeEffect = new EdgeEffect
+                {
+                    Colour = colours.BlueDark.Opacity(glow_strength),
+                    Type = EdgeEffectType.Glow,
+                    Radius = 8,
+                };
+
+                FadeEdgeEffectTo(0);
+
+                updateGlow();
+                Flush(true);
+            }
+        }
+    }
+}
diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs
index de390b63b3..8ee0de12b9 100644
--- a/osu.Game/Screens/Play/Player.cs
+++ b/osu.Game/Screens/Play/Player.cs
@@ -224,6 +224,15 @@ namespace osu.Game.Screens.Play
             };
         }
 
+        protected override void Update()
+        {
+            // eagerly pause when we lose window focus (if we are locally playing).
+            if (!Game.IsActive && !HitRenderer.HasReplayLoaded)
+                Pause();
+
+            base.Update();
+        }
+
         private void initializeSkipButton()
         {
             const double skip_required_cutoff = 3000;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index 919b2bb732..63acdb1914 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -80,6 +80,7 @@
     <Compile Include="Overlays\Music\FilterControl.cs" />
     <Compile Include="Overlays\Music\PlaylistItem.cs" />
     <Compile Include="Overlays\Music\PlaylistList.cs" />
+    <Compile Include="Overlays\OnScreenDisplay.cs" />
     <Compile Include="Overlays\Settings\SettingsHeader.cs" />
     <Compile Include="Overlays\Settings\Sections\Audio\MainMenuSettings.cs" />
     <Compile Include="Overlays\Toolbar\ToolbarChatButton.cs" />