uses
Classes, SysUtils,LCLType,StdCtrls,Controls
,uCEFTypes,uCEFInterfaces,uCEFChromium
,Gtk2Globals,gtk2, glib2, gdk2, gtk2proc, gtk2int,pango;
type
PDisplay = Pointer; // X11 的 Display*
KeySym = Cardinal; // 通常 32 位无符号整数
Tmyclass = class
procedure Chromium1PreKeyEvent(Sender: TObject;
const browser: ICefBrowser; const event: PCefKeyEvent;
osEvent: TCefEventHandle; out isKeyboardShortcut: Boolean; out Result: Boolean
);
procedure Button1Click(Sender: TObject);
end;
function gdk_x11_display_get_xdisplay(display: PGdkDisplay): PDisplay; cdecl; external 'libgdk-x11-2.0.so.0';
function XKeycodeToKeysym(d: PDisplay; keycode: Byte; index: Longint): KeySym; cdecl; external 'libX11.so.6';
const
EVENTFLAG_SHIFT_DOWN = $00000002;
EVENTFLAG_CONTROL_DOWN = $00000004;
EVENTFLAG_ALT_DOWN = $00000008;
EVENTFLAG_CAPS_LOCK_ON = $00000020;
EVENTFLAG_SHIFTKEY = 1 shl 1;
EVENTFLAG_CONTROLKEY = 1 shl 2;
EVENTFLAG_ALTKEY = 1 shl 3;
var
InSignalKeyC:gulong;//Linux输入完成信号id
IsEn:Boolean;
Chromium1: TChromium;
Button1: TButton;
CEFShow:TWinControl;
myclass:Tmyclass;
cefkey:TCefKeyEvent;
//切换中英文用
FShiftPressed: Boolean = False;
FControlPressed: Boolean = False;
FAltPressed: Boolean = False;
FShiftPressedAlone: Boolean; // 新增:标记 Shift 是否被单独按下
FInputing:Boolean;//正在输入中文
procedure intInput(InChromium: TChromium;InButton: TButton;InCEFShow:TWinControl);
procedure outInput ;
implementation
//----- TCefKeyEvent转PGdkEventKey---------
function CefKeyEventToGdkEventKey(const CefEv: TCefKeyEvent): PGdkEventKey;
var
KeySym: guint;
Modifiers: guint;
// === 工具函数 ===
function NewGdkEventKey: PGdkEventKey;
begin
New(Result);
FillChar(Result^, SizeOf(TGdkEventKey), 0);
end;
procedure FreeGdkEventKey(var Ev: PGdkEventKey);
begin
if Assigned(Ev) then
begin
Dispose(Ev);
Ev := nil;
end;
end;
function GetCurrentEventTime: guint32;
begin
// GTK 要求时间戳是毫秒(X11 时间戳单位)
Result := guint32(GetTickCount64); // Lazarus 提供,跨平台
end;
function GetX11Display: PDisplay;
begin
Result := gdk_x11_display_get_xdisplay(gdk_display_get_default());
end;
function KeyCodeToKeysym(KeyCode: Integer): guint;
var
Disp: PDisplay;
begin
Result := 0;
Disp := GetX11Display;
if Assigned(Disp) and (KeyCode >= 0) and (KeyCode <= 255) then
begin
Result := XKeycodeToKeysym(Disp, Byte(KeyCode), 0);
end;
end;
begin
Result := NewGdkEventKey;
if not Assigned(Result) then Exit;
// 1. 事件类型
case CefEv.kind of
KEYEVENT_KEYDOWN, KEYEVENT_RAWKEYDOWN:
Result^._type := GDK_KEY_PRESS;
KEYEVENT_KEYUP:
Result^._type := GDK_KEY_RELEASE;
else
FreeGdkEventKey(Result);
Exit;
end;
// 2. 时间戳
Result^.time := GetCurrentEventTime;
// 3. keycode 和 keyval(使用 X11)
Result^.hardware_keycode := guint16(CefEv.native_key_code);
KeySym :=KeyCodeToKeysym(CefEv.native_key_code);
Result^.keyval := KeySym;
// 4. 修饰键(Modifiers)
Modifiers := 0;
if (CefEv.modifiers and EVENTFLAG_SHIFT_DOWN) <> 0 then
Modifiers := Modifiers or GDK_SHIFT_MASK;
if (CefEv.modifiers and EVENTFLAG_CONTROL_DOWN) <> 0 then
Modifiers := Modifiers or GDK_CONTROL_MASK;
if (CefEv.modifiers and EVENTFLAG_ALT_DOWN) <> 0 then
Modifiers := Modifiers or GDK_MOD1_MASK; // Alt = MOD1
if (CefEv.modifiers and EVENTFLAG_CAPS_LOCK_ON) <> 0 then
Modifiers := Modifiers or GDK_LOCK_MASK;
Result^.state := Modifiers;
// 5. 其他字段(可选)
Result^.length := 0;
Result^._string := nil;
Result^.window := nil;
Result^.send_event := 0;
Result^.group := 0;
end;
//-------- TCefKeyEvent转PGdkEventKey----------------
//----输入法输入完成事件--
procedure OnInputMethodCommitString({%H-}AContext: PGtkIMContext; AText: PChar; Data: gPointer); cdecl;
var
temp:String;
IMStr:widestring;
JS: UTF8String;
begin
temp := {%H-}UTF8Decode(AText); //输入的字符
temp:=StringReplace(temp, '\', '\\', [rfReplaceAll]);
temp:=StringReplace(temp, '"', '\"', [rfReplaceAll]);
IMStr := {%H-}temp;
if IMStr <> '' then
begin
JS := Format(
'var el=document.activeElement;' +
'if(el&&(el.tagName==="INPUT"||el.tagName==="TEXTAREA"||el.isContentEditable)){' +
' if(el.setSelectionRange){' +
' var start=el.selectionStart, end=el.selectionEnd, val=el.value;' +
' el.value=val.substring(0,start)+%s+val.substring(end);' +
' el.setSelectionRange(start+%d, start+%d);' +
' } else if(el.isContentEditable){' +
' document.execCommand("insertText",false,%s);' +
' }' +
'}',
[QuotedStr(IMStr), Length(IMStr), Length(IMStr), QuotedStr(IMStr)]
);
end;
im_context_use := False;
//js方式插入文本
Chromium1.ExecuteJavaScript(
JS{%H-}, '', Chromium1.Browser.FocusedFrame, 0);
end;
//判断是否还在输入中
function HasPreeditText: Boolean;
var
preeditStr: Pgchar; // 实际字符串指针
attrs: PPangoAttrList; // 属性列表指针(我们不用)
cursorPos: gint; // 光标位置(我们不用)
begin
// 初始化输出参数
preeditStr := nil;
attrs := nil;
cursorPos := 0;
// 调用 GTK 函数:注意 str 参数是 @preeditStr(取地址)
gtk_im_context_get_preedit_string(
im_context,
@preeditStr, // 传地址(PPgchar = ^Pgchar)
attrs, // 可传 nil,但 safest 是传指针
@cursorPos
);
try
// 检查字符串是否非空
Result := (preeditStr <> nil) and (preeditStr^ <> #0);
finally
// 释放内存
g_free(preeditStr);
if attrs <> nil then
pango_attr_list_unref(attrs);
end;
end;
{Tmyclass}
//CEF输入事件
procedure Tmyclass.Chromium1PreKeyEvent(Sender: TObject;
const browser: ICefBrowser; const event: PCefKeyEvent;
osEvent: TCefEventHandle; out isKeyboardShortcut: Boolean; out Result: Boolean
);
var
gdkEvent: PGdkEventKey;
keyCode: Integer;
isModifierKey: Boolean;
pr: TGdkRectangle;
MouseP:TPoint;
ClientP: TPoint;
begin
Result := False;//默认交给cef处理
keyCode := event^.windows_key_code;
//按Shift切换中英文
//方法一
isKeyboardShortcut := False;
if not Assigned(event) then
Exit;
// 判断是否为修饰键(Shift/Ctrl/Alt)
isModifierKey := (keyCode = VK_SHIFT) or (keyCode = VK_CONTROL) or (keyCode = VK_MENU);
// 更新修饰键状态(仅用于状态跟踪)
case keyCode of
VK_SHIFT: FShiftPressed := (event^.kind = KEYEVENT_RAWKEYDOWN);
VK_CONTROL: FControlPressed := (event^.kind = KEYEVENT_RAWKEYDOWN);
VK_MENU: FAltPressed := (event^.kind = KEYEVENT_RAWKEYDOWN);
end;
// 如果是 Shift 按下
if (keyCode = VK_SHIFT) and (event^.kind = KEYEVENT_RAWKEYDOWN) then
begin
// 假设此时是“单独按下 Shift”(后续可能被推翻)
FShiftPressedAlone := True;
end
// 如果在 Shift 按下期间,按下了其他非修饰键,则取消“单独 Shift”状态
else if (event^.kind = KEYEVENT_RAWKEYDOWN) and (not isModifierKey) then
begin
if FShiftPressed then
FShiftPressedAlone := False;
end
// 如果是 Shift 释放
else if (keyCode = VK_SHIFT) and (event^.kind = KEYEVENT_KEYUP) then
begin
if FShiftPressedAlone then
begin
// 确保没有 Ctrl/Alt 被按下(虽然理论上已由 FShiftPressedAlone 保证)
if (not FControlPressed) and (not FAltPressed) then
begin
if not HasPreeditText then
begin
Button1Click(Button1); // 用按钮切换中英文
Result := True; // 阻止事件传递(可选)
end;
end;
end;
// 重置标志
FShiftPressedAlone := False;
Exit;
end;
//---开始输入---
if not IsEn then//不是英文将信息发送到输入法,是则直接交给cef
begin
//设置输入框弹出位置
mouseP:=Mouse.CursorPos;
ClientP := CEFShow.ScreenToClient(MouseP);
pr.height:=0;
pr.width:=0;
pr.x:=ClientP.X;
pr.y:=ClientP.y;
gtk_im_context_set_cursor_location(IM_Context, @pr);//设置输入框位置--重点
//中文输入 发送给输入法
gdkEvent := CefKeyEventToGdkEventKey(Event^);
// 判断是否为需要特殊处理的键
if (keyCode = VK_BACK) or (keyCode = VK_DELETE) or
(keyCode = VK_RETURN) or (keyCode = VK_LEFT) or
(keyCode = VK_RIGHT) or (keyCode = VK_UP) or (keyCode = VK_DOWN) then
begin
if HasPreeditText then
begin
// 有选词框 → 交给 IME 处理
Result := True;
gtk_im_context_filter_keypress(im_context, gdkEvent);
end;
end
else
begin
//gtk_im_context_filter_keypress返回真则交给cef;返回假,阻止事件继续传播,Chromium 不会处理此键事件
Result := not (gtk_im_context_filter_keypress(im_context, gdkEvent));
if event^.character='.' then Result:=True;//中文输入时输入.会出现返回.。的问题
if event^.character='''' then Result:=True;//中文输入时输入.会出现返回.。的问题
end;
Dispose(gdkEvent);
end;
end;
procedure Tmyclass.Button1Click(Sender: TObject);
begin
if IsEn then
begin
IsEn:=False;
Button1.Caption:='中文';
end
else
begin
IsEn:=True;
Button1.Caption:='英文';
end;
end;
procedure intInput(InChromium: TChromium;InButton: TButton;InCEFShow:TWinControl);
begin
Chromium1:=InChromium;
Button1:=InButton;
CEFShow:=InCEFShow;
Chromium1.OnPreKeyEvent:=@myclass. Chromium1PreKeyEvent;
Button1.OnClick:=@myclass.Button1Click;
gtk_im_context_focus_in(IM_Context);
InSignalKeyC:= g_signal_connect(IM_Context, 'commit', TGTKSignalFunc(@OnInputMethodCommitString),Nil);
IsEn:=False;
FInputing:=False;
Button1.ShowHint:=True;
Button1.Hint:='保持启动中文输入法'+#10#13+'并切换到中文输入状态'+#10#13+'用Shift切换中英文';
Button1.Caption:='中文';
end;
procedure outInput ;
begin
g_signal_handler_disconnect(im_context,InSignalKeyC);
end;
使用方法。
在
procedure TForm1.FormCreate(Sender: TObject);
begin
//加这行
IputCN.intInput(Chromium1,Button1,CEFLinkedWindowParent1);
end;
浙公网安备 33010602011771号