/* Copyright (c) Citrix Systems Inc. * All rights reserved. * * Redistribution and use in source and binary forms, * with or without modification, are permitted provided * that the following conditions are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the above * copyright notice, this list of conditions and the * following disclaimer in the documentation and/or other * materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. */ using System; using System.Runtime.InteropServices; using System.Threading; using System.Windows.Forms; namespace XenAdmin.Core { class Clip { private static readonly log4net.ILog log = log4net.LogManager.GetLogger(System.Reflection.MethodBase.GetCurrentMethod().DeclaringType); internal static EventHandler ClipboardChanged = null; internal static string ClipboardText = ""; private static IntPtr registeredClipboardHandle = IntPtr.Zero; private static IntPtr nextClipboardViewer = IntPtr.Zero; private static bool processingChangeCBChain = false; private static bool processingDrawClipboard = false; private static long lastDrawClipboard = 0L; private static int drawClipboardCount = 0; private static volatile bool GettingClipboard = false; internal static void RegisterClipboardViewer() { Program.AssertOnEventThread(); UnregisterClipboardViewer(); // Register ourselves at the front of the chain. Win32.SetLastError(0); registeredClipboardHandle = Program.MainWindow.Handle; nextClipboardViewer = Win32.SetClipboardViewer(registeredClipboardHandle); int err = Marshal.GetLastWin32Error(); if (err != 0) { log.ErrorFormat("SetClipboardViewer failed with code {0}: {1}", err, Win32.GetMessageString((uint)err)); registeredClipboardHandle = IntPtr.Zero; nextClipboardViewer = IntPtr.Zero; return; } // Sanity check -- I can't see why this should ever happen, but xorg/cygwin checks. if (nextClipboardViewer == Program.MainWindow.Handle) { log.Error("SetClipboardViewer has given us our Handle back!"); try { Win32.ChangeClipboardChain(registeredClipboardHandle, IntPtr.Zero); } catch (Exception) { log.WarnFormat("ChangeClipboardChain failed"); } registeredClipboardHandle = IntPtr.Zero; nextClipboardViewer = IntPtr.Zero; return; } StartGetClipboard(); } internal static void UnregisterClipboardViewer() { if (registeredClipboardHandle != IntPtr.Zero) { // Remove existing registration from chain, if any. Win32.SetLastError(0); Win32.ChangeClipboardChain(registeredClipboardHandle, nextClipboardViewer); int err = Marshal.GetLastWin32Error(); if (err != 0) { log.ErrorFormat("ChangeClipboardChain failed with code {0}: {1}", err, Win32.GetMessageString((uint)err)); } registeredClipboardHandle = IntPtr.Zero; nextClipboardViewer = IntPtr.Zero; } } internal static void ProcessWMChangeCBChain(Message e) { Program.AssertOnEventThread(); if (!processingChangeCBChain) { processingChangeCBChain = true; try { if (nextClipboardViewer == e.WParam) { // The handle being removed (WParam) is the one that we've got. We need to // switch to the next handle in the chain (LParam). nextClipboardViewer = e.LParam; } else { // Pass the message on -- it's not our link in the chain that is affected. ForwardClipboardMessage(e); } } finally { processingChangeCBChain = false; } } } internal static void ProcessWMDrawClipboard(Message e) { Program.AssertOnEventThread(); if (!processingDrawClipboard) { processingDrawClipboard = true; try { // Windows XP Remote Desktop server is broken wrt the clipboard. // If we receive 10 clipboard events in 500 msec then we move // ourselves back to the front of the chain. // Without this, we receive endless WM_DRAWCLIPBOARD events // after disconnecting and reconnecting the RDP session. long now = DateTime.Now.Ticks; if (now - lastDrawClipboard > 5000000) { drawClipboardCount = 0; lastDrawClipboard = now; } else { drawClipboardCount++; if (drawClipboardCount > 10) { drawClipboardCount = 0; lastDrawClipboard = now; RegisterClipboardViewer(); } } StartGetClipboard(); ForwardClipboardMessage(e); } finally { processingDrawClipboard = false; } } } private static void ForwardClipboardMessage(Message e) { Program.AssertOnEventThread(); if (nextClipboardViewer == IntPtr.Zero) return; Win32.SetLastError(0); Win32.SendMessage(nextClipboardViewer, e.Msg, e.WParam, e.LParam); int err = Marshal.GetLastWin32Error(); if (err != 0) { log.ErrorFormat("SendMessage({0}) failed with code {1}: {2}", Win32.GetWindowsMessageName(e.Msg), err, Win32.GetMessageString((uint)err)); } } private static void StartGetClipboard() { Program.AssertOnEventThread(); if (!GettingClipboard) { GettingClipboard = true; Thread clipboardThread = new Thread(GetClipboard); clipboardThread.SetApartmentState(ApartmentState.STA); clipboardThread.IsBackground = true; clipboardThread.Start(); } } private static void GetClipboard() { Program.AssertOffEventThread(); try { if (Clipboard.ContainsText()) { string s = Clipboard.GetText(); if (s != ClipboardText) { ClipboardText = s; Program.Invoke(Program.MainWindow, OnClipboardChanged); } } } catch { } GettingClipboard = false; } private static void OnClipboardChanged() { Program.AssertOnEventThread(); if (ClipboardChanged != null) ClipboardChanged(null, null); } internal static void SetClipboardText(string text) { Program.AssertOnEventThread(); try { Clipboard.SetText(text); } catch (Exception ex) { log.Error("Exception while trying to set clipboard text.", ex); log.Error(ex, ex); } } } }