Added ENotifyKeypresses and ECaptureCtrlC flags to CCommandBase.
Commands can now get keypresses and handle ctrl-C via callbacks instead of having to implement custom active objects. As part of this extended the CCommandBase extension interface to MCommandExtensionsV2 for the new virtual functions KeyPressed(TUint aKeyCode, TUint aModifiers) and CtrlCPressed(). sudo now cleans up correctly by using ECaptureCtrlC.
// terminalkeyboardcons.cpp
//
// Copyright (c) 2010 Accenture. All rights reserved.
// This component and the accompanying materials are made available
// under the terms of the "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Accenture - Initial contribution
//
#include "terminalkeyboardcons.h"
#include <fshell/common.mmh>
#include <e32debug.h>
#include <fshell/memoryaccess.h>
const TInt KTkbdMessageId = 0xD6;
// These are the defaults unless overridden by --console-size
const TInt KWidth = 80;
const TInt KHeight = 24;
class CMessageWatcher : public CActive
{
public:
CMessageWatcher(CTerminalKeyboardCons& aConsole)
: CActive(CActive::EPriorityStandard), iConsole(aConsole)
{
CActiveScheduler::Add(this);
iConsole.iDriver.RequestMessage(iStatus);
SetActive();
}
void DoCancel()
{
iConsole.iDriver.CancelMessage();
}
void RunL()
{
iConsole.MessageReceived(iStatus.Int());
if (iStatus.Int() == KErrNone)
{
iConsole.iDriver.RequestMessage(iStatus);
SetActive();
}
}
~CMessageWatcher()
{
Cancel();
}
private:
CTerminalKeyboardCons& iConsole;
};
CTerminalKeyboardCons::CTerminalKeyboardCons()
: iTracker(TSize(KWidth, KHeight), this)
{
}
CTerminalKeyboardCons::~CTerminalKeyboardCons()
{
delete iIdleUpdateTimer;
CleanupUnderlyingConsole();
delete iWatcher;
iDriver.Close();
iTextBuffer.Close();
iMungedTextBuffer.Close();
}
TInt CTerminalKeyboardCons::Create(const TDesC& aTitle, TSize aSize)
{
TRAPD(err, ConstructL(aTitle, aSize));
HandleConsoleCreationError(_L("TerminalKeyboard"), err);
return err;
}
void CTerminalKeyboardCons::ConstructL(const TDesC& aTitle, const TSize& aSize)
{
if (aTitle == _L("debug")) SetDebug(ETrue);
if (aSize.iWidth > 10 && aSize.iHeight > 10)
{
// Override size now we know it, checking that it's sane-ish - be sure to do this before anything that uses the console size!
new (&iTracker) TCursorTracker(aSize, this);
}
iTextBuffer.CreateMaxL(ScreenSize().iWidth * ScreenSize().iHeight);
#ifdef SHOW_TEXTSHELL_BORDERS
iMungedTextBuffer.CreateL((ScreenSize().iWidth + 2) * (ScreenSize().iHeight + 2));
#endif
iIdleUpdateTimer = CPeriodic::NewL(CActive::EPriorityLow); // Lower priority than the message watcher
TInt err = User::LoadLogicalDevice(KTcLddDriverName);
if (err && err != KErrAlreadyExists)
{
Message(EError, _L("Couldn't load LDD %S: %d"), &KTcLddDriverName, err);
User::Leave(err);
}
// Idiotic driver only accepts connections from processes with nokia vid - like that will stop us
TUint originalVid = RProcess().VendorId();
RMemoryAccess memAccess;
User::LeaveIfError(memAccess.Open());
TProcessProperties props;
props.iVid = 0x101FB657;
RProcess me; me.Open(RProcess().Id());
memAccess.SetProcessProperties(me, props);
err = iDriver.Open();
props.iVid = originalVid;
memAccess.SetProcessProperties(me, props);
me.Close();
memAccess.Close();
if (err)
{
Message(EError, _L("Couldn't open RTcDriver: %d"), err);
User::Leave(err);
}
err = iDriver.Subscribe(KTkbdMessageId);
if (err)
{
Message(EError, _L("Couldn't subscribe: %d"), err);
User::Leave(err);
}
iWatcher = new(ELeave) CMessageWatcher(*this);
CleanupUnderlyingConsole();
ClearScreen();
}
TInt CTerminalKeyboardCons::Extension_(TUint aExtensionId, TAny*& a0, TAny* a1)
{
/*if (aExtensionId == ConsoleMode::KSetConsoleModeExtension)
{
ConsoleMode::TMode mode = (ConsoleMode::TMode)(TInt)a1;
iInputController->SetMode(mode);
iOutputController->SetMode(mode);
return KErrNone;
}
else*/
/*else if (aExtensionId == ConsoleAttributes::KSetConsoleAttributesExtension)
{
ConsoleAttributes::TAttributes* attributes = (ConsoleAttributes::TAttributes*)a1;
return iOutputController->SetAttributes(attributes->iAttributes, attributes->iForegroundColor, attributes->iBackgroundColor);
}*/
TInt ret = MIosrvConsoleHelper_Extension(aExtensionId, a0, a1);
if (ret == KErrExtensionNotSupported) ret = CConsoleBase::Extension_(aExtensionId, a0, a1);
return ret;
}
void CTerminalKeyboardCons::MessageReceived(TInt aError)
{
Message(EDebug, _L("MessageReceived err=%d"), aError);
TInt err = aError;
if (err == KErrNone)
{
TPtrC8 data;
err = iDriver.GetMessageData(data);
if (!err && data.Length() < 1) err = KErrCorrupt;
if (!err && !iGotKey)
{
TUint16 rawkey = data[0];
Message(EDebug, _L("CTerminalKeyboardCons got key %d"), (TInt)rawkey);
// Terminal keyboard doesn't support control keys or cursor keys so we use our own two-stage sticky modifier (like unix meta key I think?)
if (iBacktickModifierDown)
{
// Terminal keyboard sends 2,4,6,8 for cursors, so we translate backtick-2 (ie backtick-leftarrow) as meaning left arrow
if (rawkey == '2') rawkey = (TUint16)EKeyUpArrow;
else if (rawkey == '4') rawkey = (TUint16)EKeyLeftArrow;
else if (rawkey == '6') rawkey = (TUint16)EKeyRightArrow;
else if (rawkey == '8') rawkey = (TUint16)EKeyDownArrow;
else if (rawkey >= 'a' && rawkey <= 'z')
{
// backtick-c means CTRL-C
rawkey = rawkey - 'a'+1;
}
else if (rawkey == ' ') rawkey = '`'; // backtick-space is how you do a backtick
else if (rawkey == '`') rawkey = (TUint16)EKeyEscape; // backtick-backtick is 'escape'
else if (rawkey == '1') rawkey = (TUint16)EKeyTab; // backtick-1 is 'tab'
iBacktickModifierDown = EFalse;
Message(EDebug, _L("Backtick escape converted to %d"), (TInt)rawkey);
}
else if (rawkey == '`')
{
iBacktickModifierDown = ETrue;
return;
}
iKeyCode = (TKeyCode)rawkey; // Close enough for now!
iGotKey = ETrue;
}
}
if (iClientStatus)
{
User::RequestComplete(iClientStatus, err);
if (err == KErrNone) iGotKey = EFalse;
}
}
void CTerminalKeyboardCons::Read(TRequestStatus& aStatus)
{
/*if (iClientStatus)
{
TRequestStatus* stat = &aStatus;
User::RequestComplete(stat, KErrInUse);
}
else*/
{
aStatus = KRequestPending;
iClientStatus = &aStatus;
if (iGotKey)
{
iGotKey = EFalse;
User::RequestComplete(iClientStatus, KErrNone);
}
}
}
void CTerminalKeyboardCons::ReadCancel()
{
if (iClientStatus)
{
User::RequestComplete(iClientStatus, KErrCancel);
}
}
void CTerminalKeyboardCons::Write(const TDesC& aDes)
{
for (TInt i = 0; i < aDes.Length(); i++)
{
TInt textBufPos = CursorPos().iY * ScreenSize().iWidth + CursorPos().iX;
TChar ch(aDes[i]);
iTracker.WriteChar(ch);
if (ch.IsPrint()) iTextBuffer[textBufPos] = aDes[i];
}
Update();
}
TPoint CTerminalKeyboardCons::CursorPos() const
{
return iTracker.CursorPos();
}
void CTerminalKeyboardCons::SetCursorPosAbs(const TPoint& aPoint)
{
iTracker.SetCursorPosAbs(aPoint);
}
void CTerminalKeyboardCons::SetCursorPosRel(const TPoint& aPoint)
{
iTracker.SetCursorPosRel(aPoint);
}
void CTerminalKeyboardCons::SetCursorHeight(TInt /*aPercentage*/)
{
//iOutputController->SetCursorHeight(aPercentage);
}
void CTerminalKeyboardCons::SetTitle(const TDesC& /*aDes*/)
{
//iOutputController->SetTitle(aDes);
}
void CTerminalKeyboardCons::ClearScreen()
{
SetCursorPosAbs(TPoint(0,0));
iTextBuffer.Fill(' ');
Update();
}
void CTerminalKeyboardCons::ClearToEndOfLine()
{
iTextBuffer.MidTPtr(CursorPos().iY * ScreenSize().iWidth + CursorPos().iX, ScreenSize().iWidth - CursorPos().iX).Fill(' ');
Update();
}
TSize CTerminalKeyboardCons::ScreenSize() const
{
return iTracker.ConsoleSize();
}
TKeyCode CTerminalKeyboardCons::KeyCode() const
{
return iKeyCode;
}
TUint CTerminalKeyboardCons::KeyModifiers() const
{
return iKeyModifiers;
}
// All these magic vals are as per the DOS 437 codepage http://en.wikipedia.org/wiki/CP437 and not Windows-1252 like the ws_win.cpp source incorrectly states
// See also http://en.wikipedia.org/wiki/Box_drawing_characters
enum
{
EBoxHorizontalBar = 0xCD,
EBoxVerticalBar = 0xBA,
EBoxTopLeft = 0xC9,
EBoxTopRight = 0xBB,
EBoxBottomLeft = 0xC8,
EBoxBottomRight = 0xBC,
};
void CTerminalKeyboardCons::Update()
{
// Delay updates to the screen, to improve performance
static const TInt KDelay = 100000; // 0.1s
if (!iIdleUpdateTimer->IsActive())
{
// Don't reset timer if we're already running - otherwise constant typing will never show up until you stop pressing keys
iIdleUpdateTimer->Start(KDelay, KDelay, TCallBack(&UpdateCallback, this));
}
}
TInt CTerminalKeyboardCons::UpdateCallback(TAny* aSelf)
{
static_cast<CTerminalKeyboardCons*>(aSelf)->DoUpdate();
return 0;
}
void CTerminalKeyboardCons::DoUpdate()
{
iIdleUpdateTimer->Cancel(); // Stop any further updates
#ifdef SHOW_TEXTSHELL_BORDERS
// Update munged buffer
const TInt contentWidth = ScreenSize().iWidth;
const TInt width = contentWidth + 2;
iMungedTextBuffer.SetLength(width);
iMungedTextBuffer.Fill(TChar(EBoxHorizontalBar));
iMungedTextBuffer[0] = EBoxTopLeft;
iMungedTextBuffer[width-1] = EBoxTopRight;
for (TInt i = 0; i < ScreenSize().iHeight; i++)
{
TPtrC line(iTextBuffer.Mid(i*contentWidth, contentWidth));
iMungedTextBuffer.AppendFormat(_L("%c%S%c"), EBoxVerticalBar, &line, EBoxVerticalBar);
}
iMungedTextBuffer.Append(EBoxBottomLeft);
iMungedTextBuffer.SetLength(iMungedTextBuffer.Length() + contentWidth);
iMungedTextBuffer.RightTPtr(contentWidth).Fill(EBoxHorizontalBar);
iMungedTextBuffer.Append(EBoxBottomRight);
// And send the munged buffer
Transmit(iMungedTextBuffer, width, ScreenSize().iHeight + 2);
#else
// Just send it straight
Transmit(iTextBuffer, ScreenSize().iWidth, ScreenSize().iHeight);
#endif
}
void CTerminalKeyboardCons::Transmit(const TDesC& aBuf, TInt aWidth, TInt aHeight)
{
// This is how terminal keyboard does it - pretty horrible really
static const TInt KMaxLen = 200; // This is what terminal keyboard uses - technically you could go as far as a total of 256
TInt numLinesPerPrint = KMaxLen / aWidth;
TBuf<256> line;
TInt startLine = 0;
while (startLine < aHeight)
{
if (startLine == 0)
{
// First line has extra info
line.Format(_L("#$STIConsole#$%02d%02X%02X"), startLine, aWidth, aHeight);
}
else
{
line.Format(_L("#$STIConsole#$%02d"), startLine);
}
for (TInt i = 0; i < numLinesPerPrint; i++)
{
TPtrC theContents(aBuf.Mid((startLine+i)*aWidth, aWidth));
//RDebug::Printf("line len=%d theContents len=%d", line.Length(), theContents.Length());
line.Append(theContents);
}
RDebug::Print(line);
startLine += numLinesPerPrint;
}
}
void CTerminalKeyboardCons::ConsoleScrolled(TInt aNumberOfLines)
{
TInt numChars = Abs(aNumberOfLines) * ScreenSize().iWidth;
if (aNumberOfLines > 0)
{
iTextBuffer.Delete(0, numChars);
iTextBuffer.AppendFill(' ', numChars);
}
else if (aNumberOfLines < 0)
{
iTextBuffer.SetLength(iTextBuffer.Length() - numChars);
while (numChars--) iTextBuffer.Insert(0, _L(" "));
}
}
extern "C" EXPORT_C TAny* NewConsole()
{
return new CTerminalKeyboardCons;
}