1

I'm trying to write a small application that's sits in the system tray. I've registered a hotkey. When the hotkey is fired and the application is activated I want to send Ctrl+C to the last active window so I can get the highlighted text into the clipboard.

This is what I got so far:

    //http://stackoverflow.com/questions/9468476/switch-to-last-active-application-like-alt-tab

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool IsWindowVisible(IntPtr hWnd);

    [DllImport("user32.dll")]
    static extern IntPtr GetLastActivePopup(IntPtr hWnd);

    [DllImport("user32.dll", ExactSpelling = true)]
    static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll")]
    [return: MarshalAs(UnmanagedType.Bool)]
    static extern bool SetForegroundWindow(IntPtr hWnd);

    const uint GA_PARENT = 1;
    const uint GA_ROOT = 2;
    const uint GA_ROOTOWNER = 3;

    public static IntPtr GetPreviousWindow()
    {
        IntPtr activeAppWindow = GetForegroundWindow();
        if (activeAppWindow == IntPtr.Zero)
            return IntPtr.Zero;

        IntPtr prevAppWindow = GetLastActivePopup(activeAppWindow);
        return IsWindowVisible(prevAppWindow) ? prevAppWindow : IntPtr.Zero;
    }

    public static void FocusToPreviousWindow()
    {
        IntPtr prevWindow = GetPreviousWindow();
        if (prevWindow != IntPtr.Zero)
            SetForegroundWindow(prevWindow);
    }

    ...

    private static void OnHotKeyFired()
    {
        FocusToPreviousWindow();

        SendKeys.SendWait("^(c)");

        _viewModel.Input = Clipboard.GetText();

        new UIWindow(_viewModel).ShowDialog();
    }

But I can't get the SendKeys to work. In most apps nothing happpens, meaning ctrl-c is not fired. In Ms Word a copyright sign (c) is inserted in my document when SendWait is executed.

UPDATE:

I've tried with WM_COPY and SendMessage:

