tag:blogger.com,1999:blog-76644685815593371552024-03-08T08:22:10.839-08:00Symbianic lifeYury Schkatulahttp://www.blogger.com/profile/00615519415444576045noreply@blogger.comBlogger1125tag:blogger.com,1999:blog-7664468581559337155.post-67869953957194513172010-03-28T08:06:00.000-07:002010-03-28T10:28:45.312-07:00Integrate with Symbian clipboard<span style="font-family:trebuchet ms;"><span style="font-size:85%;"><strong><span style="font-size:100%;">Introduction<br /></span></strong>One day you can face a trivial task: to introduce clipboard support into your Symbian application. At first moment, you’re confused a bit but the solution is nearby. Your SDK (at least from 3rd edition MR) provides an example:</span><br /></span><span style="font-size:85%;"><br /><span style="font-family:trebuchet ms;"><blockquote><p><span style="font-size:85%;"><span style="font-family:trebuchet ms;">SDK root\Examples\SysLibs\Clipboard\Basics</span></span><span style="font-family:trebuchet ms;"></p></span></blockquote></span></span><span style="font-size:85%;"><br /><span style="font-family:trebuchet ms;">This sample shows how to pass custom objects via clipboard. It looks great but some kind of overkill if to pass text only. Also, usage of custom stream hides your clipboard content from other apps.<br><br />Next stage of googling leads to built-in CPlainText class. It contains two methods to read text from or write to clipboard correspondingly. Nokia provides some kind of “Clipboard for dummies” article as well:<br /></span></span><a href="http://wiki.forum.nokia.com/index.php/Clipboard_Copy/Cut/Paste"><span style="font-family:trebuchet ms;font-size:85%;">http://wiki.forum.nokia.com/index.php/Clipboard_Copy/Cut/Paste</span></a><span style="font-family:trebuchet ms;"><br /><span style="font-size:85%;">Copy-paste the code into your sources, compile and run. End of story?</span> </span><br /><br /><span style="font-family:trebuchet ms;font-size:85%;">CPlainText could be very nice solution. But in Symbian reality, let read the manual. There is one small but important phrase which can figure out CPlainText as non-acceptable solution in your case:</span><br /><span style="font-family:trebuchet ms;font-size:85%;"><br /><blockquote>“…all line feeds are converted into paragraph delimiters…”</blockquote><br />What does it mean? Actually, you can play safe as long as your text is paragraph-delimited, and each piece is finished with “carriage return” and “line feed” symbols (code 13 and code 10 correspondingly). And if CPlainText receives, for example, Unix-style text, it modifies the text replacing single “line feed” symbol with pair of “carriage return” and “line feed”.</span><br /><span style="font-family:trebuchet ms;font-size:85%;"><ul><li>New text is longer (do not forget to re-allocate, buffer overrun attempt otherwise!)</span></li><li><span style="font-family:trebuchet ms;font-size:85%;">Some parsers may fail if expect exact string format (with no “carriage return” symbols, for example)</span></li><span style="font-family:trebuchet ms;font-size:85%;"><li>New text and old one are not equal, finally.</li></ul></span><br /><br /><br /><span style="font-size:85%;"><span style="font-size:100%;"><strong>Let’s read it. Naïve.</strong></span><br />So, we have to repeat what CPlainText does but without text corruption. This turns us back to SDK sample. Naïve <span style="font-family:trebuchet ms;">implementation</span> may look like following:<br /><br /><pre></span><br /><span style="font-size:85%;">TDesC * ReadFromClipboardL(RFs & aFs)<br />{<br /> TDesC *result = NULL;<br /> CClipboard *cb = CClipboard::NewForReadingLC(aFs);<br /><br /> TStreamId stid = cb->StreamDictionary().At(KClipboardUidTypePlainText);<br /> if (KNullStreamId != stid)<br /> {<br /> RStoreReadStream stream;<br /> stream.OpenLC(cb->Store(), stid);<br /><br /> TBuf<512> *buf = new (ELeave) TBuf<512>;<br /> buf->SetLength(512);<br /> stream >> (*buf)[0];<br /><br /> stream.Close();<br /> CleanupStack::Pop(); // stream.OpenLC<br /> result = buf;<br /> }<br /> CleanupStack::PopAndDestroy(cb);<br /><br /> return result;<br />}</span><span style="font-size:85%;"></pre></span><br /><span style="font-size:85%;">We try to open plain-text stream of the clipboard and if so, read from the stream. Actually, we have no idea how long is the text so let hard-code some value as 512 symbols (yes, hard-coding is bad style but this time we’re naïve, right?).<br /><br />Start any text-handling Symbian app (for example, Notes application or SMS Editor) and copy some text to clipboard. Let copy something short and trivial, “Ping” word for example. After that, switch to our test application and run ReadFromClipboardL() code. Regardless dumping to screen or evaluating in debugger, our buffer contains complete trash with no idea about clipboard text. If you try to paste the clipboard content into another text-handling Symbian app, everything is OK. So, error is on our side. </span><br /><br /><span style="font-family:trebuchet ms;font-size:85%;"><span style="font-size:100%;"><strong>Let’s read it. Reverse-engineering.</strong></span><br />If go back to revise our code, first suspicion could fall to hard-coded buffer length. Symbian applications get a clue how long the text is. It is reasonable to expect that plain text is not “so plain”. There should be some header with useful info. Time to reverse-engineering!<br />If reading clipboard stream byte-by-byte, it’s easy to obtain following picture:</span><br /><span style="font-family:trebuchet ms;font-size:85%;"><ul><li>First 4 bytes – length of clipboard text.</li><li><span style="color:#006600;"><strong>Next bytes – viola, clipboard content!</strong></span></li><li>Zero byte</li><li>Byte with value of 2 (0x02 code) – have no idea about. Number of TInt32 below?</li><li>4 bytes with KClipboardUidTypePlainText value</li><li>4 bytes with KMaxFieldBufferSize value</li></ul><br />We cool! We’ve hacked it! Let’s update the code:<br /><pre><br /><span style="font-family:courier new;">TDesC * ReadFromClipboardL(RFs & aFs)<br />{<br /> TDesC *result = NULL;<br /> CClipboard *cb = CClipboard::NewForReadingLC(aFs);<br /><br /> TStreamId stid = cb->StreamDictionary().At(KClipboardUidTypePlainText);<br /> if (KNullStreamId != stid)<br /> {<br /> RStoreReadStream stream;<br /> stream.OpenLC(cb->Store(), stid);<br /> const TInt32 size = stream.ReadInt32L();<br /> HBufC *buf = HBufC::NewLC(size);<br /> buf->Des().SetLength(size);<br /><br /> for(TInt i = 0; i <>> buf->Des()[i];<br /><br /> CleanupStack::Pop(buf);<br /> stream.Close();<br /> CleanupStack::Pop(); // stream.OpenLC<br /><br /> result = buf;<br /> }<br /> CleanupStack::PopAndDestroy(cb);<br /><br /> return result;<br />}</span><br /></pre><br /> <br />Compile, run… Bang! Number of chars is correct but stupid rectangles instead of the text are on the screen. What’s wrong?<br />By default, modern Symbian S60 treats all chars as Unicode (16 bit wide) but as we “reversed” right now, the clipboard operates with 8 bit wide chars. C’mon, let replace TDesC with TDesC8 and HBufC with HBufC8. Compile, run… Great! We see correct clipboard text (“Ping” word, remember?). You can submit your code into production and go develop other tasks. Or go eat, whatever. Enjoy your coffee until QA engineer or even overseas customer does complain about broken clipboard functionality for non-English languages.</span><br /><br /><br /><span style="font-family:trebuchet ms;font-size:85%;"><strong><span style="font-size:100%;">Let’s read it. Unicode.</span></strong><br />Quickly repeating our reverse-engineering, we can figure out that non-English chars are present in clipboard stream but they’re encoded somehow. Looks like, 7-bit chars (with zero high bit) are chained “as is” but non-English chars (with high bit set) are prefixed with some extra byte (but it is not counted in string length). Coding this staff manually is going to be boring but other apps do it somehow, right? CPlainText does it as well. So, built-in decoder class should be nearby. And its name is TUnicodeExpander:</span><br /><span style="font-family:courier new;font-size:85%;"><br /><pre><br />TDesC * ReadFromClipboardL(RFs & aFs)<br />{<br /> TDesC *result = NULL;<br /> CClipboard *cb = CClipboard::NewForReadingLC(aFs);<br /><br /> TStreamId stid = cb->StreamDictionary().At(KClipboardUidTypePlainText);<br /> if (KNullStreamId != stid)<br /> {<br /> RStoreReadStream stream;<br /> stream.OpenLC(cb->Store(), stid);<br /> const TInt32 size = stream.ReadInt32L();<br /> HBufC *buf = HBufC::NewLC(size);<br /> buf->Des().SetLength(size);<br /><br /> TUnicodeExpander e;<br /> TMemoryUnicodeSink sink(&buf->Des()[0]);<br /> e.ExpandL(sink, stream, size);<br /><br /> CleanupStack::Pop(buf);<br /> stream.Close();<br /> CleanupStack::Pop(); // stream.OpenLC<br /> result = buf;<br /> }<br /> CleanupStack::PopAndDestroy(cb);<br /><br /> return result;<br />}<br /><br /></pre><br /></span><br /><span style="font-family:trebuchet ms;font-size:85%;">Happy end. Music. Credits. Lights on. </span><br /><br /><span style="font-family:trebuchet ms;font-size:85%;"><strong><span style="font-size:100%;">Write it.</span></strong><br />Taking into account our latest knowledge, it could be piece of cake to compose a code with opposite functionality.</span><span style="font-family:trebuchet ms;font-size:85%;"><br /><ul><li>Create KClipboardUidTypePlainText stream</li><li>Put 4 bytes with string length</li><li>Put encoded text</li><li>Close the stream</li></ul>Let give a try:</span><span style="font-family:courier new;font-size:85%;"><br /><pre><br />void WriteToClipboardL(RFs &aFs, const TDesC & aText)<br />{<br /> CClipboard *cb = CClipboard::NewForWritingLC(aFs);<br /><br /> RStoreWriteStream stream;<br /> TStreamId stid = stream.CreateLC(cb->Store());<br /> stream.WriteInt32L(aText.Length());<br /><br /> TUnicodeCompressor c;<br /> TMemoryUnicodeSource source(aText.Ptr());<br /> TInt bytes(0);<br /> TInt words(0);<br /> c.CompressL(stream, source, KMaxTInt, aText.Length(), &bytes, &words);<br /><br /> stream.CommitL();<br /> cb->StreamDictionary().AssignL(KClipboardUidTypePlainText, stid);<br /> cb->CommitL();<br /><br /> stream.Close();<br /> CleanupStack::PopAndDestroy(); // stream.CreateLC<br /> CleanupStack::PopAndDestroy(cb);<br />}<br /></pre><br /></span><br /> <br /><span style="font-family:trebuchet ms;font-size:85%;"><br />From first glance, this code runs OK, Symbian clipboard does receive the text. You’re in trouble next step, while pasting into any text-handling Symbian app. Your text appears but accompanied with “System error (-25)” message. What’s wrong?<br />If you step back to our reverse engineering results, you could see the answer. The most funny thing here that any Windows-experienced programmer would point this answer in few seconds. Same time, you can’t imagine this is really the answer if you’re experienced with Symbian text descriptors. It is a real mind-trap. You have to put zero byte at the end of encoded text!</span><br /><br /><pre><br /><span style="font-family:trebuchet ms;font-size:85%;"><span style="font-family:courier new;">void WriteToClipboardL(RFs &aFs, const TDesC & aText)<br />{<br /> CClipboard *cb = CClipboard::NewForWritingLC(aFs);<br /><br /> RStoreWriteStream stream;<br /> TStreamId stid = stream.CreateLC(cb->Store());<br /> stream.WriteInt32L(aText.Length());<br /><br /> TUnicodeCompressor c;<br /> TMemoryUnicodeSource source(aText.Ptr());<br /> TInt bytes(0);<br /> TInt words(0);<br /> c.CompressL(stream, source, KMaxTInt, aText.Length(), &bytes, &words);<br /><br /> stream.WriteInt8L(0); // magic command! :)<br /><br /> stream.CommitL();<br /> cb->StreamDictionary().AssignL(KClipboardUidTypePlainText, stid);<br /> cb->CommitL();<br /><br /> stream.Close();<br /> CleanupStack::PopAndDestroy(); // stream.CreateLC<br /> CleanupStack::PopAndDestroy(cb);<br />}</span><br /></pre></span><br /> <br /><span style="font-family:trebuchet ms;font-size:85%;"><span style="font-size:100%;"><strong>P.S. </strong></span><br />I had no chance to verify it vs. hieroglyphs. Anybody?<br /></span>Yury Schkatulahttp://www.blogger.com/profile/00615519415444576045noreply@blogger.com2