漢王手寫輸入的系統部分(Palm平臺) (20千字)
漢王手寫輸入的系統部分
工具: IDA Pro 4.x
平臺: Palm OS 3.5, Motorola 68000 指令集
下載: http://www.hwpen.net/download.htm
年初, 下了一個漢王試了一下, 覺得它的浮動選單做的比較好,
因為它在任何App下都可以冒出來. 要知道Palm是單任務的, 在一個時候只能有一個APP在執行. 當時剛剛接觸Palm, 如果要讓我實現這些功能, 還真的不知道怎麼下手.
於是想到了用IDA試試在非Windows平臺的反彙編. 最後得到的這些程式碼完全說明了它是怎樣實現這些功能的. 對於Palm程式設計師是有參考價值的.
另外, 有幾點需要特別說明:
1. 對於它的識別引擎, 我沒有做逆向, 因為我對它後面的高深理論不瞭解也沒有興趣.
2. 因為是為了學習, 而不是破解這套軟體, 我沒有去逆向它的註冊碼演算法部分. 從支援國產軟體方面考慮, 我也不會這樣做.(言下之意:漢王你可別找我麻煩
:) )
3. 這裡公佈的只是任何Palm書籍上都有的程式設計的最基礎部分, 應該不會對漢王在技術上構成什麼危害.
4. 這些可以編譯的程式碼只用於學習目的,
請勿做它用.
5. 用SetTrap的方法掛接HW_SysHandleEvent()而沒有其它措施是不安全的,因為系統在特定的時候會移動程式碼資源,
這肯定會讓系統垮掉, 應該使用DmDatabaseProtect()對這段程式碼資源加以保護. 希望漢王以後會這樣做.
// #include
<windows.h>
#include <PalmOS.h>
#include <DLServer.h>
#include <PalmUtils.h>
#define version350
0x3503000
#define HW_MainForm
1000
#define RomIncompatibleAlert 1001
#define HWDefinedCmd
0xddbb
#define HW_COMMONCCHAR
0x0001
#define HW_SUBCOMMONCCHAR
0x0002
#define HW_TRA2SIMPLIFIED
0x0008
#define HW_NUMERIC
0x0010
#define HW_ENGLISH
0x0060
#define HW_SYMBOL
0x0180
#define HW_UPPER2LOWER 0x2000
#define HW_LOWER2UPPER 0x4000
#define HW_HALF2FULL 0x8000
// 選單模式
#define FOLDED 0 //
收成"V"
#define LOWER 1 // 在螢幕的下半部
#define UPPER 2 // 在螢幕的上半部
#define HIDDEN
3 // 隱藏
// 手寫系統階段
#define READY
0 // 就緒
#define ENTER 1
// 進入視窗或Form
#define LEAVE 2 //
離開視窗或Form
#define CHGFLD 3 // 切換到其它欄位
typedef struct tagHW_PREF {
Boolean bInstallHW;
// 是否安裝漢王手寫輸入系統. 0
Int8
al_b1; //
未被使用 1
UInt16 flags;
// 標誌位 2
UInt16 RecogRange;
// 識別範圍 4
Int8 RecogWaitTime;
// 識別等待時間 6
Int8 PenThickness;
// 筆跡粗細 7
Int8 PenColor;
// 筆的顏色 8
Int8 al_b9;
// 未被使用 9
Int16 ShotcutPenDownX; // 快捷方式的起點螢幕座標
X a
Int16 ShotcutPenDownY; // 快捷方式的起點螢幕座標
Y c
Int16 ShotcutPenUpX; // 快捷方式的終點螢幕座標
X e
Int16 ShotcutPenUpY; // 快捷方式的終點螢幕座標
Y 10
Boolean bSetting; //
是否正在設定快捷方式 12
Int8 bUsingShortcut; //
是否正在使用快捷方式 13
Boolean bSplitScreen; //
全屏分割 14
Boolean bHalfToFull; // 半形字元轉成全形字元
15
Boolean bUpperToLower; // 大寫字母轉成小寫字母
16
Boolean bLowerToUpper; // 小寫字母轉成大寫字母
17
Boolean bMouse;
// 是否作為滑鼠使用 18
Boolean bReged;
// 是否是註冊使用者: 0xff 是, 0 否. 19
Int8
MenuMode; // 0: FOLDED,
1: LOWER, 2: UPPER, 3: HIDDEN 1a
Boolean bMenuFolded;
// 選單是否被收成"V" 1b
Boolean bOpenHW;
// 是否啟動漢王手寫輸入 1c
Boolean bHalfScrnInput;
// 是否在半屏手寫模式 1d
Boolean bRedrawMenu;
// 選單是否需要重畫 1e
Int8 Phase;
// 手寫系統階段 0: READY, 1: ENTER,
2: LEAVE, 3: CHGFLD 1f
} HW_PREF;
typedef struct tagHW_PENS {
UInt16 Cands[20][10]; // 候選字Buffer 0
UInt8 index; //
候選字在候選字Buffer中的下標索引 0x190/400
UInt8 b191;
// not used
UInt16
InsPtPos; // 插入點偏移, 192h, 402
UInt16 CandiDispOfs; // 候選字顯示偏移 194h 404
char* AssocBuffer; // 聯想字buf
196h 406
UInt16 AssocNum; //
聯想字個數 19a 410
UInt8 AssocOfs;
// 聯想字偏移 19c 412
UInt8
AssocDispOfs; // 聯想字顯示偏移 19d 413
} HW_PENS;
typedef struct tagHW_TRACE {
PointType points[2048];
// 筆跡取樣點陣列 0
UInt16 PtCount;
// 筆跡取樣存放位置 2000
UInt32 dw2002; // 2002
UInt8 b2006; // 2006
UInt8 b2007; //
2007
UInt8 b2008; // 2008
} HW_TRACE;
int HW_Install(void);
void HW_Uninstall(void);
UInt16 GetRecogRange(HW_PREF*
pPref)
{
UInt16 flags;
if
(pPref->RecogRange)
flags = pPref->RecogRange;
else
flags
= pPref->flags;
if (pPref->bHalfToFull)
flags |= 0x8000;
if
(pPref->bUpperToLower)
flags |= 0x2000;
if (pPref->bLowerToUpper)
flags |= 0x4000;
return flags;
}
Int8 GetMenuMode(void)
{
Int16 x, y;
InsPtGetLocation(&x, &y);
if
(y > 109)
return UPPER;
else
return LOWER;
}
char V[] = "V";
void ClearMenu(Int8 MenuMode)
{
RectangleType rc;
if (MenuMode
== HIDDEN) return;
if (MenuMode == FOLDED) {
WinEraseChars(V, 1, 141, 141);
return;
}
if (MenuMode == LOWER)
RctSetRectangle(&rc,
0, 118, 160, 30);
else if (MenuMode == UPPER)
RctSetRectangle(&rc, 0, 78, 160, 30);
WinEraseRectangle(&rc, 0/*cornerDiam, 0 for square
corners*/);
}
void RedrawCurForm(void)
{
FormPtr
frm = FrmGetActiveForm();
WindowType* winHandle = FrmGetWindowHandle(frm);
if (winGetActiveWindow() == winHandle &&
FrmValidatePtr(frm))
FrmDrawForm(frm);
}
void DrawMenu(HW_PREF* pPrefs, HW_PENS* pHWPens) // a3 = pPrefs,
a2 = pHWPens
{
int ofs, x, d5;
RectangleType
rc;
UInt16* ptr;
if (pHWPens->index
== 0)
x = 0;
else
x = pHWPens->index - 1;
if (pPrefs->MenuMode == UPPER) {
RctSetRectangle(&rc,
2, 80, 155, 25);
WinDrawRectangleFrame(dialogFrame,
&rc);
// This routine does
not draw in the gray color; it draws with
//
alternating foreground and background pixels. That is, it uses
// the grayPattern pattern type.
WinDrawGrayLine(74,
93, 74, 104);
WinDrawGragLine(2, 92,
156, 92);
WinDrawChars("→", 2, 63,
94);
WinDrawChars("英", 2, 76,
94);
if (pPrefs->RecogRange == HW_ENGLISH)
{
RctSetRectangle(&rc,
75, 93, 12, 12);
WinInvertRectangle(&rc,
0);
}
WinDrawChars("數", 2, 88, 94);
if
(pPrefs->RecogRange == HW_NUMERIC) {
RctSetRectangle(&rc, 87, 93, 12, 12);
WinInvertRectangle(&rc, 0);
}
WinDrawChars("符",
2, 100, 94);
if (pPrefs->RecogRange
== HW_SYMBOL) {
RctSetRectangle(&rc,
99, 93, 12, 12);
WinInvertRectangle(&rc,
0);
}
WinDrawChars("全", 2, 112, 94);
if
(pPrefs->bHalfToFull) {
RctSetRectangle(&rc,
111, 93, 12, 12);
WinInvertRectangle(&rc,
0);
}
WinDrawChars("窗", 2, 124, 94);
WinDrawChars("鼠", 2, 136, 94);
if
(pPrefs->bMouse) {
RctSetRectangle(&rc,
135, 93, 12, 12);
WinInvertRectangle(&rc,
0);
}
WinDrawChars("^", 1, 148, 94);
WinDrawChars("←",
2, 132, 81);
WinDrawChars("→", 2, 145,
81);
d5 = pHWPens->CandiDispOfs
* 2;
ptr = pHWPens->Cands[x];
// 顯示5個候選字
if (ptr[d5] || ptr[d5 + 1]) {
for (ofs = d5; ofs < d5 + 10; ofs += 2) {
if (ptr[ofs]) {
WinDrawChars(&ptr[ofs],
2, x, 94);
x += 12;
}
else {
if (ptr[d5 + 1] <= ' ')
break;
WinDrawChars(&ptr[d5 + 1],
1, x, 94);
x += 12;
}
}
}
// 顯示10個聯想字
if (pHWPens->AssocNum)
{
x = 3;
for (ofs = pHWPens->AssocDispOfs;
ofs < pHWPens->AssocOfs + 20; ofs += 2) {
if (ofs - pHWPens->AssocOfs < pHWPens->AssocNum)
break;
WinDrawChars(&pHWPens->AssocBuffer[ofs], 2, x, 81);
x += 13;
}
}
}
else if (pPrefs->MenuMode
== LOWER) {
RctSetRectangle(&rc,
2, 120, 155, 25);
WinDrawRectangleFrame(dialogFrame,
&rc);
// This routine does
not draw in the gray color; it draws with
//
alternating foreground and background pixels. That is, it uses
// the grayPattern pattern type.
WinDrawGrayLine(74,
133, 74, 144);
WinDrawGragLine(2, 132,
156, 312);
WinDrawChars("→", 2, 63,
134);
WinDrawChars("英", 2, 76,
134);
if (pPrefs->RecogRange == HW_ENGLISH)
{
RctSetRectangle(&rc,
75, 93, 12, 12);
WinInvertRectangle(&rc,
0);
}
WinDrawChars("數", 2, 88, 134);
if
(pPrefs->RecogRange == HW_NUMERIC) {
RctSetRectangle(&rc, 87, 93, 12, 12);
WinInvertRectangle(&rc, 0);
}
WinDrawChars("符",
2, 100, 134);
if (pPrefs->RecogRange
== HW_SYMBOL) {
RctSetRectangle(&rc,
99, 133, 12, 12);
WinInvertRectangle(&rc,
0);
}
WinDrawChars("全", 2, 112, 134);
if
(pPrefs->bHalfToFull) {
RctSetRectangle(&rc,
111, 133, 12, 12);
WinInvertRectangle(&rc,
0);
}
WinDrawChars("窗", 2, 124, 134);
WinDrawChars("鼠", 2, 136, 134);
if
(pPrefs->bMouse) {
RctSetRectangle(&rc,
135, 133, 12, 12);
WinInvertRectangle(&rc,
0);
}
WinDrawChars("^", 1, 148, 134);
WinDrawChars("←",
2, 132, 121);
WinDrawChars("→", 2,
145, 121);
d5 = pHWPens->CandiDispOfs
* 2;
ptr = pHWPens->Cands[x];
// 顯示5個候選字
if (ptr[d5] || ptr[d5 + 1]) {
for (ofs = d5; ofs < d5 + 10; ofs += 2) {
if (ptr[ofs]) {
WinDrawChars(&ptr[ofs],
2, x, 134);
x += 12;
}
else {
if (ptr[d5 + 1] <= ' ')
break;
WinDrawChars(&ptr[d5 + 1],
1, x, 134);
x += 12;
}
}
}
// 顯示10個聯想字
if (pHWPens->AssocNum)
{
x = 3;
for (ofs = pHWPens->AssocDispOfs;
ofs < pHWPens->AssocOfs + 20; ofs += 2) {
if (ofs - pHWPens->AssocOfs > pHWPens->AssocNum)
break;
WinDrawChars(&pHWPens->AssocBuffer[ofs], 2, x, 121);
x += 13;
}
}
}
else if (pPrefs->MenuMode
== FOLDED)
WinDrawChars("V", 1, 141,
141);
}
void RedrawMenu(HW_PREF* pPrefs, char* pHWPens)
{
WinHandle winHandle = WinSetDrawWindow(WinGetDisplayWindow());
ClearMenu(pPrefs->MenuMode);
DrawMenu(pPrefs,
pHWPens);
WinSetDrawWindow(winHandle);
}
FieldPtr GetCurField(FormPtr frm)
{
// Too bad to
access directly FormType data member. Palm strongly disencourage
//
such behaviors
if (frm->focus != noFocus &&
frm->objects[frm->focus].object.field !=
frmFieldObj&&
frm->objects[frm->focus].object.field
!= frmTableObj)
{
return
TblGetCurrentField(frm->objects[frm->focus].object.field);
}
return NULL;
}
void EnqueueChChar(FieldPtr
fp, UInt16* pCand, Boolean bBackspace)
{
MemHandle
memHandle;
HW_PREF* pPrefs;
UInt16 InsPos;
FtrGet('HW99', 10, (UInt32 *)&memHandle);
pPrefs = (HW_PREF*)MemHandleLock(memHandle); // a2
InsPos = FldGetInsPtPosition(fp); // d3
if (bBackspace)
FldDelete(fp,
InsPos - 2, InsPos);
FldInsert(fp, pCand, 2);
if (pPrefs->bReged != 0xff) {
pPrefs->bReged++;
pPrefs->bReged
%= 10;
if (pPrefs->bReged == 0)
ShowNag();
}
MemHandleUnlock(memHandle);
InsPos = FldGetInsPtPosition(fp);
FldSetSelection(fp,
InsPos, InsPos);
}
void GetAssoc(HW_PENS* pHWPens, UInt16 Cand)
{
pHWPens->AssocNum = 0;
pHWPens->AssocOfs
= 0;
if (GetAssocFromAssocBuf(pHWPens->AssocBuffer, Cand)
> 0) {
pHWPens->AssocNum = *pHWPens->AssocBuffer;
pHWPens->AssocBuffer++;
if (pHWPens->AssocNum == -1) {
pHWPens->AssocNum += *pHWPens->AssocBuffer;
pHWPens->AssocBuffer++;
}
pHWPens->AssocNum *= 2;
pHWPens->AssocOfs
= 0;
}
}
void EnqueueInputKeys(FormType*
frm, HW_PENS* pHWPens, UInt16 index, BOOL bAssoc)
{
FieldPtr
fp = GetCurField(frm); // a3
WChar ascii;
UInt16 InsPos;
if (fp != NULL) {
if ((UInt8)pHWPens->Cands[index][0] == 0)
{
ascii = *((UInt8*)&pHWPens->Cands[index][0]
+ 1);
if (ascii
> ' ') {
EvtEnqueueKey(ascii, 0, 0);
if (++pHWPeg-{>index == 20)
pHWPens->index
= 0;
pHWPens->CandiDispOfs
= 0;
pHWPens->InsPtPos
= FldGetInsPtPosition(fp) + 1;
}
else
if (ascii == ' ')
EvtEnqueueKey(32, 0, 0);
else if (ascii == 8)
EvtEnqueueKey(backspaceChr, 0, 0);
else if (ascii == '\n')
EvtEnqueueKey('\r',
0, 0), EvtEnqueueKey('\n', 0, 0);
}
else {
EnqueueChChar(fp, pHWPens->Cands[index], false);
if (++pHWPeg-{>index
== 20)
pHWPens->index
= 0;
pHWPens->CandiDispOfs
= 0;
pHWPens->InsPtPos
= FldGetInsPtPosition(fp);
if
(bAssoc)
GetAssoc(pHWPens, pHWPens->Cands[index][0]);
}
}
}
void AdjustCandIndex(FormPtr
frm, HW_PENS* pHWPens)
{
UInt16 InsPos = FldGetInsPtPosition(GetCurField(frm));
// d3
Int16 InsOfs = InsPos - pHWPens->InsPtPos;
Int16 index;
pHWPens->InsPtPos
= InsPos;
if (InsOfs > 40 || InsOfs < -40)
return;
if (pHWPens->index
== 0)
index = 19;
else
index = pHWPens->index - 1; // d3 =
index
if (InsOfs < 0) {
do {
if
(pHWPens->Cands[index][0] == 0) {
if (pHWPens->Cands[index][1] == 0) {
if
(index == 19)
pHWPens->index = 0;
else
pHWPens->index = index + 1;
return;
}
else
{
InsOfs++;
if (index == 0)
index
= 19;
else
index--;
}
}
else {
InsOfs += 2;
if (index == 0)
index = 19;
else
index--;
}
} while (InsOfs < 0); // 5F4C here
} //
goto 5fa2
else if (InsOfs > 0) {
do {
if
(pHWPens->Cands[index][0] == 0) {
if (pHWPens->Cands[index][1] == 0) {
if
(index == 19)
pHWPens->index = 0;
else
pHWPens->index = index + 1;
return;
}
else
{
InsOfs--;
if (index == 19)
index
= 0;
else
index++;
}
}
else {
InsOfs -= 2;
if (index == 19)
index = 0;
else
index++;
}
} while (InsOfs > 0);
}
if (index == 19)
pHWPens->index
= 0;
else
pHWPens->index
= index + 1;
}
BOOL OnPenDown(EventPtr eventP, HW_PREF* pPrefs,
HW_PENS* pHWPens, HW_TRACE* pHWTrace)
{
Coord x,
y;
FormPtr frm;
Int8 MenuMode;
UInt16 screenX, screenY;
// d3
= eventP(a6)
// a2 = pPrefs
//
a3 = pHWPens
// a4 = pHWTrace
x
= eventP->screenX;
y = eventP->screenY;
WinWindowToDisplayPt(&x, &y);
if (x
> 160 || y > 160)
return 0;
MenuMode = pPrefs->MenuMode;
switch
(MenuMode)
{
case FOLDED:
if (screenX > 141 && screenX <
151 && screenY > 141 && screenY < 151)
break;
case LOWER:
if (screenY > 120 && screenY <
145)
break;
case UPPER:
if (screenY
> 80 && screenY < 105)
break;
default:
if (pPrefs->bMouse)
return false;
pHWTrace->b2006
= 0;
pHWTrace->points[pHWTrace->PtCount].x
= screenX;
pHWTrace->points[pHWTrace->PtCount].y
= screenY;
if (pHWTrace->PtCount ==
0)
pHWTrace->b2007
= 1;
pHWTrace->PtCount++;
pHWTrace->dw2002 = 0;
return true;
}
//
如果已有筆跡取樣點, 將新點加入筆跡陣列.
if (pHWTrace->PtCount) {
pHWTrace->points[pHWTrace->PtCount].x = screenX;
pHWTrace->points[pHWTrace->PtCount].y
= screenY;
pHWTrace->PtCount++;
return true;
}
pHWTrace->b2006 = true;
if (MenuMode == LOWER && x > 3 && x < 63 &&
y > 134 && y < 146 ||
MenuMode
== UPPER && x > 3 && x < 63 && y > 94 && y
< 106)
{
UInt16
ofs = (pHWPens->CandiDispOfs + (x - 1)) / 12;
char*
p = (char*)&pHWPens->Cands[pHWPens->index - 1][ofs];
if (*p == 0) {
if (*(p + 1) == 0)
return true;
EvtEnqueueKey(backspaceChr,
0, 0);
EvtEnqueueKey(*(p
+ 1), 0, 0);
}
else {
EnqueueChChar(GetCurField(FrmGetActiveForm()),
p, true);
GetAssoc(pHWPens,
*(UInt16*)p);
}
pPrefs->bRedrawMenu = true;
return
true;
}
if (MenuMode
== LOWER && x > 3 && x < 133 && y > 121 &&
y < 133 ||
MenuMode == UPPER &&
x > 3 && x < 133 && y > 81 && y < 93) // else goto
6b4a
{
UInt16
pos = (x - 1) / 13 * 2;
if (pHWPens->AssocOfs
+ pos < pHWPens->AssocNum) {
EnqueueChChar(GetCurField(FrmGetActiveForm()), pHWPens->AssocBuffer[pos],
false);
pHWPens->AssocNum
= 0;
pHWPens->AssocOfs
= 0;
}
return true;
}
if
(MenuMode == LOWER && x > 63 && x < 75 && y > 134 &&
y < 146 ||
MenuMode == UPPER &&
x > 63 && x < 75 && y > 94 && y < 106) // else goto
6be0
{
UInt16
ofs = pHWPens->CandiDispOfs + 5;
if
(ofs == 10) {
pHWPens->CandiDispOfs
= 0;
pPrefs->bRedrawMenu
= true;
}
else if (pHWPens->Cands[pHWPens->index - 1][ofs] != 0) {
pHWPens->CandiDispOfs + 5;
pPrefs->bRedrawMenu =
true;
}
return true;
}
if
(MenuMode == LOWER && x > 132 && x < 144 && y > 121
&& y < 133 ||
MenuMode ==
UPPER && x > 132 && x < 144 && y > 81 && y
< 93) // else goto 6c4a
{
if (pHWPens->AssocOfs) {
pHWPens->AssocOfs -= 20;
pPrefs->bRedrawMenu = true;
}
return true;
}
if (MenuMode == LOWER && x > 145 &&
x < 157 && y > 121 && y < 133 ||
MenuMode == UPPER && x > 145 && x < 157 &&
y > 81 && y < 93) // else goto 6cba
{
if (pHWPens->AssocOfs + 20 < pHWPens->AssocNum)
{
pHWPens->AssocOfs
+= 20;
pPrefs->bRedrawMenu
= true;
}
re
相關文章
- Win8系統下如何開啟手寫輸入公式功能2016-12-21公式
- 筆記本win8.1系統開啟手寫輸入公式的技巧2016-12-22筆記公式
- Palm打造手機專用Linux作業系統(轉)2007-08-12Linux作業系統
- 輸入輸出系統2020-12-22
- win10手寫輸入怎麼開啟_win10開啟手寫輸入的步驟2020-07-13Win10
- Aurora Engine 遊戲引擎入門 13(新增平臺的輸入)2020-12-22遊戲引擎
- win10如何設定手寫_w10如何手寫輸入2020-08-10Win10
- 直播電商平臺開發,輸入框的防抖2023-01-03
- Unreal 輸入系統 解析2022-05-03Unreal
- Windows 8下開啟手寫輸入公式功能2016-07-11Windows公式
- 讓手寫輸入在 Web 上持久留存2006-01-05Web
- 自然碼輸入系統2000 for NT/2K破解(PECOMPACT殼),見笑啦:)
(3千字)2000-06-24
- 直播系統原始碼,Vue 禁止輸入框輸入空格2023-11-16原始碼Vue
- 管理系統的登入控制?手寫一個釋出訂閱模型!2018-10-18模型
- 跨境電商綜合平臺系統2021-04-15
- scp 手動輸入密碼後後臺執行的方法2018-10-29密碼
- 開放的彈性的資訊系統平臺 DIY系統2007-01-03
- Android輸入系統(四)輸入事件是如何分發到Window的?2019-01-14Android事件
- Android 輸入系統介紹2023-11-24Android
- 直播平臺製作,html+css復刻登入輸入框2023-04-07HTMLCSS
- win10系統設定手機要求輸入10位手機號碼的解決方法2019-07-03Win10
- 薄平臺:Stripe的金融作業系統2022-06-27作業系統
- 回收系統平臺要具備的功能2023-04-12
- 回收小程式系統平臺的優勢2023-04-11
- Android輸入系統(一)輸入事件傳遞流程和InputManagerService的誕生2018-11-01Android事件
- 資訊系統設計一個平臺--利於實施的平臺2007-08-09
- 大宗商品撮合交易平臺系統2021-04-13
- Java 9 平臺模組系統初探2018-04-12Java
- RK 平臺安裝 ubuntu 系統2024-04-29Ubuntu
- windows8.1系統的輸入切換方法2016-10-18Windows
- Linux 下的檔案管理&管理系統中的輸出輸入2020-10-08Linux
- windows10系統怎麼增加輸入法 windows10系統新增輸入法設定2017-07-25Windows
- Android輸入系統(二)IMS的啟動過程和輸入事件的處理2019-02-14Android事件
- 【Android】Android輸入子系統2014-01-05Android
- Ubuntu系統安裝搜狗輸入法2014-05-01Ubuntu
- 陪診小程式系統平臺的優勢2023-04-11
- Oracle 10g 跨平臺傳輸 相容平臺列表2007-12-21Oracle 10g
- Palm興衰史:從Palm到WebOS(2)2014-01-09Web