private static void OnHotKeyFired()
{
    IntPtr handle = GetPreviousWindow();
    SendMessage(handle, WM_COPY, IntPtr.Zero, IntPtr.Zero);
    _viewModel.Input = Clipboard.GetText();
    ...

And it works in Word but not in Excel, Notepad, Visual Studio

Niels Bosma
  • 11,758
  • 29
  • 89
  • 148
  • 1
    I'm guessing you want to copy what is selected in the current app so it can be used by/pasted into your app that is activated by the hotkey. In that case you could try sending the [`WM_COPY` message](https://msdn.microsoft.com/en-us/library/windows/desktop/ms649022%28v=vs.85%29.aspx) to the current app, _and then_ switch to your app. That way, you don't have to faff about trying to get the previously selected app. – Steven Rands Mar 10 '15 at 16:39
  • 2
    Have you tried `^c`, not `^(c)`? – dymanoid Mar 10 '15 at 16:46
  • @StevenRands How do I do that? My app is activated by the hotkey so I need to activate the previous one to send the key? – Niels Bosma Mar 11 '15 at 05:47
  • @NielsBosma I assume you are using the `RegisterHotKey` Windows API? If so, that just intercepts the key and posts a message to the registered window doesn't it? (I haven't used this particular API before so I genuinely don't know). The MSDN docs say nothing about the app that owns the registered window being activated when the hotkey is pressed. – Steven Rands Mar 11 '15 at 09:16
  • I show how to do it with the `RegisterHotKey` method here: http://stackoverflow.com/questions/35297710/sendkeys-ctrl-c-to-external-applications-text-into-clipboard on the previously active window. – Jeremy Thompson Feb 24 '16 at 05:34

3 Answers3

5
    SendKeys.SendWait("^(c)");

That does not send Ctrl+C, the parentheses are sent as well. You probably meant "^{c}", curly braces instead of parentheses. Otherwise the reason that Word inserts the copyright symbol, it auto-corrects (c) to ©. Fix:

    SendKeys.SendWait("^c");

If you still have problems, you probably will, then read the MSDN article for SendKeys. It recommends using a .config file so it uses a different way to inject keystrokes. SendInput() works better than a journal hook on later Windows versions.

Hans Passant
  • 922,412
  • 146
  • 1,693
  • 2,536
1

I think your issue is related to timing.

To set the windows focus and to do the actual copy to clipboard you need to wait for the window to get focus, and to wait for the clipboard to be updated.

There are a couple of ways to deal with these ugly win32 things.

For the clipboard content. I compare the original content to the current content. I set the original content to string.empty if its an image or some other none text data. I then await a function that checks the clipboard for changes.

For the SetForegroundWindow I currently add a delay in my async function. You could also probably find a win32 api call to wait for this window to properly be put in the foreground.

I do both of these in an async function and await it so as to no block.

SendKeys should work with SendWait("^c"). SendWait("^(c)") will not always work as noted in the other answers. However the ctrl+c copy to clipboard doesn't happen instantly.

Point p;
if (GetCursorPos(out p))
{
    IntPtr ptr = WindowFromPoint(p);
    if (ptr != IntPtr.Zero)
    {                    
        SetForegroundWindow(ptr);

        //wait for window to get focus quick and ugly
        // probably a cleaner way to wait for windows to send a message
        // that it has updated the foreground window
        await Task.Delay(300);

        //try to copy text in the current window
        SendKeys.Send("^c");

        await WaitForClipboardToUpdate(originalClipboardText);
   }
}

}

    private static async Task WaitForClipboardToUpdate(string originalClipboardText)
    {
        Stopwatch sw = Stopwatch.StartNew();

        while (true)
        {
            if (await DoClipboardCheck(originalClipboardText)) return;

            if (sw.ElapsedMilliseconds >= 1500) throw new Exception("TIMED OUT WAITING FOR CLIPBOARD TO UPDATE.");
        }
    }

    private static async Task<bool> DoClipboardCheck(string originalClipboardText)
    {
        await Task.Delay(10);

        if (!Clipboard.ContainsText()) return false;

        var currentText = Clipboard.GetText();

        Debug.WriteLine("current text: " + currentText + " original text: " + originalClipboardText);

        return currentText != originalClipboardText;
    }


   [DllImport("user32.dll")]
    private static extern bool GetCursorPos(out Point pt);

    [DllImport("user32.dll", EntryPoint = "WindowFromPoint", CharSet = CharSet.Auto, ExactSpelling = true)]
    private static extern IntPtr WindowFromPoint(Point pt);

    // Activate an application window.
    [DllImport("USER32.DLL")]
    public static extern bool SetForegroundWindow(IntPtr hWnd);

    [DllImportAttribute("user32.dll", EntryPoint = "GetForegroundWindow")]
    public static extern IntPtr GetForegroundWindow();

    [DllImport("user32.dll", SetLastError = true)]
    static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint processId);

Also - A real quick and dirty way to quickly validate/check if timing is your issue is you could put a Thread.Sleep(1000); after your SetForegroundWindow, and SendKeys.SendWait calls.

kmcnamee
  • 5,097
  • 2
  • 25
  • 36
0

I know you're programming something, but in case you do it because you didn't find an easier solution... You could try AutoHotKey, it's a small program that sits in your tray and do all kind of hotkeys/macros kind of stuff... You'll need to write a small script for your macro, something like that:

#c::Send !{ESC}, ^c

This simple script means:

  • #c:: (Define the hotkey Windows+c)
  • Send (Send some keystrokes)
  • !{ESC} (Alt+Esc which toggle to the last program)
  • ^c (Control+c)

There is all sort of fancy scripting that you can do with this software like launching programs, regexp, threading, mouse events and more...

I know it doesn't answer your question, but it's a simple alternative.

Nicolas C
  • 752
  • 5
  • 18