Talking Clipboard






4.62/5 (21 votes)
An application that automatically speaks the contents of the Windows clipboard
Introduction
TalkingClipboard
is a little .NET app that speaks text that has been copied to the Windows clipboard, or that is manually typed into the application. The application demonstrates how to listen for Windows clipboard events and how to use the SpeechSynthesizer
class that's new in .NET 3.0. This article was inspired in part by this Lounge post. I hope you have as much fun using TalkingClipboard
as I had writing it!

How to Use TalkingClipboard
TalkingClipboard
is easy to use - just start it and copy some text to the Windows clipboard. The application displays the contents of the clipboard and starts speaking it. If you copy new text into the clipboard, TalkingClipboard
will interrupt its monolog and respond with the clipboard's new contents.
To stop the current speech, click "Stop". You can also directly type into the text box and click "Speak" to have text read aloud to you. Click "Pause/Resume" to pause and resume speaking. Check the "Stop monitoring clipboard" box to temporarily prevent TalkingClipboard
from grabbing the clipboard's text. To save the speech to a .WAV
file, click the "Save As .WAV File..." button.
If you have more than one voice installed on your system, you can select a voice by clicking the "Voice" selector. The "Speed" trackbar allows you to select a speed between -10 and +10, zero being the default.
How It Works
There are two aspects to TalkingClipboard
's operation:
- listening to the Windows clipboard
- the TTS (text to speech) engine
Listening to the Windows Clipboard
Listening to the Windows clipboard is done using a few Win32 APIs:
SetClipboardViewer()
This API registers the application's main window with the Windows clipboard. Once registered, Windows sends our main window aWM_DRAWCLIPBOARD
message when the contents of the clipboard changes.ChangeClipboardChain()
This API is used to set the clipboard viewer chain (i.e. the list of windows registered with the Windows clipboard) back to the state before our application became a viewer. This is done when our application exits.- We also use the ubiquitous
SendMessage()
API to delegate the handling of changes to the clipboard viewer chain (sent asWM_CHANGECBCHAIN
notifications) to other windows.
These APIs are made available to us via .NET's interop services located in the System.Runtime.InteropServices
namespace. Perhaps the most important (and commonly used) functionality provided by this namespace is the DllImport
attribute that lets us define a managed equivalent of a Win32 API. Invoking a Windows platform API is usually referred to as P/Invoke (for "platform invoke"). The site www.pinvoke.net
contains a large collection of predefined P/Invoke signatures. For our purposes, we use the following declarations:
[DllImport("user32.dll")]
static extern IntPtr SetClipboardViewer (IntPtr hWndNewViewer);
[DllImport("user32.dll")]
static extern bool ChangeClipboardChain (IntPtr hWndRemove, IntPtr hWndNewNext);
[DllImport("user32.dll")]
public static extern int SendMessage
(IntPtr hwnd, int wMsg, IntPtr wParam, IntPtr lParam);
In order to process notifications sent by Windows, we override Form.WndProc()
and handle the appropriate messages. The notifications that interest us are WM_DRAWCLIPBOARD
and WM_CHANGECBCHAIN
.
const int WM_DRAWCLIPBOARD = 0x0308;
const int WM_CHANGECBCHAIN = 0x030D;
protected override void WndProc
(ref Message m)
{
base.WndProc (ref m);
switch (m.Msg) {
case WM_DRAWCLIPBOARD:
...
break;
case WM_CHANGECBCHAIN:
...
break;
}
}
When Windows sends us a WM_DRAWCLIPBOARD
message to inform us that the contents of the clipboard have changed, we first check if the new contents are a blob of text. If so, we obtain the text and place it in the text box and request the TTS engine to speak it, as shown below:
IDataObject dataObj = Clipboard.GetDataObject();
if (dataObj.GetDataPresent (DataFormats.Text)) {
string clipboardText = dataObj.GetData (DataFormats.Text) as string;
_editText.Text = clipboardText;
if (_synth.State == SynthesizerState.Speaking)
_synth.SpeakAsyncCancelAll();
_btnSpeak.PerformClick();
}
The application receives the WM_CHANGECBCHAIN
notification when the chain of clipboard listeners has changed. If the next window in the clipboard chain has changed, we keep track of the fact. For other cases of WM_CHANGECBCHAIN
, we delegate the handling to the next window in the chain by forwarding it the message.
if (m.WParam == _chainedWnd)
_chainedWnd = m.LParam;
else
SendMessage (_chainedWnd, m.Msg, m.WParam, m.LParam);
The TTS (Text to Speech) Engine
Among the new bits in .NET 3.0 is the SpeechSynthesizer
class that exposes SAPI's functionality to managed code without having to resort to P/Invoke. SpeechSynthesizer
is located in the System.Speech.Synthesis
namespace. To speak text, we simply call Speak()
or SpeakAsync()
with a string containing the text to be spoken. To terminate the speech, we call SpeakAsyncCancelAll()
.
SpeechSynthesizer ss = new SpeechSynthesizer();
ss.SpeakAsync ("Hello, world");
...
ss.SpeakAsyncCancelAll();
The speech is spoken using the object's current properties of Voice
, Rate
and Volume
. Voice
is the name of an installed voice, Rate
is an integer between -10 and 10, and Volume
is an integer between 0 and 100 and represents the percentage of the audio device's main volume. The list of installed voices is obtained by calling GetInstalledVoices()
as shown below:
ReadOnlyCollection<InstalledVoice> voices = ss.GetInstalledVoices();
if (voices.Count > 0)
ss.Voice = voices[0];
Saving the spoken text as a .WAV file is easily done by directing the synthesizer's output to the file, speaking the text, and redirecting the output to the default audio device.
// Save speech as .WAV file
ss.SetOutputToWaveFile ("C:\\hello.wav");
ss.Speak ("Hello, world");
ss.SetOutputToDefaultAudioDevice();
Conclusion
There you have it - a simple yet useful little app that was fun to write and hopefully fun to learn from.
Revision History
- 14 Jan 2008
- Added checkbox to suspend monitoring the clipboard
- Added Pause/Resume speech functionality
- 14 Jan 2008
- Initial version