diff --git a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/SimpleOSRBrowser.lpi b/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/SimpleOSRBrowser.lpi
index 8aec6845..b0394b26 100644
--- a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/SimpleOSRBrowser.lpi
+++ b/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/SimpleOSRBrowser.lpi
@@ -37,7 +37,7 @@
-
+
@@ -55,11 +55,6 @@
-
-
-
-
-
diff --git a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/SimpleOSRBrowser.lpr b/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/SimpleOSRBrowser.lpr
index 8324b612..46cbfafe 100644
--- a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/SimpleOSRBrowser.lpr
+++ b/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/SimpleOSRBrowser.lpr
@@ -9,7 +9,7 @@ uses
// "Interfaces" is a custom unit used to initialize the LCL WidgetSet
// We keep the same name to avoid a Lazarus warning.
Interfaces, // this includes the LCL widgetset
- Forms, lazmouseandkeyinput, uSimpleOSRBrowser, uCEFLinuxOSRIMEHandler,
+ Forms, lazmouseandkeyinput, uSimpleOSRBrowser,
{ you can add units after this }
uCEFApplication;
diff --git a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/usimpleosrbrowser.lfm b/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/usimpleosrbrowser.lfm
index cc161bf1..c2030325 100644
--- a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/usimpleosrbrowser.lfm
+++ b/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/usimpleosrbrowser.lfm
@@ -1,11 +1,13 @@
object Form1: TForm1
- Left = 1658
+ Left = 561
Height = 630
- Top = 423
+ Top = 252
Width = 1000
Caption = ' Initializing browser. Please wait...'
ClientHeight = 630
ClientWidth = 1000
+ Position = poScreenCenter
+ LCLVersion = '4.2.0.0'
OnActivate = FormActivate
OnCloseQuery = FormCloseQuery
OnCreate = FormCreate
@@ -13,7 +15,6 @@ object Form1: TForm1
OnHide = FormHide
OnShow = FormShow
OnWindowStateChange = FormWindowStateChange
- Position = poScreenCenter
object AddressPnl: TPanel
Left = 0
Height = 30
@@ -66,7 +67,7 @@ object Form1: TForm1
Align = alClient
AutoSelect = False
ItemHeight = 0
- ItemIndex = 7
+ ItemIndex = 0
Items.Strings = (
'https://www.google.com'
'https://www.bing.com'
@@ -125,7 +126,7 @@ object Form1: TForm1
'chrome://process-internals'
)
TabOrder = 0
- Text = 'https://www.baidu.com'
+ Text = 'https://www.google.com'
OnEnter = AddressCbEnter
end
end
@@ -138,6 +139,8 @@ object Form1: TForm1
OnIMEPreEditEnd = Panel1IMEPreEditEnd
OnIMEPreEditChanged = Panel1IMEPreEditChanged
OnIMECommit = Panel1IMECommit
+ OnGdkKeyPress = Panel1GdkKeyPress
+ OnGdkKeyRelease = Panel1GdkKeyRelease
Align = alClient
Caption = 'Panel1'
Color = clWhite
diff --git a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/usimpleosrbrowser.pas b/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/usimpleosrbrowser.pas
index 0c14341e..456b3591 100644
--- a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/usimpleosrbrowser.pas
+++ b/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/usimpleosrbrowser.pas
@@ -9,10 +9,9 @@ interface
uses
Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
- LCLType, ComCtrls, Types, SyncObjs, LMessages,
- gtk2, glib2, gdk2, gtk2proc, gtk2int,
+ LCLType, ComCtrls, Types, SyncObjs, LMessages, gdk2, gtk2proc, gtk2int,
uCEFChromium, uCEFTypes, uCEFInterfaces, uCEFConstants,
- {$IFDEF CEF_USE_IME}uCEFLinuxOSRIMEHandler,{$ENDIF} uCEFBufferPanel, uCEFChromiumEvents;
+ {$IFDEF CEF_USE_IME}uCEFLinuxOSRIMEHandler,{$ENDIF} uCEFBufferPanel;
type
TDevToolsStatus = (dtsIdle, dtsGettingNodeID, dtsGettingNodeInfo, dtsGettingNodeRect);
@@ -31,6 +30,8 @@ type
procedure Panel1Enter(Sender: TObject);
procedure Panel1Exit(Sender: TObject);
+ procedure Panel1GdkKeyPress(Sender: TObject; aEvent: PGdkEventKey; var aHandled: boolean);
+ procedure Panel1GdkKeyRelease(Sender: TObject; aEvent: PGdkEventKey; var aHandled: boolean);
procedure Panel1IMECommit(Sender: TObject; const aCommitText: ustring);
procedure Panel1IMEPreEditChanged(Sender: TObject; aFlag: cardinal; const aPreEditText: ustring);
procedure Panel1IMEPreEditEnd(Sender: TObject);
@@ -47,6 +48,7 @@ type
procedure Chromium1BeforeClose(Sender: TObject; const browser: ICefBrowser);
procedure Chromium1BeforePopup(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; popup_id: Integer; const targetUrl, targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean; const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo; var client: ICefClient; var settings: TCefBrowserSettings; var extra_info: ICefDictionaryValue; var noJavascriptAccess: Boolean; var Result: Boolean);
procedure Chromium1CursorChange(Sender: TObject; const browser: ICefBrowser; cursor_: TCefCursorHandle; cursorType: TCefCursorType; const customCursorInfo: PCefCursorInfo; var aResult : boolean);
+ procedure Chromium1DevToolsMethodResult(Sender: TObject; const browser: ICefBrowser; message_id: integer; success: boolean; const result: ICefValue);
procedure Chromium1GetScreenInfo(Sender: TObject; const browser: ICefBrowser; var screenInfo: TCefScreenInfo; out Result: Boolean);
procedure Chromium1GetScreenPoint(Sender: TObject; const browser: ICefBrowser; viewX, viewY: Integer; var screenX, screenY: Integer; out Result: Boolean);
procedure Chromium1GetViewRect(Sender: TObject; const browser: ICefBrowser; var rect: TCefRect);
@@ -54,10 +56,9 @@ type
procedure Chromium1Paint(Sender: TObject; const browser: ICefBrowser; type_: TCefPaintElementType; dirtyRectsCount: NativeUInt; const dirtyRects: PCefRectArray; const buffer: Pointer; aWidth, aHeight: Integer);
procedure Chromium1PopupShow(Sender: TObject; const browser: ICefBrowser; aShow: Boolean);
procedure Chromium1PopupSize(Sender: TObject; const browser: ICefBrowser; const rect: PCefRect);
- procedure Chromium1Tooltip(Sender: TObject; const browser: ICefBrowser; var aText: ustring; out Result: Boolean);
procedure Chromium1ProcessMessageReceived(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; sourceProcess: TCefProcessId; const message: ICefProcessMessage; out Result: Boolean);
- procedure Chromium1DevToolsMethodResult(Sender: TObject; const browser: ICefBrowser; message_id: integer; success: boolean; const result: ICefValue);
procedure Chromium1SetFocus(Sender: TObject; const browser: ICefBrowser; source: TCefFocusSource; out Result: Boolean);
+ procedure Chromium1Tooltip(Sender: TObject; const browser: ICefBrowser; var aText: ustring; out Result: Boolean);
procedure FormActivate(Sender: TObject);
procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
@@ -70,13 +71,11 @@ type
procedure Application_OnActivate(Sender: TObject);
procedure Application_OnDeactivate(Sender: TObject);
+ procedure AddressCbEnter(Sender: TObject);
procedure GoBtnClick(Sender: TObject);
procedure GoBtnEnter(Sender: TObject);
procedure SnapshotBtnClick(Sender: TObject);
-
- procedure Timer1Timer(Sender: TObject);
- procedure AddressCbEnter(Sender: TObject);
- private
+ procedure Timer1Timer(Sender: TObject);
protected
FPopUpRect : TRect;
@@ -95,7 +94,6 @@ type
FDevToolsStatus : TDevToolsStatus;
FCheckEditable : boolean;
FWasEditing : boolean;
- FBrowserIsFocused : boolean;
{$IFDEF CEF_USE_IME}
FIMEHandler : TCEFLinuxOSRIMEHandler;
{$ENDIF}
@@ -140,7 +138,7 @@ type
property IsEditing : boolean read GetIsEditing write SetIsEditing;
public
- function HandleKeyEvent(Event: PGdkEventKey) : boolean;
+
end;
var
@@ -170,11 +168,6 @@ implementation
// be stored in a TCEFBitmapBitBuffer class instead of a simple TBitmap to avoid
// issues with GTK.
-// Chromium needs the key press data available in the GDK signals
-// "key-press-event" and "key-release-event" but Lazarus doesn't expose that
-// information so we have to call g_signal_connect to receive that information
-// in the GTKKeyPress function.
-
// Chromium renders the web contents asynchronously. It uses multiple processes
// and threads which makes it complicated to keep the correct browser size.
@@ -207,7 +200,7 @@ implementation
// Chromium was rendering the page.
// The TChromium.OnPaint event in the demo also calls
-// TBufferPanel.UpdateBufferDimensions and TBufferPanel.BufferIsResized to check
+// TBufferPanel.UpdateOrigBufferDimensions and TBufferPanel.BufferIsResized to check
// the width and height of the buffer parameter, and the internal buffer size in
// the TBufferPanel component.
@@ -241,6 +234,8 @@ const
NONEDITABLE_MSGNAME = 'noneditable';
{$ENDIF}
+{GlobalCEFApp functions}
+{%Region}
{$IFDEF CEF_USE_IME}
procedure GlobalCEFApp_OnFocusedNodeChanged(const browser : ICefBrowser;
const frame : ICefFrame;
@@ -280,60 +275,827 @@ begin
{$IFDEF CEF_USE_IME}
GlobalCEFApp.OnFocusedNodeChanged := @GlobalCEFApp_OnFocusedNodeChanged;
{$ENDIF}
-end;
+end;
+{%Endregion}
-function GTKKeyPress(Widget: PGtkWidget; Event: PGdkEventKey; Data: gPointer) : GBoolean; cdecl;
+{TBufferPanel events}
+{%Region}
+procedure TForm1.Panel1Enter(Sender: TObject);
begin
- Result := Form1.HandleKeyEvent(Event);
+ IsEditing := FWasEditing;
+ FCheckEditable := True;
+ Chromium1.SetFocus(True);
end;
-procedure ConnectKeyPressReleaseEvents(const aWidget : PGtkWidget);
+procedure TForm1.Panel1Exit(Sender: TObject);
begin
- g_signal_connect(aWidget, 'key-press-event', G_CALLBACK(@GTKKeyPress), nil);
- g_signal_connect(aWidget, 'key-release-event', G_CALLBACK(@GTKKeyPress), nil);
+ FWasEditing := IsEditing;
+ Chromium1.SetFocus(False);
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler.Blur;
+ {$ENDIF}
end;
-{ TForm1 }
-
-function TForm1.HandleKeyEvent(Event: PGdkEventKey) : boolean;
+procedure TForm1.Panel1GdkKeyPress(Sender: TObject; aEvent: PGdkEventKey;
+ var aHandled: boolean);
var
TempCefEvent : TCefKeyEvent;
begin
- Result := True;
+ aHandled := True;
- if not(HandleIMEKeyEvent(Event)) then
+ if not(HandleIMEKeyEvent(aEvent)) then
begin
- GdkEventKeyToCEFKeyEvent(Event, TempCefEvent);
+ GdkEventKeyToCEFKeyEvent(aEvent, TempCefEvent);
- if (Event^._type = GDK_KEY_PRESS) then
- begin
- TempCefEvent.kind := KEYEVENT_RAWKEYDOWN;
- Chromium1.SendKeyEvent(@TempCefEvent);
- TempCefEvent.kind := KEYEVENT_CHAR;
- Chromium1.SendKeyEvent(@TempCefEvent);
- end
- else
- begin
- TempCefEvent.kind := KEYEVENT_KEYUP;
- Chromium1.SendKeyEvent(@TempCefEvent);
- end;
+ TempCefEvent.kind := KEYEVENT_RAWKEYDOWN;
+ Chromium1.SendKeyEvent(@TempCefEvent);
+ TempCefEvent.kind := KEYEVENT_CHAR;
+ Chromium1.SendKeyEvent(@TempCefEvent);
end;
end;
-function TForm1.HandleIMEKeyEvent(Event: PGdkEventKey) : boolean;
+procedure TForm1.Panel1GdkKeyRelease(Sender: TObject; aEvent: PGdkEventKey;
+ var aHandled: boolean);
+var
+ TempCefEvent : TCefKeyEvent;
+begin
+ aHandled := True;
+
+ if not(HandleIMEKeyEvent(aEvent)) then
+ begin
+ GdkEventKeyToCEFKeyEvent(aEvent, TempCefEvent);
+
+ TempCefEvent.kind := KEYEVENT_KEYUP;
+ Chromium1.SendKeyEvent(@TempCefEvent);
+ end;
+end;
+
+procedure TForm1.Panel1IMECommit(Sender: TObject; const aCommitText: ustring);
+{$IFDEF CEF_USE_IME}
+const
+ UINT32_MAX = high(cardinal);
+var
+ replacement_range : TCefRange;
+{$ENDIF}
+begin
+ {$IFDEF CEF_USE_IME}
+ replacement_range.from := UINT32_MAX;
+ replacement_range.to_ := UINT32_MAX;
+
+ Chromium1.IMECommitText(aCommitText, @replacement_range, 0);
+ {$ENDIF}
+end;
+
+procedure TForm1.Panel1IMEPreEditChanged(Sender: TObject; aFlag: cardinal;
+ const aPreEditText: ustring);
+begin
+ {$IFDEF CEF_USE_IME}
+ SetIMECursorLocation;
+ {$ENDIF}
+end;
+
+procedure TForm1.Panel1IMEPreEditEnd(Sender: TObject);
+begin
+ {$IFDEF CEF_USE_IME}
+ Chromium1.IMECancelComposition;
+ {$ENDIF}
+end;
+
+procedure TForm1.Panel1IMEPreEditStart(Sender: TObject);
+begin
+ {$IFDEF CEF_USE_IME}
+ SetIMECursorLocation;
+ {$ENDIF}
+end;
+
+procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
+ Shift: TShiftState; X, Y: Integer);
+var
+ TempEvent : TCefMouseEvent;
+begin
+ Panel1.SetFocus;
+
+ TempEvent.x := X;
+ TempEvent.y := Y;
+ TempEvent.modifiers := getModifiers(Shift);
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseClickEvent(@TempEvent, GetButton(Button), False, 1);
+end;
+
+procedure TForm1.Panel1MouseEnter(Sender: TObject);
+var
+ TempEvent : TCefMouseEvent;
+ TempPoint : TPoint;
+begin
+ TempPoint := Panel1.ScreenToClient(mouse.CursorPos);
+ TempEvent.x := TempPoint.x;
+ TempEvent.y := TempPoint.y;
+ TempEvent.modifiers := EVENTFLAG_NONE;
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseMoveEvent(@TempEvent, False);
+end;
+
+procedure TForm1.Panel1MouseLeave(Sender: TObject);
+var
+ TempEvent : TCefMouseEvent;
+ TempPoint : TPoint;
+begin
+ TempPoint := Panel1.ScreenToClient(mouse.CursorPos);
+ TempEvent.x := TempPoint.x;
+ TempEvent.y := TempPoint.y;
+ TempEvent.modifiers := EVENTFLAG_NONE;
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseMoveEvent(@TempEvent, True);
+end;
+
+procedure TForm1.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,
+ Y: Integer);
+var
+ TempEvent : TCefMouseEvent;
+begin
+ TempEvent.x := x;
+ TempEvent.y := y;
+ TempEvent.modifiers := getModifiers(Shift);
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseMoveEvent(@TempEvent, False);
+end;
+
+procedure TForm1.Panel1MouseUp(Sender: TObject; Button: TMouseButton;
+ Shift: TShiftState; X, Y: Integer);
+var
+ TempEvent : TCefMouseEvent;
+ TempParams : ICefDictionaryValue;
+begin
+ TempEvent.x := X;
+ TempEvent.y := Y;
+ TempEvent.modifiers := getModifiers(Shift);
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseClickEvent(@TempEvent, GetButton(Button), True, 1);
+
+ // GlobalCEFApp.OnFocusedNodeChanged is not triggered the first time the
+ // browser gets the focus. We need to call a series of DevTool methods to
+ // check if the HTML element under the mouse is editable to save its position
+ // and size. This information will be used to show the IME.
+ if FCheckEditable then
+ try
+ FCheckEditable := False;
+ FDevToolsStatus := dtsGettingNodeId;
+
+ TempParams := TCefDictionaryValueRef.New;
+ TempParams.SetInt('x', DeviceToLogical(X, Panel1.ScreenScale));
+ TempParams.SetInt('y', DeviceToLogical(Y, Panel1.ScreenScale));
+
+ Chromium1.ExecuteDevToolsMethod(0, 'DOM.getNodeForLocation', TempParams);
+ finally
+ TempParams := nil;
+ end;
+end;
+
+procedure TForm1.Panel1MouseWheel(Sender: TObject; Shift: TShiftState;
+ WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
+var
+ TempEvent : TCefMouseEvent;
+begin
+ TempEvent.x := MousePos.x;
+ TempEvent.y := MousePos.y;
+ TempEvent.modifiers := getModifiers(Shift);
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseWheelEvent(@TempEvent, 0, WheelDelta);
+end;
+
+procedure TForm1.Panel1Resize(Sender: TObject);
+begin
+ DoResize;
+end;
+{%Endregion}
+
+{TChromium events}
+{%Region}
+procedure TForm1.Chromium1AfterCreated(Sender: TObject; const browser: ICefBrowser);
+begin
+ // Now the browser is fully initialized we can initialize the UI.
+ SendCompMessage(CEF_AFTERCREATED);
+end;
+
+procedure TForm1.Chromium1BeforeClose(Sender: TObject;
+ const browser: ICefBrowser);
+begin
+ FCanClose := True;
+ SendCompMessage(CEF_BEFORECLOSE);
+end;
+
+procedure TForm1.Chromium1BeforePopup(Sender: TObject;
+ const browser: ICefBrowser; const frame: ICefFrame; popup_id: Integer;
+ const targetUrl, targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition;
+ userGesture: Boolean; const popupFeatures: TCefPopupFeatures;
+ var windowInfo: TCefWindowInfo; var client: ICefClient;
+ var settings: TCefBrowserSettings; var extra_info: ICefDictionaryValue;
+ var noJavascriptAccess: Boolean; var Result: Boolean);
+begin
+ // For simplicity, this demo blocks all popup windows and new tabs
+ Result := (targetDisposition in [CEF_WOD_NEW_FOREGROUND_TAB, CEF_WOD_NEW_BACKGROUND_TAB, CEF_WOD_NEW_POPUP, CEF_WOD_NEW_WINDOW]);
+end;
+
+procedure TForm1.Chromium1CursorChange(Sender: TObject;
+ const browser: ICefBrowser; cursor_: TCefCursorHandle;
+ cursorType: TCefCursorType; const customCursorInfo: PCefCursorInfo;
+ var aResult : boolean);
+begin
+ PanelCursor := CefCursorToWindowsCursor(cursorType);
+ aResult := True;
+
+ SendCompMessage(CEF_UPDATE_CURSOR);
+end;
+
+procedure TForm1.Chromium1DevToolsMethodResult(Sender: TObject;
+ const browser: ICefBrowser; message_id: integer; success: boolean;
+ const result: ICefValue);
+begin
+ case FDevToolsStatus of
+ dtsGettingNodeId : HandleGettingNodeIdResult(success, result);
+ dtsGettingNodeInfo : HandleGettingNodeInfoResult(success, result);
+ dtsGettingNodeRect : HandleGettingNodeRectResult(success, result);
+ end;
+end;
+
+procedure TForm1.Chromium1GetScreenInfo(Sender: TObject;
+ const browser: ICefBrowser; var screenInfo: TCefScreenInfo; out
+ Result: Boolean);
+var
+ TempRect : TCEFRect;
+ TempScale : single;
+begin
+ TempScale := Panel1.ScreenScale;
+ TempRect.x := 0;
+ TempRect.y := 0;
+ TempRect.width := DeviceToLogical(Panel1.Width, TempScale);
+ TempRect.height := DeviceToLogical(Panel1.Height, TempScale);
+
+ screenInfo.device_scale_factor := TempScale;
+ screenInfo.depth := 0;
+ screenInfo.depth_per_component := 0;
+ screenInfo.is_monochrome := Ord(False);
+ screenInfo.rect := TempRect;
+ screenInfo.available_rect := TempRect;
+
+ Result := True;
+end;
+
+procedure TForm1.Chromium1GetScreenPoint(Sender: TObject;
+ const browser: ICefBrowser; viewX, viewY: Integer; var screenX,
+ screenY: Integer; out Result: Boolean);
+begin
+ try
+ FBrowserCS.Acquire;
+ screenX := LogicalToDevice(viewX, Panel1.ScreenScale) + FPanelOffset.x;
+ screenY := LogicalToDevice(viewY, Panel1.ScreenScale) + FPanelOffset.y;
+ Result := True;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TForm1.Chromium1GetViewRect(Sender: TObject;
+ const browser: ICefBrowser; var rect: TCefRect);
+var
+ TempScale : single;
+begin
+ TempScale := Panel1.ScreenScale;
+ rect.x := 0;
+ rect.y := 0;
+ rect.width := DeviceToLogical(Panel1.Width, TempScale);
+ rect.height := DeviceToLogical(Panel1.Height, TempScale);
+end;
+
+procedure TForm1.Chromium1OpenUrlFromTab(Sender: TObject;
+ const browser: ICefBrowser; const frame: ICefFrame; const targetUrl: ustring;
+ targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean; out
+ Result: Boolean);
+begin
+ // For simplicity, this demo blocks all popup windows and new tabs
+ Result := (targetDisposition in [CEF_WOD_NEW_FOREGROUND_TAB, CEF_WOD_NEW_BACKGROUND_TAB, CEF_WOD_NEW_POPUP, CEF_WOD_NEW_WINDOW]);
+end;
+
+procedure TForm1.Chromium1Paint(Sender: TObject; const browser: ICefBrowser;
+ type_: TCefPaintElementType; dirtyRectsCount: NativeUInt;
+ const dirtyRects: PCefRectArray; const buffer: Pointer; aWidth, aHeight: Integer
+ );
+var
+ src, dst: PByte;
+ i, j, TempLineSize, TempSrcOffset, TempDstOffset, SrcStride : Integer;
+ n : NativeUInt;
+ TempWidth, TempHeight : integer;
+ TempBufferBits : Pointer;
+ TempForcedResize : boolean;
+ TempBitmap : TCEFBitmapBitBuffer;
+ TempSrcRect : TRect;
+begin
+ try
+ FResizeCS.Acquire;
+ TempForcedResize := False;
+
+ if Panel1.BeginBufferDraw then
+ begin
+ if (type_ = PET_POPUP) then
+ begin
+ Panel1.UpdateOrigPopupBufferDimensions(aWidth, aHeight);
+
+ TempBitmap := Panel1.OrigPopupBuffer;
+ TempWidth := Panel1.OrigPopupBufferWidth;
+ TempHeight := Panel1.OrigPopupBufferHeight;
+ end
+ else
+ begin
+ TempForcedResize := Panel1.UpdateOrigBufferDimensions(aWidth, aHeight) or
+ not(Panel1.BufferIsResized(False));
+
+ TempBitmap := Panel1.OrigBuffer;
+ TempWidth := Panel1.OrigBufferWidth;
+ TempHeight := Panel1.OrigBufferHeight;
+ end;
+
+ SrcStride := aWidth * SizeOf(TRGBQuad);
+ n := 0;
+
+ while (n < dirtyRectsCount) do
+ begin
+ if (dirtyRects^[n].x >= 0) and (dirtyRects^[n].y >= 0) then
+ begin
+ TempLineSize := min(dirtyRects^[n].width, TempWidth - dirtyRects^[n].x) * SizeOf(TRGBQuad);
+
+ if (TempLineSize > 0) then
+ begin
+ TempSrcOffset := ((dirtyRects^[n].y * aWidth) + dirtyRects^[n].x) * SizeOf(TRGBQuad);
+ TempDstOffset := (dirtyRects^[n].x * SizeOf(TRGBQuad));
+
+ src := @PByte(buffer)[TempSrcOffset];
+
+ i := 0;
+ j := min(dirtyRects^[n].height, TempHeight - dirtyRects^[n].y);
+
+ while (i < j) do
+ begin
+ TempBufferBits := TempBitmap.Scanline[dirtyRects^[n].y + i];
+ dst := @PByte(TempBufferBits)[TempDstOffset];
+
+ Move(src^, dst^, TempLineSize);
+
+ Inc(src, SrcStride);
+ inc(i);
+ end;
+ end;
+ end;
+
+ inc(n);
+ end;
+
+ if FShowPopup then
+ begin
+ TempSrcRect := Rect(0, 0,
+ FPopUpRect.Right - FPopUpRect.Left,
+ FPopUpRect.Bottom - FPopUpRect.Top);
+
+ Panel1.DrawOrigPopupBuffer(TempSrcRect, FPopUpRect);
+ end;
+
+ Panel1.EndBufferDraw;
+
+ SendCompMessage(CEF_PENDINGINVALIDATE);
+
+ if (type_ = PET_VIEW) then
+ begin
+ if TempForcedResize or FPendingResize then
+ SendCompMessage(CEF_PENDINGRESIZE);
+
+ FResizing := False;
+ FPendingResize := False;
+ end;
+ end;
+ finally
+ FResizeCS.Release;
+ end;
+end;
+
+procedure TForm1.Chromium1PopupShow(Sender: TObject; const browser: ICefBrowser; aShow: Boolean);
+begin
+ if aShow then
+ FShowPopUp := True
+ else
+ begin
+ FShowPopUp := False;
+ FPopUpRect := rect(0, 0, 0, 0);
+
+ if (Chromium1 <> nil) then Chromium1.Invalidate(PET_VIEW);
+ end;
+end;
+
+procedure TForm1.Chromium1PopupSize(Sender: TObject; const browser: ICefBrowser; const rect: PCefRect);
+begin
+ LogicalToDevice(rect^, Panel1.ScreenScale);
+
+ FPopUpRect.Left := rect^.x;
+ FPopUpRect.Top := rect^.y;
+ FPopUpRect.Right := rect^.x + rect^.width - 1;
+ FPopUpRect.Bottom := rect^.y + rect^.height - 1;
+end;
+
+procedure TForm1.Chromium1ProcessMessageReceived(Sender: TObject;
+ const browser: ICefBrowser; const frame: ICefFrame;
+ sourceProcess: TCefProcessId; const message: ICefProcessMessage; out
+ Result: Boolean);
begin
Result := False;
{$IFDEF CEF_USE_IME}
- if SetIMECursorLocation then
- Result := FIMEHandler.FilterKeyPress(Event);
+ if (message = nil) then exit;
+
+ if (message.Name = EDITABLE_MSGNAME) then
+ UpdateElementBounds(message.ArgumentList)
+ else
+ IsEditing := False;
+
+ Result := True;
{$ENDIF}
end;
+procedure TForm1.Chromium1SetFocus(Sender: TObject; const browser: ICefBrowser;
+ source: TCefFocusSource; out Result: Boolean);
+begin
+ Result := not(Panel1.Focused);
+end;
+
+procedure TForm1.Chromium1Tooltip(Sender: TObject; const browser: ICefBrowser; var aText: ustring; out Result: Boolean);
+begin
+ PanelHint := aText;
+ Result := True;
+
+ SendCompMessage(CEF_UPDATE_HINT);
+end;
+{%Endregion}
+
+{TForm events}
+{%Region}
+procedure TForm1.FormActivate(Sender: TObject);
+begin
+ // This will trigger the AfterCreated event when the browser is fully
+ // initialized and ready to receive commands.
+
+ // GlobalCEFApp.GlobalContextInitialized has to be TRUE before creating any browser
+ // If it's not initialized yet, we use a simple timer to create the browser later.
+
+ // Linux needs a visible form to create a browser so we need to use the
+ // TForm.OnActivate event instead of the TForm.OnShow event
+
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler.CreateContext;
+ {$ENDIF}
+
+ if not(Chromium1.Initialized) then
+ begin
+ // We have to update the DeviceScaleFactor here to get the scale of the
+ // monitor where the main application form is located.
+ GlobalCEFApp.UpdateDeviceScaleFactor;
+ Panel1.UpdateDeviceScaleFactor;
+ Panel1.ConnectSignals;
+ UpdatePanelOffset;
+
+ // opaque white background color
+ Chromium1.Options.BackgroundColor := CefColorSetARGB($FF, $FF, $FF, $FF);
+ Chromium1.DefaultURL := UTF8Decode(AddressCb.Text);
+
+ if not(Chromium1.CreateBrowser) then Timer1.Enabled := True;
+ end;
+end;
+
+procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: boolean);
+begin
+ CanClose := FCanClose;
+
+ if not(FClosing) then
+ begin
+ FClosing := True;
+ Visible := False;
+ Chromium1.CloseBrowser(True);
+ end;
+end;
+
+procedure TForm1.FormCreate(Sender: TObject);
+begin
+ FCheckEditable := True;
+ FWasEditing := False;
+ FDevToolsStatus := dtsIdle;
+ FPopUpRect := rect(0, 0, 0, 0);
+ FShowPopUp := False;
+ FResizing := False;
+ FPendingResize := False;
+ FCanClose := False;
+ FClosing := False;
+ FResizeCS := TCriticalSection.Create;
+ FBrowserCS := TCriticalSection.Create;
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler := TCEFLinuxOSRIMEHandler.Create(Panel1);
+ {$ENDIF}
+ IsEditing := False;
+
+ Panel1.CopyOriginalBuffer := True;
+
+ Application.OnActivate := @Application_OnActivate;
+ Application.OnDeactivate := @Application_OnDeactivate;
+end;
+
+procedure TForm1.FormDestroy(Sender: TObject);
+begin
+ if (FResizeCS <> nil) then FreeAndNil(FResizeCS);
+ if (FBrowserCS <> nil) then FreeAndNil(FBrowserCS);
+ {$IFDEF CEF_USE_IME}
+ if (FIMEHandler <> nil) then FreeAndNil(FIMEHandler);
+ {$ENDIF}
+end;
+
+procedure TForm1.FormHide(Sender: TObject);
+begin
+ Chromium1.SetFocus(False);
+ Chromium1.WasHidden(True);
+end;
+
+procedure TForm1.FormShow(Sender: TObject);
+begin
+ Chromium1.WasHidden(False);
+ Chromium1.SetFocus(Panel1.Focused);
+end;
+
+procedure TForm1.FormWindowStateChange(Sender: TObject);
+begin
+ if (WindowState = wsMinimized) then
+ begin
+ Chromium1.SetFocus(False);
+ Chromium1.WasHidden(True);
+ end
+ else
+ begin
+ Chromium1.WasHidden(False);
+ Chromium1.SetFocus(Panel1.Focused);
+ end;
+end;
+{%Endregion}
+
+{TApplication events}
+{%Region}
+procedure TForm1.Application_OnActivate(Sender: TObject);
+begin
+ IsEditing := FWasEditing;
+ FCheckEditable := True;
+ Chromium1.SetFocus(Panel1.Focused);
+end;
+
+procedure TForm1.Application_OnDeactivate(Sender: TObject);
+begin
+ FWasEditing := IsEditing;
+ Chromium1.SetFocus(False);
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler.Blur;
+ {$ENDIF}
+end;
+{%Endregion}
+
+{Other events}
+{%Region}
+procedure TForm1.AddressCbEnter(Sender: TObject);
+begin
+ Chromium1.SetFocus(False);
+end;
+
+procedure TForm1.GoBtnClick(Sender: TObject);
+begin
+ FResizeCS.Acquire;
+ FResizing := False;
+ FPendingResize := False;
+ FResizeCS.Release;
+
+ Chromium1.LoadURL(UTF8Decode(AddressCb.Text));
+end;
+
+procedure TForm1.GoBtnEnter(Sender: TObject);
+begin
+ Chromium1.SetFocus(False);
+end;
+
+procedure TForm1.SnapshotBtnClick(Sender: TObject);
+begin
+ if SaveDialog1.Execute then
+ Panel1.SaveToFile(SaveDialog1.FileName);
+end;
+
+procedure TForm1.Timer1Timer(Sender: TObject);
+begin
+ Timer1.Enabled := False;
+
+ if not(Chromium1.CreateBrowser) and not(Chromium1.Initialized) then
+ Timer1.Enabled := True;
+end;
+{%Endregion}
+
+{Getters and setters}
+{%Region}
+function TForm1.GetPanelCursor : TCursor;
+begin
+ try
+ FBrowserCS.Acquire;
+ Result := FPanelCursor;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+function TForm1.GetPanelHint : ustring;
+begin
+ try
+ FBrowserCS.Acquire;
+ Result := FPanelHint;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+function TForm1.GetIsEditing : boolean;
+begin
+ try
+ FBrowserCS.Acquire;
+ Result := FIsEditing;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TForm1.SetPanelCursor(aValue : TCursor);
+begin
+ try
+ FBrowserCS.Acquire;
+ FPanelCursor := aValue;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TForm1.SetPanelHint(const aValue : ustring);
+begin
+ try
+ FBrowserCS.Acquire;
+ FPanelHint := aValue;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TForm1.SetIsEditing(aValue : boolean);
+begin
+ try
+ FBrowserCS.Acquire;
+
+ if aValue then
+ FIsEditing := True
+ else
+ begin
+ FIsEditing := False;
+ FElementBounds := rect(0, 0, 0, 0);
+ end;
+
+ SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+{%Endregion}
+
+{Misc functions}
+{%Region}
+procedure TForm1.SendCompMessage(aMsg : cardinal; aData: PtrInt);
+begin
+ case aMsg of
+ CEF_AFTERCREATED : Application.QueueAsyncCall(@BrowserCreatedMsg, aData);
+ CEF_BEFORECLOSE : Application.QueueAsyncCall(@BrowserCloseFormMsg, aData);
+ CEF_PENDINGRESIZE : Application.QueueAsyncCall(@PendingResizeMsg, aData);
+ CEF_PENDINGINVALIDATE : Application.QueueAsyncCall(@PendingInvalidateMsg, aData);
+ CEF_UPDATE_CURSOR : Application.QueueAsyncCall(@PendingCursorUpdateMsg, aData);
+ CEF_UPDATE_HINT : Application.QueueAsyncCall(@PendingHintUpdateMsg, aData);
+ CEF_FOCUSENABLED : Application.QueueAsyncCall(@FocusEnabledMsg, aData);
+ end;
+end;
+
+function TForm1.getModifiers(Shift: TShiftState): TCefEventFlags;
+begin
+ Result := EVENTFLAG_NONE;
+
+ if (ssShift in Shift) then Result := Result or EVENTFLAG_SHIFT_DOWN;
+ if (ssAlt in Shift) then Result := Result or EVENTFLAG_ALT_DOWN;
+ if (ssCtrl in Shift) then Result := Result or EVENTFLAG_CONTROL_DOWN;
+ if (ssLeft in Shift) then Result := Result or EVENTFLAG_LEFT_MOUSE_BUTTON;
+ if (ssRight in Shift) then Result := Result or EVENTFLAG_RIGHT_MOUSE_BUTTON;
+ if (ssMiddle in Shift) then Result := Result or EVENTFLAG_MIDDLE_MOUSE_BUTTON;
+end;
+
+function TForm1.GetButton(Button: TMouseButton): TCefMouseButtonType;
+begin
+ case Button of
+ TMouseButton.mbRight : Result := MBT_RIGHT;
+ TMouseButton.mbMiddle : Result := MBT_MIDDLE;
+ else Result := MBT_LEFT;
+ end;
+end;
+
+procedure TForm1.DoResize;
+begin
+ try
+ FResizeCS.Acquire;
+
+ if FResizing then
+ FPendingResize := True
+ else
+ if Panel1.BufferIsResized then
+ Chromium1.Invalidate(PET_VIEW)
+ else
+ begin
+ FResizing := True;
+ Chromium1.WasResized;
+ end;
+ finally
+ FResizeCS.Release;
+ end;
+end;
+
+procedure TForm1.UpdatePanelOffset;
+var
+ TempPoint : TPoint;
+begin
+ try
+ FBrowserCS.Acquire;
+ TempPoint.x := 0;
+ TempPoint.y := 0;
+ FPanelOffset := Panel1.ClientToScreen(TempPoint);
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TForm1.UpdateElementBounds(const aArgumentList : ICefListValue);
+begin
+ try
+ FBrowserCS.Acquire;
+
+ if assigned(aArgumentList) and (aArgumentList.GetSize = 4) then
+ begin
+ FIsEditing := True;
+ FElementBounds.Left := aArgumentList.GetInt(0);
+ FElementBounds.Top := aArgumentList.GetInt(1);
+ FElementBounds.Right := FElementBounds.Left + aArgumentList.GetInt(2) - 1;
+ FElementBounds.Bottom := FElementBounds.Top + aArgumentList.GetInt(3) - 1;
+ end
+ else
+ begin
+ FIsEditing := False;
+ FElementBounds := rect(0, 0, 0, 0);
+ end;
+
+ SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TForm1.UpdateElementBounds(const aRect : TRect);
+begin
+ try
+ FBrowserCS.Acquire;
+ FIsEditing := True;
+ FElementBounds := aRect;
+ SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+function TForm1.CopyElementBounds(var aBounds : TRect) : boolean;
+begin
+ Result := False;
+ aBounds := rect(0, 0, 0, 0);
+
+ try
+ FBrowserCS.Acquire;
+
+ if FIsEditing then
+ begin
+ aBounds := FElementBounds;
+ Result := True;
+ end;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
function TForm1.SetIMECursorLocation : boolean;
{$IFDEF CEF_USE_IME}
var
- TempBounds : TRect;
- TempPoint : TPoint;
+ TempBounds : TRect;
+ TempPoint : TPoint;
TempScale : single;
{$ENDIF}
begin
@@ -351,60 +1113,12 @@ begin
{$ENDIF}
end;
-procedure TForm1.GoBtnClick(Sender: TObject);
+function TForm1.HandleIMEKeyEvent(Event: PGdkEventKey) : boolean;
begin
- FResizeCS.Acquire;
- FResizing := False;
- FPendingResize := False;
- FResizeCS.Release;
-
- Chromium1.LoadURL(UTF8Decode(AddressCb.Text));
-end;
-
-procedure TForm1.Chromium1AfterCreated(Sender: TObject; const browser: ICefBrowser);
-begin
- // Now the browser is fully initialized we can initialize the UI.
- SendCompMessage(CEF_AFTERCREATED);
-end;
-
-procedure TForm1.Chromium1SetFocus(Sender: TObject; const browser: ICefBrowser;
- source: TCefFocusSource; out Result: Boolean);
-begin
- Result := not(FBrowserIsFocused);
-end;
-
-procedure TForm1.AddressCbEnter(Sender: TObject);
-begin
- Chromium1.SetFocus(False);
-end;
-
-procedure TForm1.FormWindowStateChange(Sender: TObject);
-begin
- if (WindowState = wsMinimized) then
- begin
- Chromium1.SetFocus(False);
- Chromium1.WasHidden(True);
- end
- else
- begin
- Chromium1.WasHidden(False);
- Chromium1.SetFocus(FBrowserIsFocused);
- end;
-end;
-
-procedure TForm1.Application_OnActivate(Sender: TObject);
-begin
- IsEditing := FWasEditing;
- FCheckEditable := True;
- Chromium1.SetFocus(FBrowserIsFocused);
-end;
-
-procedure TForm1.Application_OnDeactivate(Sender: TObject);
-begin
- FWasEditing := IsEditing;
- Chromium1.SetFocus(False);
+ Result := False;
{$IFDEF CEF_USE_IME}
- FIMEHandler.Blur;
+ if SetIMECursorLocation then
+ Result := FIMEHandler.FilterKeyPress(Event);
{$ENDIF}
end;
@@ -412,7 +1126,7 @@ function TForm1.HandleGettingNodeIdResult(aSuccess : boolean; const aResult: ICe
var
TempParams, TempRsltDict : ICefDictionaryValue;
TempNodeID : integer;
-begin
+begin
Result := False;
if aSuccess and (aResult <> nil) then
@@ -589,492 +1303,16 @@ begin
FDevToolsStatus := dtsIdle;
end;
end;
+{%Endregion}
-procedure TForm1.Chromium1DevToolsMethodResult(Sender: TObject;
- const browser: ICefBrowser; message_id: integer; success: boolean;
- const result: ICefValue);
-begin
- case FDevToolsStatus of
- dtsGettingNodeId : HandleGettingNodeIdResult(success, result);
- dtsGettingNodeInfo : HandleGettingNodeInfoResult(success, result);
- dtsGettingNodeRect : HandleGettingNodeRectResult(success, result);
- end;
-end;
-
-procedure TForm1.FormActivate(Sender: TObject);
-begin
- // This will trigger the AfterCreated event when the browser is fully
- // initialized and ready to receive commands.
-
- // GlobalCEFApp.GlobalContextInitialized has to be TRUE before creating any browser
- // If it's not initialized yet, we use a simple timer to create the browser later.
-
- // Linux needs a visible form to create a browser so we need to use the
- // TForm.OnActivate event instead of the TForm.OnShow event
-
- {$IFDEF CEF_USE_IME}
- FIMEHandler.CreateContext;
- {$ENDIF}
-
- if not(Chromium1.Initialized) then
- begin
- // We have to update the DeviceScaleFactor here to get the scale of the
- // monitor where the main application form is located.
- GlobalCEFApp.UpdateDeviceScaleFactor;
- Panel1.UpdateDeviceScaleFactor;
- UpdatePanelOffset;
-
- // opaque white background color
- Chromium1.Options.BackgroundColor := CefColorSetARGB($FF, $FF, $FF, $FF);
- Chromium1.DefaultURL := UTF8Decode(AddressCb.Text);
-
- if not(Chromium1.CreateBrowser) then Timer1.Enabled := True;
- end;
-end;
-
-procedure TForm1.Panel1Enter(Sender: TObject);
-begin
- IsEditing := FWasEditing;
- FCheckEditable := True;
- FBrowserIsFocused := True;
- Chromium1.SetFocus(True);
-end;
-
-procedure TForm1.Panel1Exit(Sender: TObject);
-begin
- FWasEditing := IsEditing;
- FBrowserIsFocused := False;
- Chromium1.SetFocus(False);
- {$IFDEF CEF_USE_IME}
- FIMEHandler.Blur;
- {$ENDIF}
-end;
-
-procedure TForm1.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
- Shift: TShiftState; X, Y: Integer);
-var
- TempEvent : TCefMouseEvent;
-begin
- Panel1.SetFocus;
-
- TempEvent.x := X;
- TempEvent.y := Y;
- TempEvent.modifiers := getModifiers(Shift);
- DeviceToLogical(TempEvent, Panel1.ScreenScale);
- Chromium1.SendMouseClickEvent(@TempEvent, GetButton(Button), False, 1);
-end;
-
-procedure TForm1.Panel1MouseEnter(Sender: TObject);
-var
- TempEvent : TCefMouseEvent;
- TempPoint : TPoint;
-begin
- TempPoint := Panel1.ScreenToClient(mouse.CursorPos);
- TempEvent.x := TempPoint.x;
- TempEvent.y := TempPoint.y;
- TempEvent.modifiers := EVENTFLAG_NONE;
- DeviceToLogical(TempEvent, Panel1.ScreenScale);
- Chromium1.SendMouseMoveEvent(@TempEvent, False);
-end;
-
-procedure TForm1.Panel1MouseLeave(Sender: TObject);
-var
- TempEvent : TCefMouseEvent;
- TempPoint : TPoint;
-begin
- TempPoint := Panel1.ScreenToClient(mouse.CursorPos);
- TempEvent.x := TempPoint.x;
- TempEvent.y := TempPoint.y;
- TempEvent.modifiers := EVENTFLAG_NONE;
- DeviceToLogical(TempEvent, Panel1.ScreenScale);
- Chromium1.SendMouseMoveEvent(@TempEvent, True);
-end;
-
-procedure TForm1.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,
- Y: Integer);
-var
- TempEvent : TCefMouseEvent;
-begin
- TempEvent.x := x;
- TempEvent.y := y;
- TempEvent.modifiers := getModifiers(Shift);
- DeviceToLogical(TempEvent, Panel1.ScreenScale);
- Chromium1.SendMouseMoveEvent(@TempEvent, False);
-end;
-
-procedure TForm1.Panel1MouseUp(Sender: TObject; Button: TMouseButton;
- Shift: TShiftState; X, Y: Integer);
-var
- TempEvent : TCefMouseEvent;
- TempParams : ICefDictionaryValue;
-begin
- TempEvent.x := X;
- TempEvent.y := Y;
- TempEvent.modifiers := getModifiers(Shift);
- DeviceToLogical(TempEvent, Panel1.ScreenScale);
- Chromium1.SendMouseClickEvent(@TempEvent, GetButton(Button), True, 1);
-
- // GlobalCEFApp.OnFocusedNodeChanged is not triggered the first time the
- // browser gets the focus. We need to call a series of DevTool methods to
- // check if the HTML element under the mouse is editable to save its position
- // and size. This information will be used to show the IME.
- if FCheckEditable then
- try
- FCheckEditable := False;
- FDevToolsStatus := dtsGettingNodeId;
-
- TempParams := TCefDictionaryValueRef.New;
- TempParams.SetInt('x', DeviceToLogical(X, Panel1.ScreenScale));
- TempParams.SetInt('y', DeviceToLogical(Y, Panel1.ScreenScale));
-
- Chromium1.ExecuteDevToolsMethod(0, 'DOM.getNodeForLocation', TempParams);
- finally
- TempParams := nil;
- end;
-end;
-
-procedure TForm1.Panel1MouseWheel(Sender: TObject; Shift: TShiftState;
- WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
-var
- TempEvent : TCefMouseEvent;
-begin
- TempEvent.x := MousePos.x;
- TempEvent.y := MousePos.y;
- TempEvent.modifiers := getModifiers(Shift);
- DeviceToLogical(TempEvent, Panel1.ScreenScale);
- Chromium1.SendMouseWheelEvent(@TempEvent, 0, WheelDelta);
-end;
-
-procedure TForm1.Panel1Resize(Sender: TObject);
-begin
- DoResize;
-end;
-
-function TForm1.getModifiers(Shift: TShiftState): TCefEventFlags;
-begin
- Result := EVENTFLAG_NONE;
-
- if (ssShift in Shift) then Result := Result or EVENTFLAG_SHIFT_DOWN;
- if (ssAlt in Shift) then Result := Result or EVENTFLAG_ALT_DOWN;
- if (ssCtrl in Shift) then Result := Result or EVENTFLAG_CONTROL_DOWN;
- if (ssLeft in Shift) then Result := Result or EVENTFLAG_LEFT_MOUSE_BUTTON;
- if (ssRight in Shift) then Result := Result or EVENTFLAG_RIGHT_MOUSE_BUTTON;
- if (ssMiddle in Shift) then Result := Result or EVENTFLAG_MIDDLE_MOUSE_BUTTON;
-end;
-
-function TForm1.GetButton(Button: TMouseButton): TCefMouseButtonType;
-begin
- case Button of
- TMouseButton.mbRight : Result := MBT_RIGHT;
- TMouseButton.mbMiddle : Result := MBT_MIDDLE;
- else Result := MBT_LEFT;
- end;
-end;
-
-procedure TForm1.Chromium1BeforeClose(Sender: TObject;
- const browser: ICefBrowser);
-begin
- FCanClose := True;
- SendCompMessage(CEF_BEFORECLOSE);
-end;
-
-procedure TForm1.Chromium1BeforePopup(Sender: TObject;
- const browser: ICefBrowser; const frame: ICefFrame; popup_id: Integer;
- const targetUrl, targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition;
- userGesture: Boolean; const popupFeatures: TCefPopupFeatures;
- var windowInfo: TCefWindowInfo; var client: ICefClient;
- var settings: TCefBrowserSettings; var extra_info: ICefDictionaryValue;
- var noJavascriptAccess: Boolean; var Result: Boolean);
-begin
- // For simplicity, this demo blocks all popup windows and new tabs
- Result := (targetDisposition in [CEF_WOD_NEW_FOREGROUND_TAB, CEF_WOD_NEW_BACKGROUND_TAB, CEF_WOD_NEW_POPUP, CEF_WOD_NEW_WINDOW]);
-end;
-
-procedure TForm1.Chromium1CursorChange(Sender: TObject;
- const browser: ICefBrowser; cursor_: TCefCursorHandle;
- cursorType: TCefCursorType; const customCursorInfo: PCefCursorInfo;
- var aResult : boolean);
-begin
- PanelCursor := CefCursorToWindowsCursor(cursorType);
- aResult := True;
-
- SendCompMessage(CEF_UPDATE_CURSOR);
-end;
-
-procedure TForm1.Chromium1GetScreenInfo(Sender: TObject;
- const browser: ICefBrowser; var screenInfo: TCefScreenInfo; out
- Result: Boolean);
-var
- TempRect : TCEFRect;
- TempScale : single;
-begin
- TempScale := Panel1.ScreenScale;
- TempRect.x := 0;
- TempRect.y := 0;
- TempRect.width := DeviceToLogical(Panel1.Width, TempScale);
- TempRect.height := DeviceToLogical(Panel1.Height, TempScale);
-
- screenInfo.device_scale_factor := TempScale;
- screenInfo.depth := 0;
- screenInfo.depth_per_component := 0;
- screenInfo.is_monochrome := Ord(False);
- screenInfo.rect := TempRect;
- screenInfo.available_rect := TempRect;
-
- Result := True;
-end;
-
-procedure TForm1.Chromium1GetScreenPoint(Sender: TObject;
- const browser: ICefBrowser; viewX, viewY: Integer; var screenX,
- screenY: Integer; out Result: Boolean);
-begin
- try
- FBrowserCS.Acquire;
- screenX := LogicalToDevice(viewX, Panel1.ScreenScale) + FPanelOffset.x;
- screenY := LogicalToDevice(viewY, Panel1.ScreenScale) + FPanelOffset.y;
- Result := True;
- finally
- FBrowserCS.Release;
- end;
-end;
-
-procedure TForm1.Chromium1GetViewRect(Sender: TObject;
- const browser: ICefBrowser; var rect: TCefRect);
-var
- TempScale : single;
-begin
- TempScale := Panel1.ScreenScale;
- rect.x := 0;
- rect.y := 0;
- rect.width := DeviceToLogical(Panel1.Width, TempScale);
- rect.height := DeviceToLogical(Panel1.Height, TempScale);
-end;
-
-procedure TForm1.Chromium1OpenUrlFromTab(Sender: TObject;
- const browser: ICefBrowser; const frame: ICefFrame; const targetUrl: ustring;
- targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean; out
- Result: Boolean);
-begin
- // For simplicity, this demo blocks all popup windows and new tabs
- Result := (targetDisposition in [CEF_WOD_NEW_FOREGROUND_TAB, CEF_WOD_NEW_BACKGROUND_TAB, CEF_WOD_NEW_POPUP, CEF_WOD_NEW_WINDOW]);
-end;
-
-procedure TForm1.Chromium1Paint(Sender: TObject; const browser: ICefBrowser;
- type_: TCefPaintElementType; dirtyRectsCount: NativeUInt;
- const dirtyRects: PCefRectArray; const buffer: Pointer; aWidth, aHeight: Integer
- );
-var
- src, dst: PByte;
- i, j, TempLineSize, TempSrcOffset, TempDstOffset, SrcStride : Integer;
- n : NativeUInt;
- TempWidth, TempHeight : integer;
- TempBufferBits : Pointer;
- TempForcedResize : boolean;
- TempBitmap : TCEFBitmapBitBuffer;
- TempSrcRect : TRect;
-begin
- try
- FResizeCS.Acquire;
- TempForcedResize := False;
-
- if Panel1.BeginBufferDraw then
- begin
- if (type_ = PET_POPUP) then
- begin
- Panel1.UpdateOrigPopupBufferDimensions(aWidth, aHeight);
-
- TempBitmap := Panel1.OrigPopupBuffer;
- TempWidth := Panel1.OrigPopupBufferWidth;
- TempHeight := Panel1.OrigPopupBufferHeight;
- end
- else
- begin
- TempForcedResize := Panel1.UpdateOrigBufferDimensions(aWidth, aHeight) or
- not(Panel1.BufferIsResized(False));
-
- TempBitmap := Panel1.OrigBuffer;
- TempWidth := Panel1.OrigBufferWidth;
- TempHeight := Panel1.OrigBufferHeight;
- end;
-
- SrcStride := aWidth * SizeOf(TRGBQuad);
- n := 0;
-
- while (n < dirtyRectsCount) do
- begin
- if (dirtyRects^[n].x >= 0) and (dirtyRects^[n].y >= 0) then
- begin
- TempLineSize := min(dirtyRects^[n].width, TempWidth - dirtyRects^[n].x) * SizeOf(TRGBQuad);
-
- if (TempLineSize > 0) then
- begin
- TempSrcOffset := ((dirtyRects^[n].y * aWidth) + dirtyRects^[n].x) * SizeOf(TRGBQuad);
- TempDstOffset := (dirtyRects^[n].x * SizeOf(TRGBQuad));
-
- src := @PByte(buffer)[TempSrcOffset];
-
- i := 0;
- j := min(dirtyRects^[n].height, TempHeight - dirtyRects^[n].y);
-
- while (i < j) do
- begin
- TempBufferBits := TempBitmap.Scanline[dirtyRects^[n].y + i];
- dst := @PByte(TempBufferBits)[TempDstOffset];
-
- Move(src^, dst^, TempLineSize);
-
- Inc(src, SrcStride);
- inc(i);
- end;
- end;
- end;
-
- inc(n);
- end;
-
- if FShowPopup then
- begin
- TempSrcRect := Rect(0, 0,
- FPopUpRect.Right - FPopUpRect.Left,
- FPopUpRect.Bottom - FPopUpRect.Top);
-
- Panel1.DrawOrigPopupBuffer(TempSrcRect, FPopUpRect);
- end;
-
- Panel1.EndBufferDraw;
-
- SendCompMessage(CEF_PENDINGINVALIDATE);
-
- if (type_ = PET_VIEW) then
- begin
- if TempForcedResize or FPendingResize then
- SendCompMessage(CEF_PENDINGRESIZE);
-
- FResizing := False;
- FPendingResize := False;
- end;
- end;
- finally
- FResizeCS.Release;
- end;
-end;
-
-procedure TForm1.Chromium1PopupShow(Sender: TObject; const browser: ICefBrowser; aShow: Boolean);
-begin
- if aShow then
- FShowPopUp := True
- else
- begin
- FShowPopUp := False;
- FPopUpRect := rect(0, 0, 0, 0);
-
- if (Chromium1 <> nil) then Chromium1.Invalidate(PET_VIEW);
- end;
-end;
-
-procedure TForm1.Chromium1PopupSize(Sender: TObject; const browser: ICefBrowser; const rect: PCefRect);
-begin
- LogicalToDevice(rect^, Panel1.ScreenScale);
-
- FPopUpRect.Left := rect^.x;
- FPopUpRect.Top := rect^.y;
- FPopUpRect.Right := rect^.x + rect^.width - 1;
- FPopUpRect.Bottom := rect^.y + rect^.height - 1;
-end;
-
-procedure TForm1.Chromium1Tooltip(Sender: TObject; const browser: ICefBrowser; var aText: ustring; out Result: Boolean);
-begin
- PanelHint := aText;
- Result := True;
-
- SendCompMessage(CEF_UPDATE_HINT);
-end;
-
-procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: boolean);
-begin
- CanClose := FCanClose;
-
- if not(FClosing) then
- begin
- FClosing := True;
- Visible := False;
- Chromium1.CloseBrowser(True);
- end;
-end;
-
-procedure TForm1.FormCreate(Sender: TObject);
-begin
- FCheckEditable := True;
- FWasEditing := False;
- FDevToolsStatus := dtsIdle;
- FPopUpRect := rect(0, 0, 0, 0);
- FShowPopUp := False;
- FResizing := False;
- FPendingResize := False;
- FCanClose := False;
- FClosing := False;
- FBrowserIsFocused := False;
- FResizeCS := TCriticalSection.Create;
- FBrowserCS := TCriticalSection.Create;
- {$IFDEF CEF_USE_IME}
- FIMEHandler := TCEFLinuxOSRIMEHandler.Create(Panel1);
- {$ENDIF}
- IsEditing := False;
-
- Panel1.CopyOriginalBuffer := True;
-
- ConnectKeyPressReleaseEvents(PGtkWidget(Panel1.Handle));
-
- Application.OnActivate := @Application_OnActivate;
- Application.OnDeactivate := @Application_OnDeactivate;
-end;
-
-procedure TForm1.FormDestroy(Sender: TObject);
-begin
- if (FResizeCS <> nil) then FreeAndNil(FResizeCS);
- if (FBrowserCS <> nil) then FreeAndNil(FBrowserCS);
- {$IFDEF CEF_USE_IME}
- if (FIMEHandler <> nil) then FreeAndNil(FIMEHandler);
- {$ENDIF}
-end;
-
-procedure TForm1.FormHide(Sender: TObject);
-begin
- Chromium1.SetFocus(False);
- Chromium1.WasHidden(True);
-end;
-
-procedure TForm1.FormShow(Sender: TObject);
-begin
- Chromium1.WasHidden(False);
- Chromium1.SetFocus(FBrowserIsFocused);
-end;
-
-procedure TForm1.GoBtnEnter(Sender: TObject);
-begin
- Chromium1.SetFocus(False);
-end;
-
-procedure TForm1.SnapshotBtnClick(Sender: TObject);
-begin
- if SaveDialog1.Execute then
- Panel1.SaveToFile(SaveDialog1.FileName);
-end;
-
-procedure TForm1.Timer1Timer(Sender: TObject);
-begin
- Timer1.Enabled := False;
-
- if not(Chromium1.CreateBrowser) and not(Chromium1.Initialized) then
- Timer1.Enabled := True;
-end;
-
+{Message handlers}
+{%Region}
procedure TForm1.BrowserCreatedMsg(Data: PtrInt);
begin
Caption := 'Simple OSR Browser';
AddressPnl.Enabled := True;
- Chromium1.SetFocus(FBrowserIsFocused);
+ Chromium1.SetFocus(Panel1.Focused);
Chromium1.NotifyMoveOrResizeStarted;
end;
@@ -1104,175 +1342,14 @@ begin
Panel1.ShowHint := (length(Panel1.hint) > 0);
end;
-procedure TForm1.SendCompMessage(aMsg : cardinal; aData: PtrInt);
+procedure TForm1.FocusEnabledMsg(Data: PtrInt);
begin
- case aMsg of
- CEF_AFTERCREATED : Application.QueueAsyncCall(@BrowserCreatedMsg, aData);
- CEF_BEFORECLOSE : Application.QueueAsyncCall(@BrowserCloseFormMsg, aData);
- CEF_PENDINGRESIZE : Application.QueueAsyncCall(@PendingResizeMsg, aData);
- CEF_PENDINGINVALIDATE : Application.QueueAsyncCall(@PendingInvalidateMsg, aData);
- CEF_UPDATE_CURSOR : Application.QueueAsyncCall(@PendingCursorUpdateMsg, aData);
- CEF_UPDATE_HINT : Application.QueueAsyncCall(@PendingHintUpdateMsg, aData);
- CEF_FOCUSENABLED : Application.QueueAsyncCall(@FocusEnabledMsg, aData);
- end;
-end;
-
-procedure TForm1.DoResize;
-begin
- try
- FResizeCS.Acquire;
-
- if FResizing then
- FPendingResize := True
- else
- if Panel1.BufferIsResized then
- Chromium1.Invalidate(PET_VIEW)
- else
- begin
- FResizing := True;
- Chromium1.WasResized;
- end;
- finally
- FResizeCS.Release;
- end;
-end;
-
-procedure TForm1.UpdatePanelOffset;
-var
- TempPoint : TPoint;
-begin
- try
- FBrowserCS.Acquire;
- TempPoint.x := 0;
- TempPoint.y := 0;
- FPanelOffset := Panel1.ClientToScreen(TempPoint);
- finally
- FBrowserCS.Release;
- end;
-end;
-
-procedure TForm1.UpdateElementBounds(const aArgumentList : ICefListValue);
-begin
- try
- FBrowserCS.Acquire;
-
- if assigned(aArgumentList) and (aArgumentList.GetSize = 4) then
- begin
- FIsEditing := True;
- FElementBounds.Left := aArgumentList.GetInt(0);
- FElementBounds.Top := aArgumentList.GetInt(1);
- FElementBounds.Right := FElementBounds.Left + aArgumentList.GetInt(2) - 1;
- FElementBounds.Bottom := FElementBounds.Top + aArgumentList.GetInt(3) - 1;
- end
- else
- begin
- FIsEditing := False;
- FElementBounds := rect(0, 0, 0, 0);
- end;
-
- SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
- finally
- FBrowserCS.Release;
- end;
-end;
-
-procedure TForm1.UpdateElementBounds(const aRect : TRect);
-begin
- try
- FBrowserCS.Acquire;
- FIsEditing := True;
- FElementBounds := aRect;
- SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
- finally
- FBrowserCS.Release;
- end;
-end;
-
-function TForm1.CopyElementBounds(var aBounds : TRect) : boolean;
-begin
- Result := False;
- aBounds := rect(0, 0, 0, 0);
-
- try
- FBrowserCS.Acquire;
-
- if FIsEditing then
- begin
- aBounds := FElementBounds;
- Result := True;
- end;
- finally
- FBrowserCS.Release;
- end;
-end;
-
-function TForm1.GetPanelCursor : TCursor;
-begin
- try
- FBrowserCS.Acquire;
- Result := FPanelCursor;
- finally
- FBrowserCS.Release;
- end;
-end;
-
-function TForm1.GetPanelHint : ustring;
-begin
- try
- FBrowserCS.Acquire;
- Result := FPanelHint;
- finally
- FBrowserCS.Release;
- end;
-end;
-
-function TForm1.GetIsEditing : boolean;
-begin
- try
- FBrowserCS.Acquire;
- Result := FIsEditing;
- finally
- FBrowserCS.Release;
- end;
-end;
-
-procedure TForm1.SetPanelCursor(aValue : TCursor);
-begin
- try
- FBrowserCS.Acquire;
- FPanelCursor := aValue;
- finally
- FBrowserCS.Release;
- end;
-end;
-
-procedure TForm1.SetPanelHint(const aValue : ustring);
-begin
- try
- FBrowserCS.Acquire;
- FPanelHint := aValue;
- finally
- FBrowserCS.Release;
- end;
-end;
-
-procedure TForm1.SetIsEditing(aValue : boolean);
-begin
- try
- FBrowserCS.Acquire;
-
- if aValue then
- FIsEditing := True
- else
- begin
- FIsEditing := False;
- FElementBounds := rect(0, 0, 0, 0);
- end;
-
- SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
- finally
- FBrowserCS.Release;
- end;
+ {$IFDEF CEF_USE_IME}
+ if (Data <> 0) then
+ FIMEHandler.Focus // Set the client window for the input context when an editable HTML element is focused
+ else
+ FIMEHandler.Blur; // Reset the input context when the editable HTML is not focused
+ {$ENDIF}
end;
procedure TForm1.WMMove(var Message: TLMMove);
@@ -1294,73 +1371,8 @@ begin
inherited;
UpdatePanelOffset;
Chromium1.NotifyMoveOrResizeStarted;
-end;
-
-procedure TForm1.FocusEnabledMsg(Data: PtrInt);
-begin
- {$IFDEF CEF_USE_IME}
- if (Data <> 0) then
- FIMEHandler.Focus // Set the client window for the input context when an editable HTML element is focused
- else
- FIMEHandler.Blur; // Reset the input context when the editable HTML is not focused
- {$ENDIF}
-end;
-
-procedure TForm1.Chromium1ProcessMessageReceived(Sender: TObject;
- const browser: ICefBrowser; const frame: ICefFrame;
- sourceProcess: TCefProcessId; const message: ICefProcessMessage; out
- Result: Boolean);
-begin
- Result := False;
- {$IFDEF CEF_USE_IME}
- if (message = nil) then exit;
-
- if (message.Name = EDITABLE_MSGNAME) then
- UpdateElementBounds(message.ArgumentList)
- else
- IsEditing := False;
-
- Result := True;
- {$ENDIF}
-end;
-
-procedure TForm1.Panel1IMECommit(Sender: TObject; const aCommitText: ustring);
-{$IFDEF CEF_USE_IME}
-const
- UINT32_MAX = high(cardinal);
-var
- replacement_range : TCefRange;
-{$ENDIF}
-begin
- {$IFDEF CEF_USE_IME}
- replacement_range.from := UINT32_MAX;
- replacement_range.to_ := UINT32_MAX;
-
- Chromium1.IMECommitText(aCommitText, @replacement_range, 0);
- {$ENDIF}
-end;
-
-procedure TForm1.Panel1IMEPreEditChanged(Sender: TObject; aFlag: cardinal;
- const aPreEditText: ustring);
-begin
- {$IFDEF CEF_USE_IME}
- SetIMECursorLocation;
- {$ENDIF}
-end;
-
-procedure TForm1.Panel1IMEPreEditEnd(Sender: TObject);
-begin
- {$IFDEF CEF_USE_IME}
- Chromium1.IMECancelComposition;
- {$ENDIF}
-end;
-
-procedure TForm1.Panel1IMEPreEditStart(Sender: TObject);
-begin
- {$IFDEF CEF_USE_IME}
- SetIMECursorLocation;
- {$ENDIF}
end;
+{%Endregion}
end.
diff --git a/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/00-Delete.bat b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/00-Delete.bat
new file mode 100644
index 00000000..0b5ba5c8
--- /dev/null
+++ b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/00-Delete.bat
@@ -0,0 +1,2 @@
+rmdir /S /Q lib
+rmdir /S /Q backup
diff --git a/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.ico b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.ico
new file mode 100644
index 00000000..0341321b
Binary files /dev/null and b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.ico differ
diff --git a/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.lpi b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.lpi
new file mode 100644
index 00000000..5b6a124f
--- /dev/null
+++ b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.lpi
@@ -0,0 +1,101 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.lpr b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.lpr
new file mode 100644
index 00000000..6e25c596
--- /dev/null
+++ b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.lpr
@@ -0,0 +1,36 @@
+program SimpleOSRBrowser;
+
+{$mode objfpc}{$H+}
+
+uses
+ {$IFDEF UNIX}{$IFDEF UseCThreads}
+ cthreads,
+ {$ENDIF}{$ENDIF}
+ // "Interfaces" is a custom unit used to initialize the LCL WidgetSet
+ // We keep the same name to avoid a Lazarus warning.
+ Interfaces, // this includes the LCL widgetset
+ Forms, lazmouseandkeyinput, uSimpleOSRBrowser,
+ { you can add units after this }
+ uCEFApplication;
+
+{$R *.res}
+
+begin
+ CreateGlobalCEFApp;
+
+ if GlobalCEFApp.StartMainProcess then
+ begin
+ // The LCL Widgetset must be initialized after the CEF initialization and
+ // only in the browser process.
+ CustomWidgetSetInitialization;
+ RequireDerivedFormResource:=True;
+ Application.Scaled:=True;
+ Application.Initialize;
+ Application.CreateForm(TMainForm, MainForm);
+ Application.Run;
+ CustomWidgetSetFinalization;
+ end;
+
+ DestroyGlobalCEFApp;
+end.
+
diff --git a/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.res b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.res
new file mode 100644
index 00000000..bec39b4a
Binary files /dev/null and b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/SimpleOSRBrowser.res differ
diff --git a/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/interfaces.pas b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/interfaces.pas
new file mode 100644
index 00000000..862234fd
--- /dev/null
+++ b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/interfaces.pas
@@ -0,0 +1,67 @@
+{
+ /***************************************************************************
+ Interfaces.pp - determines what interface to use
+ -------------------
+
+ Initial Revision : Thu July 1st CST 1999
+
+
+ ***************************************************************************/
+
+ *****************************************************************************
+ This file is part of the Lazarus Component Library (LCL)
+
+ See the file COPYING.modifiedLGPL.txt, included in this distribution,
+ for details about the license.
+ *****************************************************************************
+}
+
+unit interfaces;
+
+{$mode objfpc}
+{$H+}
+
+interface
+
+uses
+ {$IFDEF UNIX}{$IFNDEF DisableCWString}cwstring,{$ENDIF}{$ENDIF}
+ InterfaceBase;
+
+procedure CustomWidgetSetInitialization;
+procedure CustomWidgetSetFinalization;
+
+implementation
+
+uses
+ gtk3int, Forms, xlib;
+
+function CustomX11ErrorHandler(Display:PDisplay; ErrorEv:PXErrorEvent):longint;cdecl;
+begin
+ {$IFDEF DEBUG}
+ XError := ErrorEv^.error_code;
+ WriteLn('Error: ' + IntToStr(XError));
+ {$ENDIF}
+ Result := 0;
+end;
+
+function CustomXIOErrorHandler(Display:PDisplay):longint;cdecl;
+begin
+ Result := 0;
+end;
+
+procedure CustomWidgetSetInitialization;
+begin
+ //gdk_set_allowed_backends('X11');
+ CreateWidgetset(TGtk3WidgetSet);
+ // Install xlib error handlers so that the application won't be terminated
+ // on non-fatal errors. Must be done after initializing GTK.
+ XSetErrorHandler(@CustomX11ErrorHandler);
+ XSetIOErrorHandler(@CustomXIOErrorHandler);
+end;
+
+procedure CustomWidgetSetFinalization;
+begin
+ FreeWidgetSet;
+end;
+
+end.
diff --git a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/uceflinuxosrimehandler.pas b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/uceflinuxosrimehandler.pas
similarity index 76%
rename from demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/uceflinuxosrimehandler.pas
rename to demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/uceflinuxosrimehandler.pas
index d825b802..5c5de1e6 100644
--- a/demos/Lazarus_Linux_GTK2/SimpleOSRBrowser/uceflinuxosrimehandler.pas
+++ b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/uceflinuxosrimehandler.pas
@@ -6,6 +6,7 @@ interface
uses
{$IFDEF LCLGTK2}gtk2, glib2, gdk2,{$ENDIF}
+ {$IFDEF LCLGTK3}LazGdk3, LazGtk3, LazGObject2, LazGLib2, gtk3procs, gtk3widgets,{$ENDIF}
Classes, ExtCtrls, Forms;
type
@@ -14,9 +15,9 @@ type
FPanel : TCustomPanel;
FForm : TCustomForm;
FHasFocus : boolean;
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
FIMContext : PGtkIMContext;
- {$ENDIF}
+ {$IFEND}
function GetInitialized : boolean;
procedure SetPanel(aValue : TCustomPanel);
@@ -33,9 +34,9 @@ type
procedure Blur;
procedure Reset;
procedure SetCursorLocation(X, Y: integer);
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
function FilterKeyPress(aEvent : PGdkEventKey) : boolean;
- {$ENDIF}
+ {$ENDIF}
property Initialized : boolean read GetInitialized;
property HasFocus : boolean read FHasFocus;
@@ -48,11 +49,11 @@ implementation
// https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/gtk/input_method_context_impl_gtk.cc
uses
- {$IFDEF LCLGTK2}pango,{$ENDIF}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}pango,{$ENDIF}
{$IFDEF FPC}LCLType, LCLIntf, LMessages,{$ENDIF}
SysUtils;
-{$IFDEF LCLGTK2}
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
procedure gtk_commit_cb({%H-}context: PGtkIMContext; const Str: Pgchar; {%H-}Data: Pointer); cdecl;
begin
SendMessage(HWND(Data), LM_IM_COMPOSITION, GTK_IM_FLAG_COMMIT, LPARAM(Str));
@@ -74,7 +75,7 @@ var
TempPangoAttr : PPangoAttrList;
TempCurpos : gint;
begin
- gtk_im_context_get_preedit_string(context, @TempStr, TempPangoAttr, @TempCurpos);
+ gtk_im_context_get_preedit_string(context, @TempStr, {$IFDEF LCLGTK3}@{$ENDIF}TempPangoAttr, @TempCurpos);
SendMessage(HWND(Data), LM_IM_COMPOSITION, GTK_IM_FLAG_PREEDIT, LPARAM(pchar(TempStr)));
g_free(TempStr);
pango_attr_list_unref(TempPangoAttr);
@@ -87,7 +88,7 @@ begin
FPanel := aPanel;
FHasFocus := False;
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
FIMContext := nil;
{$ENDIF}
@@ -107,7 +108,7 @@ end;
function TCEFLinuxOSRIMEHandler.GetInitialized : boolean;
begin
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
Result := assigned(FPanel) and assigned(FIMContext);
{$ELSE}
Result := False;
@@ -127,7 +128,7 @@ end;
procedure TCEFLinuxOSRIMEHandler.CreateContext;
begin
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
if not(assigned(FIMContext)) then
begin
FIMContext := gtk_im_multicontext_new();
@@ -139,7 +140,7 @@ end;
procedure TCEFLinuxOSRIMEHandler.DestroyContext;
begin
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
if assigned(FIMContext) then
begin
g_object_unref(FIMContext);
@@ -149,7 +150,7 @@ begin
end;
procedure TCEFLinuxOSRIMEHandler.SetClientWindow;
-{$IFDEF LCLGTK2}
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
var
TempWidget : PGtkWidget;
{$ENDIF}
@@ -160,6 +161,10 @@ begin
TempWidget := PGtkWidget(FForm.Handle);
gtk_im_context_set_client_window(FIMContext, TempWidget^.window);
{$ENDIF}
+ {$IFDEF LCLGTK3}
+ TempWidget := TGtk3Widget(FForm.Handle).Widget;
+ gtk_im_context_set_client_window(FIMContext, TempWidget^.window);
+ {$ENDIF}
end;
end;
@@ -167,7 +172,7 @@ procedure TCEFLinuxOSRIMEHandler.ResetClientWindow;
begin
if Initialized then
begin
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
gtk_im_context_reset(FIMContext);
gtk_im_context_set_client_window(FIMContext, nil);
{$ENDIF}
@@ -178,6 +183,12 @@ procedure TCEFLinuxOSRIMEHandler.ConnectSignals;
begin
if Initialized then
begin
+ {$IFDEF LCLGTK3}
+ g_signal_connect_data(PGObject(@FIMContext), 'commit', TGCallback(@gtk_commit_cb), GPointer(FPanel.Handle), nil, G_CONNECT_DEFAULT);
+ g_signal_connect_data(PGObject(@FIMContext), 'preedit-start', TGCallback(@gtk_preedit_start_cb), GPointer(FPanel.Handle), nil, G_CONNECT_DEFAULT);
+ g_signal_connect_data(PGObject(@FIMContext), 'preedit-end', TGCallback(@gtk_preedit_end_cb), GPointer(FPanel.Handle), nil, G_CONNECT_DEFAULT);
+ g_signal_connect_data(PGObject(@FIMContext), 'preedit-changed', TGCallback(@gtk_preedit_changed_cb), GPointer(FPanel.Handle), nil, G_CONNECT_DEFAULT);
+ {$ENDIF}
{$IFDEF LCLGTK2}
g_signal_connect(G_OBJECT(FIMContext), 'commit', G_CALLBACK(@gtk_commit_cb), GPointer(FPanel.Handle));
g_signal_connect(G_OBJECT(FIMContext), 'preedit-start', G_CALLBACK(@gtk_preedit_start_cb), GPointer(FPanel.Handle));
@@ -191,7 +202,7 @@ procedure TCEFLinuxOSRIMEHandler.Focus;
begin
if Initialized then
begin
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
gtk_im_context_focus_in(FIMContext);
{$ENDIF}
FHasFocus := True;
@@ -202,7 +213,7 @@ procedure TCEFLinuxOSRIMEHandler.Blur;
begin
if Initialized then
begin
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
gtk_im_context_focus_out(FIMContext);
{$ENDIF}
FHasFocus := False;
@@ -213,7 +224,7 @@ procedure TCEFLinuxOSRIMEHandler.Reset;
begin
if Initialized then
begin
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
gtk_im_context_reset(FIMContext);
// Some input methods may not honour the reset call.
@@ -228,14 +239,14 @@ begin
end;
procedure TCEFLinuxOSRIMEHandler.SetCursorLocation(X, Y: integer);
-{$IFDEF LCLGTK2}
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
var
TempCurPos: TGdkRectangle;
{$ENDIF}
begin
if Initialized then
begin
- {$IFDEF LCLGTK2}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
TempCurPos.x := x;
TempCurPos.y := y;
TempCurPos.width := 0;
@@ -246,7 +257,7 @@ begin
end;
end;
-{$IFDEF LCLGTK2}
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
function TCEFLinuxOSRIMEHandler.FilterKeyPress(aEvent : PGdkEventKey) : boolean;
begin
if Initialized then
diff --git a/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/usimpleosrbrowser.lfm b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/usimpleosrbrowser.lfm
new file mode 100644
index 00000000..67acd582
--- /dev/null
+++ b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/usimpleosrbrowser.lfm
@@ -0,0 +1,162 @@
+object MainForm: TMainForm
+ Left = 1658
+ Height = 630
+ Top = 423
+ Width = 1000
+ Caption = ' Initializing browser. Please wait...'
+ ClientHeight = 630
+ ClientWidth = 1000
+ Position = poScreenCenter
+ LCLVersion = '4.2.0.0'
+ OnActivate = FormActivate
+ OnCloseQuery = FormCloseQuery
+ OnCreate = FormCreate
+ OnDestroy = FormDestroy
+ OnHide = FormHide
+ OnShow = FormShow
+ OnWindowStateChange = FormWindowStateChange
+ object AddressPnl: TPanel
+ Left = 0
+ Height = 30
+ Top = 0
+ Width = 1000
+ Align = alTop
+ ClientHeight = 30
+ ClientWidth = 1000
+ TabOrder = 0
+ object AddressCb: TComboBox
+ Left = 1
+ Height = 28
+ Top = 1
+ Width = 967
+ Align = alClient
+ AutoSelect = False
+ ItemHeight = 0
+ ItemIndex = 0
+ Items.Strings = (
+ 'https://www.google.com'
+ 'https://www.bing.com'
+ 'https://duckduckgo.com'
+ 'https://www.qwant.com'
+ 'https://yandex.com'
+ 'https://www.startpage.com'
+ 'https://www.ecosia.org'
+ 'https://www.baidu.com'
+ 'https://www.whatismybrowser.com/detect/what-http-headers-is-my-browser-sending'
+ 'https://www.w3schools.com/js/tryit.asp?filename=tryjs_win_close'
+ 'https://www.w3schools.com/js/tryit.asp?filename=tryjs_alert'
+ 'https://www.w3schools.com/js/tryit.asp?filename=tryjs_loc_assign'
+ 'https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_style_backgroundcolor'
+ 'https://www.w3schools.com/Tags/tryit.asp?filename=tryhtml_iframe_name'
+ 'https://www.w3schools.com/tags/tryit.asp?filename=tryhtml5_input_type_file'
+ 'https://www.w3schools.com/jsref/tryit.asp?filename=tryjsref_state_throw_error'
+ 'https://www.htmlquick.com/es/reference/tags/input-file.html'
+ 'https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input/file'
+ 'https://developer.mozilla.org/en-US/docs/Web/API/HTMLInputElement/webkitdirectory'
+ 'https://www.w3schools.com/html/html5_video.asp'
+ 'http://html5test.com/'
+ 'https://webrtc.github.io/samples/src/content/devices/input-output/'
+ 'https://test.webrtc.org/'
+ 'https://www.browserleaks.com/webrtc'
+ 'https://shaka-player-demo.appspot.com/demo/'
+ 'http://webglsamples.org/'
+ 'https://get.webgl.org/'
+ 'https://www.briskbard.com'
+ 'https://www.youtube.com'
+ 'https://html5demos.com/drag/'
+ 'https://frames-per-second.appspot.com/'
+ 'https://www.sede.fnmt.gob.es/certificados/persona-fisica/verificar-estado'
+ 'https://www.kirupa.com/html5/accessing_your_webcam_in_html5.htm'
+ 'https://www.xdumaine.com/enumerateDevices/test/'
+ 'https://dagrs.berkeley.edu/sites/default/files/2020-01/sample.pdf'
+ 'https://codepen.io/udaymanvar/pen/MWaePBY'
+ 'https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/accept'
+ 'chrome://version/'
+ 'chrome://net-internals/'
+ 'chrome://tracing/'
+ 'chrome://appcache-internals/'
+ 'chrome://blob-internals/'
+ 'chrome://view-http-cache/'
+ 'chrome://credits/'
+ 'chrome://histograms/'
+ 'chrome://media-internals/'
+ 'chrome://kill'
+ 'chrome://crash'
+ 'chrome://hang'
+ 'chrome://shorthang'
+ 'chrome://gpuclean'
+ 'chrome://gpucrash'
+ 'chrome://gpuhang'
+ 'chrome://extensions-support'
+ 'chrome://process-internals'
+ )
+ TabOrder = 0
+ Text = 'https://www.google.com'
+ OnEnter = AddressCbEnter
+ end
+ object GoBtn: TButton
+ Left = 968
+ Height = 28
+ Top = 1
+ Width = 31
+ Align = alRight
+ Caption = 'Go'
+ TabOrder = 1
+ OnClick = GoBtnClick
+ OnEnter = GoBtnEnter
+ end
+ end
+ object Panel1: TBufferPanel
+ Tag = 99
+ Left = 0
+ Height = 600
+ Top = 30
+ Width = 1000
+ OnIMEPreEditStart = Panel1IMEPreEditStart
+ OnIMEPreEditEnd = Panel1IMEPreEditEnd
+ OnIMEPreEditChanged = Panel1IMEPreEditChanged
+ OnIMECommit = Panel1IMECommit
+ OnGdkKeyPress = Panel1GdkKeyPress
+ OnGdkKeyRelease = Panel1GdkKeyRelease
+ CopyOriginalBuffer = True
+ Align = alClient
+ Color = clWhite
+ ParentColor = False
+ TabOrder = 1
+ TabStop = True
+ OnEnter = Panel1Enter
+ OnExit = Panel1Exit
+ OnMouseDown = Panel1MouseDown
+ OnMouseMove = Panel1MouseMove
+ OnMouseUp = Panel1MouseUp
+ OnMouseWheel = Panel1MouseWheel
+ OnResize = Panel1Resize
+ OnMouseEnter = Panel1MouseEnter
+ OnMouseLeave = Panel1MouseLeave
+ end
+ object Chromium1: TChromium
+ OnProcessMessageReceived = Chromium1ProcessMessageReceived
+ OnSetFocus = Chromium1SetFocus
+ OnTooltip = Chromium1Tooltip
+ OnCursorChange = Chromium1CursorChange
+ OnBeforePopup = Chromium1BeforePopup
+ OnAfterCreated = Chromium1AfterCreated
+ OnBeforeClose = Chromium1BeforeClose
+ OnOpenUrlFromTab = Chromium1OpenUrlFromTab
+ OnGetViewRect = Chromium1GetViewRect
+ OnGetScreenPoint = Chromium1GetScreenPoint
+ OnGetScreenInfo = Chromium1GetScreenInfo
+ OnPopupShow = Chromium1PopupShow
+ OnPopupSize = Chromium1PopupSize
+ OnPaint = Chromium1Paint
+ OnDevToolsMethodResult = Chromium1DevToolsMethodResult
+ Left = 48
+ Top = 72
+ end
+ object Timer1: TTimer
+ Enabled = False
+ OnTimer = Timer1Timer
+ Left = 48
+ Top = 152
+ end
+end
diff --git a/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/usimpleosrbrowser.pas b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/usimpleosrbrowser.pas
new file mode 100644
index 00000000..2f137b89
--- /dev/null
+++ b/demos/Lazarus_Linux_GTK3/SimpleOSRBrowser/usimpleosrbrowser.pas
@@ -0,0 +1,1364 @@
+unit uSimpleOSRBrowser;
+
+{$mode objfpc}{$H+}
+
+// Enable this DEFINE in case you need to use the IME
+{.$DEFINE CEF_USE_IME}
+
+interface
+
+uses
+ Classes, SysUtils, Forms, Controls, Graphics, Dialogs, ExtCtrls, StdCtrls,
+ LCLType, ComCtrls, Types, SyncObjs, LMessages, LazGdk3,
+ uCEFChromium, uCEFTypes, uCEFInterfaces, uCEFConstants,
+ {$IFDEF CEF_USE_IME}uCEFLinuxOSRIMEHandler,{$ENDIF} uCEFBufferPanel;
+
+type
+ TDevToolsStatus = (dtsIdle, dtsGettingNodeID, dtsGettingNodeInfo, dtsGettingNodeRect);
+
+ { TMainForm }
+ TMainForm = class(TForm)
+ AddressCb: TComboBox;
+ GoBtn: TButton;
+ Panel1: TBufferPanel;
+ Chromium1: TChromium;
+ AddressPnl: TPanel;
+ Timer1: TTimer;
+
+ procedure Panel1Enter(Sender: TObject);
+ procedure Panel1Exit(Sender: TObject);
+ procedure Panel1GdkKeyPress(Sender: TObject; aEvent: PGdkEventKey; var aHandled: boolean);
+ procedure Panel1GdkKeyRelease(Sender: TObject; aEvent: PGdkEventKey; var aHandled: boolean);
+ procedure Panel1IMECommit(Sender: TObject; const aCommitText: ustring);
+ procedure Panel1IMEPreEditChanged(Sender: TObject; aFlag: cardinal; const aPreEditText: ustring);
+ procedure Panel1IMEPreEditEnd(Sender: TObject);
+ procedure Panel1IMEPreEditStart(Sender: TObject);
+ procedure Panel1MouseDown(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+ procedure Panel1MouseEnter(Sender: TObject);
+ procedure Panel1MouseLeave(Sender: TObject);
+ procedure Panel1MouseMove(Sender: TObject; Shift: TShiftState; X, Y: Integer);
+ procedure Panel1MouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
+ procedure Panel1MouseWheel(Sender: TObject; Shift: TShiftState; WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
+ procedure Panel1Resize(Sender: TObject);
+
+ procedure Chromium1AfterCreated(Sender: TObject; const browser: ICefBrowser);
+ procedure Chromium1BeforeClose(Sender: TObject; const browser: ICefBrowser);
+ procedure Chromium1BeforePopup(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; popup_id: Integer; const targetUrl, targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean; const popupFeatures: TCefPopupFeatures; var windowInfo: TCefWindowInfo; var client: ICefClient; var settings: TCefBrowserSettings; var extra_info: ICefDictionaryValue; var noJavascriptAccess: Boolean; var Result: Boolean);
+ procedure Chromium1CursorChange(Sender: TObject; const browser: ICefBrowser; cursor_: TCefCursorHandle; cursorType: TCefCursorType; const customCursorInfo: PCefCursorInfo; var aResult : boolean);
+ procedure Chromium1DevToolsMethodResult(Sender: TObject; const browser: ICefBrowser; message_id: integer; success: boolean; const result: ICefValue);
+ procedure Chromium1GetScreenInfo(Sender: TObject; const browser: ICefBrowser; var screenInfo: TCefScreenInfo; out Result: Boolean);
+ procedure Chromium1GetScreenPoint(Sender: TObject; const browser: ICefBrowser; viewX, viewY: Integer; var screenX, screenY: Integer; out Result: Boolean);
+ procedure Chromium1GetViewRect(Sender: TObject; const browser: ICefBrowser; var rect: TCefRect);
+ procedure Chromium1OpenUrlFromTab(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; const targetUrl: ustring; targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean; out Result: Boolean);
+ procedure Chromium1Paint(Sender: TObject; const browser: ICefBrowser; type_: TCefPaintElementType; dirtyRectsCount: NativeUInt; const dirtyRects: PCefRectArray; const buffer: Pointer; aWidth, aHeight: Integer);
+ procedure Chromium1PopupShow(Sender: TObject; const browser: ICefBrowser; aShow: Boolean);
+ procedure Chromium1PopupSize(Sender: TObject; const browser: ICefBrowser; const rect: PCefRect);
+ procedure Chromium1SetFocus(Sender: TObject; const browser: ICefBrowser; source: TCefFocusSource; out Result: Boolean);
+ procedure Chromium1Tooltip(Sender: TObject; const browser: ICefBrowser; var aText: ustring; out Result: Boolean);
+ procedure Chromium1ProcessMessageReceived(Sender: TObject; const browser: ICefBrowser; const frame: ICefFrame; sourceProcess: TCefProcessId; const message: ICefProcessMessage; out Result: Boolean);
+
+ procedure FormActivate(Sender: TObject);
+ procedure FormCloseQuery(Sender: TObject; var CanClose: boolean);
+ procedure FormCreate(Sender: TObject);
+ procedure FormDestroy(Sender: TObject);
+ procedure FormHide(Sender: TObject);
+ procedure FormShow(Sender: TObject);
+ procedure FormWindowStateChange(Sender: TObject);
+
+ procedure Application_OnActivate(Sender: TObject);
+ procedure Application_OnDeactivate(Sender: TObject);
+
+ procedure AddressCbEnter(Sender: TObject);
+ procedure GoBtnClick(Sender: TObject);
+ procedure GoBtnEnter(Sender: TObject);
+ procedure Timer1Timer(Sender: TObject);
+
+ protected
+ FPopUpRect : TRect;
+ FShowPopUp : boolean;
+ FResizing : boolean;
+ FPendingResize : boolean;
+ FCanClose : boolean;
+ FClosing : boolean;
+ FResizeCS : TCriticalSection;
+ FBrowserCS : TCriticalSection;
+ FPanelCursor : TCursor;
+ FPanelHint : ustring;
+ FPanelOffset : TPoint;
+ FIsEditing : boolean;
+ FElementBounds : TRect;
+ FDevToolsStatus : TDevToolsStatus;
+ FCheckEditable : boolean;
+ FWasEditing : boolean;
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler : TCEFLinuxOSRIMEHandler;
+ {$ENDIF}
+
+ function GetPanelCursor : TCursor;
+ function GetPanelHint : ustring;
+ function GetIsEditing : boolean;
+
+ procedure SetPanelCursor(aValue : TCursor);
+ procedure SetPanelHint(const aValue : ustring);
+ procedure SetIsEditing(aValue : boolean);
+
+ procedure SendCompMessage(aMsg : cardinal; aData: PtrInt = 0);
+ function getModifiers(Shift: TShiftState): TCefEventFlags;
+ function GetButton(Button: TMouseButton): TCefMouseButtonType;
+ procedure DoResize;
+ procedure UpdatePanelOffset;
+ procedure UpdateElementBounds(const aArgumentList : ICefListValue); overload;
+ procedure UpdateElementBounds(const aRect : TRect); overload;
+ function CopyElementBounds(var aBounds : TRect) : boolean;
+ function SetIMECursorLocation : boolean;
+ function HandleIMEKeyEvent(Event: PGdkEventKey) : boolean;
+ function HandleGettingNodeIdResult(aSuccess : boolean; const aResult: ICefValue) : boolean;
+ function HandleGettingNodeInfoResult(aSuccess : boolean; const aResult: ICefValue) : boolean;
+ function HandleGettingNodeRectResult(aSuccess : boolean; const aResult: ICefValue) : boolean;
+
+ procedure BrowserCreatedMsg(Data: PtrInt);
+ procedure BrowserCloseFormMsg(Data: PtrInt);
+ procedure FocusEnabledMsg(Data: PtrInt);
+ procedure PendingResizeMsg(Data: PtrInt);
+ procedure PendingInvalidateMsg(Data: PtrInt);
+ procedure PendingCursorUpdateMsg(Data: PtrInt);
+ procedure PendingHintUpdateMsg(Data: PtrInt);
+
+ // CEF needs to handle these messages to call TChromium.NotifyMoveOrResizeStarted
+ procedure WMMove(var Message: TLMMove); message LM_MOVE;
+ procedure WMSize(var Message: TLMSize); message LM_SIZE;
+ procedure WMWindowPosChanged(var Message: TLMWindowPosChanged); message LM_WINDOWPOSCHANGED;
+
+ property PanelCursor : TCursor read GetPanelCursor write SetPanelCursor;
+ property PanelHint : ustring read GetPanelHint write SetPanelHint;
+ property IsEditing : boolean read GetIsEditing write SetIsEditing;
+ end;
+
+var
+ MainForm: TMainForm;
+
+procedure CreateGlobalCEFApp;
+
+implementation
+
+{$R *.lfm}
+
+// This is a simple CEF browser in "off-screen rendering" mode (a.k.a OSR mode)
+
+// It uses the default CEF configuration with a multithreaded message loop and
+// that means that the TChromium events are executed in a CEF thread.
+
+// GTK is not thread safe so we have to save all the information given in the
+// TChromium events and use it later in the main application thread. We use
+// critical sections to protect all that information.
+
+// For example, the browser updates the mouse cursor in the
+// TChromium.OnCursorChange event so we have to save the cursor in FPanelCursor
+// and use Application.QueueAsyncCall to update the Panel1.Cursor value in the
+// main application thread.
+
+// The raw bitmap information given in the TChromium.OnPaint event also needs to
+// be stored in a TCEFBitmapBitBuffer class instead of a simple TBitmap to avoid
+// issues with GTK.
+
+// Chromium renders the web contents asynchronously. It uses multiple processes
+// and threads which makes it complicated to keep the correct browser size.
+
+// In one hand you have the main application thread where the form is resized by
+// the user. On the other hand, Chromium renders the contents asynchronously
+// with the last browser size available, which may have changed by the time
+// Chromium renders the page.
+
+// For this reason we need to keep checking the real size and call
+// TChromium.WasResized when we detect that Chromium has an incorrect size.
+
+// TChromium.WasResized triggers the TChromium.OnGetViewRect event to let CEF
+// read the current browser size and then it triggers TChromium.OnPaint when the
+// contents are finally rendered.
+
+// TChromium.WasResized --> (time passes) --> TChromium.OnGetViewRect --> (time passes) --> TChromium.OnPaint
+
+// You have to assume that the real browser size can change between those calls
+// and events.
+
+// This demo uses a couple of fields called "FResizing" and "FPendingResize" to
+// reduce the number of TChromium.WasResized calls.
+
+// FResizing is set to True before the TChromium.WasResized call and it's set to
+// False at the end of the TChromium.OnPaint event.
+
+// FPendingResize is set to True when the browser changed its size while
+// FResizing was True. The FPendingResize value is checked at the end of
+// TChromium.OnPaint to check the browser size again because it changed while
+// Chromium was rendering the page.
+
+// The TChromium.OnPaint event in the demo also calls
+// TBufferPanel.UpdateOrigBufferDimensions and TBufferPanel.BufferIsResized to check
+// the width and height of the buffer parameter, and the internal buffer size in
+// the TBufferPanel component.
+
+// Lazarus usually initializes the GTK WidgetSet in the initialization section
+// of the "Interfaces" unit which is included in the LPR file. This causes
+// initialization problems in CEF and we need to call "CreateWidgetset" after
+// the GlobalCEFApp.StartMainProcess call.
+
+// Lazarus shows a warning if we remove the "Interfaces" unit from the LPR file
+// so we created a custom unit with the same name that includes two procedures
+// to initialize and finalize the WidgetSet at the right time.
+
+// This is the destruction sequence in OSR mode :
+// 1- FormCloseQuery sets CanClose to the initial FCanClose value (False) and
+// calls Chromium1.CloseBrowser(True) which will destroy the internal browser
+// immediately.
+// 2- Chromium1.OnBeforeClose is triggered because the internal browser was
+// destroyed. FCanClose is set to True and calls
+// SendCompMessage(CEF_BEFORECLOSE) to close the form asynchronously.
+
+uses
+ Math, mouseandkeyinput, gtk3procs,
+ uCEFMiscFunctions, uCEFApplication, uCEFBitmapBitBuffer, uCEFLinuxFunctions,
+ uCEFDictionaryValue, uCEFJson{$IFDEF CEF_USE_IME}, uCEFProcessMessage{$ENDIF};
+
+const
+ CEF_UPDATE_CURSOR = $A1D;
+ CEF_UPDATE_HINT = $A1E;
+ {$IFDEF CEF_USE_IME}
+ EDITABLE_MSGNAME = 'editable';
+ NONEDITABLE_MSGNAME = 'noneditable';
+ {$ENDIF}
+
+{GlobalCEFApp functions}
+{%Region}
+{$IFDEF CEF_USE_IME}
+procedure GlobalCEFApp_OnFocusedNodeChanged(const browser : ICefBrowser;
+ const frame : ICefFrame;
+ const node : ICefDomNode);
+var
+ TempMsg : ICefProcessMessage;
+begin
+ if (frame <> nil) and frame.IsValid then
+ try
+ if (node <> nil) and node.IsEditable then
+ begin
+ TempMsg := TCefProcessMessageRef.New(EDITABLE_MSGNAME);
+ TempMsg.ArgumentList.SetSize(4);
+ TempMsg.ArgumentList.SetInt(0, node.ElementBounds.x);
+ TempMsg.ArgumentList.SetInt(1, node.ElementBounds.y);
+ TempMsg.ArgumentList.SetInt(2, node.ElementBounds.width);
+ TempMsg.ArgumentList.SetInt(3, node.ElementBounds.height);
+ end
+ else
+ TempMsg := TCefProcessMessageRef.New(NONEDITABLE_MSGNAME);
+
+
+ frame.SendProcessMessage(PID_BROWSER, TempMsg);
+ finally
+ TempMsg := nil;
+ end;
+end;
+{$ENDIF}
+
+procedure CreateGlobalCEFApp;
+begin
+ GlobalCEFApp := TCefApplication.Create;
+ GlobalCEFApp.WindowlessRenderingEnabled := True;
+ GlobalCEFApp.BackgroundColor := CefColorSetARGB($FF, $FF, $FF, $FF);
+ GlobalCEFApp.LogFile := 'debug.log';
+ GlobalCEFApp.LogSeverity := LOGSEVERITY_INFO;
+ {$IFDEF CEF_USE_IME}
+ GlobalCEFApp.OnFocusedNodeChanged := @GlobalCEFApp_OnFocusedNodeChanged;
+ {$ENDIF}
+end;
+{%Endregion}
+
+{TBufferPanel events}
+{%Region}
+procedure TMainForm.Panel1Enter(Sender: TObject);
+begin
+ IsEditing := FWasEditing;
+ FCheckEditable := True;
+ Chromium1.SetFocus(True);
+end;
+
+procedure TMainForm.Panel1Exit(Sender: TObject);
+begin
+ FWasEditing := IsEditing;
+ Chromium1.SetFocus(False);
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler.Blur;
+ {$ENDIF}
+end;
+
+procedure TMainForm.Panel1GdkKeyPress(Sender: TObject; aEvent: PGdkEventKey;
+ var aHandled: boolean);
+var
+ TempCefEvent : TCefKeyEvent;
+begin
+ aHandled := True;
+
+ if not(HandleIMEKeyEvent(aEvent)) then
+ begin
+ GdkEventKeyToCEFKeyEvent(aEvent, TempCefEvent);
+
+ TempCefEvent.kind := KEYEVENT_RAWKEYDOWN;
+ Chromium1.SendKeyEvent(@TempCefEvent);
+ TempCefEvent.kind := KEYEVENT_CHAR;
+ Chromium1.SendKeyEvent(@TempCefEvent);
+ end;
+end;
+
+procedure TMainForm.Panel1GdkKeyRelease(Sender: TObject; aEvent: PGdkEventKey;
+ var aHandled: boolean);
+var
+ TempCefEvent : TCefKeyEvent;
+begin
+ aHandled := True;
+
+ if not(HandleIMEKeyEvent(aEvent)) then
+ begin
+ GdkEventKeyToCEFKeyEvent(aEvent, TempCefEvent);
+
+ TempCefEvent.kind := KEYEVENT_KEYUP;
+ Chromium1.SendKeyEvent(@TempCefEvent);
+ end;
+end;
+
+procedure TMainForm.Panel1IMECommit(Sender: TObject; const aCommitText: ustring);
+{$IFDEF CEF_USE_IME}
+const
+ UINT32_MAX = high(cardinal);
+var
+ replacement_range : TCefRange;
+{$ENDIF}
+begin
+ {$IFDEF CEF_USE_IME}
+ replacement_range.from := UINT32_MAX;
+ replacement_range.to_ := UINT32_MAX;
+
+ Chromium1.IMECommitText(aCommitText, @replacement_range, 0);
+ {$ENDIF}
+end;
+
+procedure TMainForm.Panel1IMEPreEditChanged(Sender: TObject; aFlag: cardinal;
+ const aPreEditText: ustring);
+begin
+ {$IFDEF CEF_USE_IME}
+ SetIMECursorLocation;
+ {$ENDIF}
+end;
+
+procedure TMainForm.Panel1IMEPreEditEnd(Sender: TObject);
+begin
+ {$IFDEF CEF_USE_IME}
+ Chromium1.IMECancelComposition;
+ {$ENDIF}
+end;
+
+procedure TMainForm.Panel1IMEPreEditStart(Sender: TObject);
+begin
+ {$IFDEF CEF_USE_IME}
+ SetIMECursorLocation;
+ {$ENDIF}
+end;
+
+procedure TMainForm.Panel1MouseDown(Sender: TObject; Button: TMouseButton;
+ Shift: TShiftState; X, Y: Integer);
+var
+ TempEvent : TCefMouseEvent;
+begin
+ Panel1.SetFocus;
+
+ TempEvent.x := X;
+ TempEvent.y := Y;
+ TempEvent.modifiers := getModifiers(Shift);
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseClickEvent(@TempEvent, GetButton(Button), False, 1);
+end;
+
+procedure TMainForm.Panel1MouseEnter(Sender: TObject);
+var
+ TempEvent : TCefMouseEvent;
+ TempPoint : TPoint;
+begin
+ TempPoint := Panel1.ScreenToClient(mouse.CursorPos);
+ TempEvent.x := TempPoint.x;
+ TempEvent.y := TempPoint.y;
+ TempEvent.modifiers := EVENTFLAG_NONE;
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseMoveEvent(@TempEvent, False);
+end;
+
+procedure TMainForm.Panel1MouseLeave(Sender: TObject);
+var
+ TempEvent : TCefMouseEvent;
+ TempPoint : TPoint;
+begin
+ TempPoint := Panel1.ScreenToClient(mouse.CursorPos);
+ TempEvent.x := TempPoint.x;
+ TempEvent.y := TempPoint.y;
+ TempEvent.modifiers := EVENTFLAG_NONE;
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseMoveEvent(@TempEvent, True);
+end;
+
+procedure TMainForm.Panel1MouseMove(Sender: TObject; Shift: TShiftState; X,
+ Y: Integer);
+var
+ TempEvent : TCefMouseEvent;
+begin
+ TempEvent.x := x;
+ TempEvent.y := y;
+ TempEvent.modifiers := getModifiers(Shift);
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseMoveEvent(@TempEvent, False);
+end;
+
+procedure TMainForm.Panel1MouseUp(Sender: TObject; Button: TMouseButton;
+ Shift: TShiftState; X, Y: Integer);
+var
+ TempEvent : TCefMouseEvent;
+ TempParams : ICefDictionaryValue;
+begin
+ TempEvent.x := X;
+ TempEvent.y := Y;
+ TempEvent.modifiers := getModifiers(Shift);
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseClickEvent(@TempEvent, GetButton(Button), True, 1);
+
+ // GlobalCEFApp.OnFocusedNodeChanged is not triggered the first time the
+ // browser gets the focus. We need to call a series of DevTool methods to
+ // check if the HTML element under the mouse is editable to save its position
+ // and size. This information will be used to show the IME.
+ if FCheckEditable then
+ try
+ FCheckEditable := False;
+ FDevToolsStatus := dtsGettingNodeId;
+
+ TempParams := TCefDictionaryValueRef.New;
+ TempParams.SetInt('x', DeviceToLogical(X, Panel1.ScreenScale));
+ TempParams.SetInt('y', DeviceToLogical(Y, Panel1.ScreenScale));
+
+ Chromium1.ExecuteDevToolsMethod(0, 'DOM.getNodeForLocation', TempParams);
+ finally
+ TempParams := nil;
+ end;
+end;
+
+procedure TMainForm.Panel1MouseWheel(Sender: TObject; Shift: TShiftState;
+ WheelDelta: Integer; MousePos: TPoint; var Handled: Boolean);
+var
+ TempEvent : TCefMouseEvent;
+begin
+ TempEvent.x := MousePos.x;
+ TempEvent.y := MousePos.y;
+ TempEvent.modifiers := getModifiers(Shift);
+ DeviceToLogical(TempEvent, Panel1.ScreenScale);
+ Chromium1.SendMouseWheelEvent(@TempEvent, 0, WheelDelta);
+end;
+
+procedure TMainForm.Panel1Resize(Sender: TObject);
+begin
+ DoResize;
+end;
+{%Endregion}
+
+{TChromium events}
+{%Region}
+procedure TMainForm.Chromium1AfterCreated(Sender: TObject; const browser: ICefBrowser);
+begin
+ // Now the browser is fully initialized we can initialize the UI.
+ SendCompMessage(CEF_AFTERCREATED);
+end;
+
+procedure TMainForm.Chromium1BeforeClose(Sender: TObject;
+ const browser: ICefBrowser);
+begin
+ FCanClose := True;
+ SendCompMessage(CEF_BEFORECLOSE);
+end;
+
+procedure TMainForm.Chromium1BeforePopup(Sender: TObject;
+ const browser: ICefBrowser; const frame: ICefFrame; popup_id: Integer;
+ const targetUrl, targetFrameName: ustring; targetDisposition: TCefWindowOpenDisposition;
+ userGesture: Boolean; const popupFeatures: TCefPopupFeatures;
+ var windowInfo: TCefWindowInfo; var client: ICefClient;
+ var settings: TCefBrowserSettings; var extra_info: ICefDictionaryValue;
+ var noJavascriptAccess: Boolean; var Result: Boolean);
+begin
+ // For simplicity, this demo blocks all popup windows and new tabs
+ Result := (targetDisposition in [CEF_WOD_NEW_FOREGROUND_TAB, CEF_WOD_NEW_BACKGROUND_TAB, CEF_WOD_NEW_POPUP, CEF_WOD_NEW_WINDOW]);
+end;
+
+procedure TMainForm.Chromium1CursorChange(Sender: TObject;
+ const browser: ICefBrowser; cursor_: TCefCursorHandle;
+ cursorType: TCefCursorType; const customCursorInfo: PCefCursorInfo;
+ var aResult : boolean);
+begin
+ PanelCursor := CefCursorToWindowsCursor(cursorType);
+ aResult := True;
+
+ SendCompMessage(CEF_UPDATE_CURSOR);
+end;
+
+procedure TMainForm.Chromium1DevToolsMethodResult(Sender: TObject;
+ const browser: ICefBrowser; message_id: integer; success: boolean;
+ const result: ICefValue);
+begin
+ case FDevToolsStatus of
+ dtsGettingNodeId : HandleGettingNodeIdResult(success, result);
+ dtsGettingNodeInfo : HandleGettingNodeInfoResult(success, result);
+ dtsGettingNodeRect : HandleGettingNodeRectResult(success, result);
+ end;
+end;
+
+procedure TMainForm.Chromium1GetScreenInfo(Sender: TObject;
+ const browser: ICefBrowser; var screenInfo: TCefScreenInfo; out
+ Result: Boolean);
+var
+ TempRect : TCEFRect;
+ TempScale : single;
+begin
+ TempScale := Panel1.ScreenScale;
+ TempRect.x := 0;
+ TempRect.y := 0;
+ TempRect.width := DeviceToLogical(Panel1.Width, TempScale);
+ TempRect.height := DeviceToLogical(Panel1.Height, TempScale);
+
+ screenInfo.device_scale_factor := TempScale;
+ screenInfo.depth := 0;
+ screenInfo.depth_per_component := 0;
+ screenInfo.is_monochrome := Ord(False);
+ screenInfo.rect := TempRect;
+ screenInfo.available_rect := TempRect;
+
+ Result := True;
+end;
+
+procedure TMainForm.Chromium1GetScreenPoint(Sender: TObject;
+ const browser: ICefBrowser; viewX, viewY: Integer; var screenX,
+ screenY: Integer; out Result: Boolean);
+begin
+ try
+ FBrowserCS.Acquire;
+ screenX := LogicalToDevice(viewX, Panel1.ScreenScale) + FPanelOffset.x;
+ screenY := LogicalToDevice(viewY, Panel1.ScreenScale) + FPanelOffset.y;
+ Result := True;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TMainForm.Chromium1GetViewRect(Sender: TObject;
+ const browser: ICefBrowser; var rect: TCefRect);
+var
+ TempScale : single;
+begin
+ TempScale := Panel1.ScreenScale;
+ rect.x := 0;
+ rect.y := 0;
+ rect.width := DeviceToLogical(Panel1.Width, TempScale);
+ rect.height := DeviceToLogical(Panel1.Height, TempScale);
+end;
+
+procedure TMainForm.Chromium1OpenUrlFromTab(Sender: TObject;
+ const browser: ICefBrowser; const frame: ICefFrame; const targetUrl: ustring;
+ targetDisposition: TCefWindowOpenDisposition; userGesture: Boolean; out
+ Result: Boolean);
+begin
+ // For simplicity, this demo blocks all popup windows and new tabs
+ Result := (targetDisposition in [CEF_WOD_NEW_FOREGROUND_TAB, CEF_WOD_NEW_BACKGROUND_TAB, CEF_WOD_NEW_POPUP, CEF_WOD_NEW_WINDOW]);
+end;
+
+procedure TMainForm.Chromium1Paint(Sender: TObject; const browser: ICefBrowser;
+ type_: TCefPaintElementType; dirtyRectsCount: NativeUInt;
+ const dirtyRects: PCefRectArray; const buffer: Pointer; aWidth, aHeight: Integer
+ );
+var
+ src, dst: PByte;
+ i, j, TempLineSize, TempSrcOffset, TempDstOffset, SrcStride : Integer;
+ n : NativeUInt;
+ TempWidth, TempHeight : integer;
+ TempBufferBits : Pointer;
+ TempForcedResize : boolean;
+ TempBitmap : TCEFBitmapBitBuffer;
+ TempSrcRect : TRect;
+begin
+ try
+ FResizeCS.Acquire;
+ TempForcedResize := False;
+
+ if Panel1.BeginBufferDraw then
+ begin
+ if (type_ = PET_POPUP) then
+ begin
+ Panel1.UpdateOrigPopupBufferDimensions(aWidth, aHeight);
+
+ TempBitmap := Panel1.OrigPopupBuffer;
+ TempWidth := Panel1.OrigPopupBufferWidth;
+ TempHeight := Panel1.OrigPopupBufferHeight;
+ end
+ else
+ begin
+ TempForcedResize := Panel1.UpdateOrigBufferDimensions(aWidth, aHeight) or
+ not(Panel1.BufferIsResized(False));
+
+ TempBitmap := Panel1.OrigBuffer;
+ TempWidth := Panel1.OrigBufferWidth;
+ TempHeight := Panel1.OrigBufferHeight;
+ end;
+
+ SrcStride := aWidth * SizeOf(TRGBQuad);
+ n := 0;
+
+ while (n < dirtyRectsCount) do
+ begin
+ if (dirtyRects^[n].x >= 0) and (dirtyRects^[n].y >= 0) then
+ begin
+ TempLineSize := min(dirtyRects^[n].width, TempWidth - dirtyRects^[n].x) * SizeOf(TRGBQuad);
+
+ if (TempLineSize > 0) then
+ begin
+ TempSrcOffset := ((dirtyRects^[n].y * aWidth) + dirtyRects^[n].x) * SizeOf(TRGBQuad);
+ TempDstOffset := (dirtyRects^[n].x * SizeOf(TRGBQuad));
+
+ src := @PByte(buffer)[TempSrcOffset];
+
+ i := 0;
+ j := min(dirtyRects^[n].height, TempHeight - dirtyRects^[n].y);
+
+ while (i < j) do
+ begin
+ TempBufferBits := TempBitmap.Scanline[dirtyRects^[n].y + i];
+ dst := @PByte(TempBufferBits)[TempDstOffset];
+
+ Move(src^, dst^, TempLineSize);
+
+ Inc(src, SrcStride);
+ inc(i);
+ end;
+ end;
+ end;
+
+ inc(n);
+ end;
+
+ if FShowPopup then
+ begin
+ TempSrcRect := Rect(0, 0,
+ FPopUpRect.Right - FPopUpRect.Left,
+ FPopUpRect.Bottom - FPopUpRect.Top);
+
+ Panel1.DrawOrigPopupBuffer(TempSrcRect, FPopUpRect);
+ end;
+
+ Panel1.EndBufferDraw;
+
+ SendCompMessage(CEF_PENDINGINVALIDATE);
+
+ if (type_ = PET_VIEW) then
+ begin
+ if TempForcedResize or FPendingResize then
+ SendCompMessage(CEF_PENDINGRESIZE);
+
+ FResizing := False;
+ FPendingResize := False;
+ end;
+ end;
+ finally
+ FResizeCS.Release;
+ end;
+end;
+
+procedure TMainForm.Chromium1PopupShow(Sender: TObject; const browser: ICefBrowser; aShow: Boolean);
+begin
+ if aShow then
+ FShowPopUp := True
+ else
+ begin
+ FShowPopUp := False;
+ FPopUpRect := rect(0, 0, 0, 0);
+
+ if (Chromium1 <> nil) then Chromium1.Invalidate(PET_VIEW);
+ end;
+end;
+
+procedure TMainForm.Chromium1PopupSize(Sender: TObject; const browser: ICefBrowser; const rect: PCefRect);
+begin
+ LogicalToDevice(rect^, Panel1.ScreenScale);
+
+ FPopUpRect.Left := rect^.x;
+ FPopUpRect.Top := rect^.y;
+ FPopUpRect.Right := rect^.x + rect^.width - 1;
+ FPopUpRect.Bottom := rect^.y + rect^.height - 1;
+end;
+
+procedure TMainForm.Chromium1ProcessMessageReceived(Sender: TObject;
+ const browser: ICefBrowser; const frame: ICefFrame;
+ sourceProcess: TCefProcessId; const message: ICefProcessMessage; out
+ Result: Boolean);
+begin
+ Result := False;
+ {$IFDEF CEF_USE_IME}
+ if (message = nil) then exit;
+
+ if (message.Name = EDITABLE_MSGNAME) then
+ UpdateElementBounds(message.ArgumentList)
+ else
+ IsEditing := False;
+
+ Result := True;
+ {$ENDIF}
+end;
+
+procedure TMainForm.Chromium1SetFocus(Sender: TObject; const browser: ICefBrowser;
+ source: TCefFocusSource; out Result: Boolean);
+begin
+ Result := not(Panel1.Focused);
+end;
+
+procedure TMainForm.Chromium1Tooltip(Sender: TObject; const browser: ICefBrowser; var aText: ustring; out Result: Boolean);
+begin
+ PanelHint := aText;
+ Result := True;
+
+ SendCompMessage(CEF_UPDATE_HINT);
+end;
+{%Endregion}
+
+{TForm events}
+{%Region}
+procedure TMainForm.FormActivate(Sender: TObject);
+begin
+ // This will trigger the AfterCreated event when the browser is fully
+ // initialized and ready to receive commands.
+
+ // GlobalCEFApp.GlobalContextInitialized has to be TRUE before creating any browser
+ // If it's not initialized yet, we use a simple timer to create the browser later.
+
+ // Linux needs a visible form to create a browser so we need to use the
+ // TForm.OnActivate event instead of the TForm.OnShow event
+
+ if not(Chromium1.Initialized) then
+ begin
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler.CreateContext;
+ {$ENDIF}
+
+ // We have to update the DeviceScaleFactor here to get the scale of the
+ // monitor where the main application form is located.
+ GlobalCEFApp.UpdateDeviceScaleFactor;
+ Panel1.UpdateDeviceScaleFactor;
+ Panel1.ConnectSignals;
+ UpdatePanelOffset;
+
+ // opaque white background color
+ Chromium1.Options.BackgroundColor := CefColorSetARGB($FF, $FF, $FF, $FF);
+ Chromium1.DefaultURL := UTF8Decode(AddressCb.Text);
+
+ if not(Chromium1.CreateBrowser) then Timer1.Enabled := True;
+ end;
+end;
+
+procedure TMainForm.FormCloseQuery(Sender: TObject; var CanClose: boolean);
+begin
+ CanClose := FCanClose;
+
+ if not(FClosing) then
+ begin
+ FClosing := True;
+ Visible := False;
+ Chromium1.CloseBrowser(True);
+ end;
+end;
+
+procedure TMainForm.FormCreate(Sender: TObject);
+begin
+ FCheckEditable := True;
+ FWasEditing := False;
+ FDevToolsStatus := dtsIdle;
+ FPopUpRect := rect(0, 0, 0, 0);
+ FShowPopUp := False;
+ FResizing := False;
+ FPendingResize := False;
+ FCanClose := False;
+ FClosing := False;
+ FResizeCS := TCriticalSection.Create;
+ FBrowserCS := TCriticalSection.Create;
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler := TCEFLinuxOSRIMEHandler.Create(Panel1);
+ {$ENDIF}
+ IsEditing := False;
+
+ Panel1.CopyOriginalBuffer := True;
+
+ Application.OnActivate := @Application_OnActivate;
+ Application.OnDeactivate := @Application_OnDeactivate;
+end;
+
+procedure TMainForm.FormDestroy(Sender: TObject);
+begin
+ if (FResizeCS <> nil) then FreeAndNil(FResizeCS);
+ if (FBrowserCS <> nil) then FreeAndNil(FBrowserCS);
+ {$IFDEF CEF_USE_IME}
+ if (FIMEHandler <> nil) then FreeAndNil(FIMEHandler);
+ {$ENDIF}
+end;
+
+procedure TMainForm.FormHide(Sender: TObject);
+begin
+ Chromium1.SetFocus(False);
+ Chromium1.WasHidden(True);
+end;
+
+procedure TMainForm.FormShow(Sender: TObject);
+begin
+ Chromium1.WasHidden(False);
+ Chromium1.SetFocus(Panel1.Focused);
+end;
+
+procedure TMainForm.FormWindowStateChange(Sender: TObject);
+begin
+ if (WindowState = wsMinimized) then
+ begin
+ Chromium1.SetFocus(False);
+ Chromium1.WasHidden(True);
+ end
+ else
+ begin
+ Chromium1.WasHidden(False);
+ Chromium1.SetFocus(Panel1.Focused);
+ end;
+end;
+{%Endregion}
+
+{TApplication events}
+{%Region}
+procedure TMainForm.Application_OnActivate(Sender: TObject);
+begin
+ IsEditing := FWasEditing;
+ FCheckEditable := True;
+ Chromium1.SetFocus(Panel1.Focused);
+end;
+
+procedure TMainForm.Application_OnDeactivate(Sender: TObject);
+begin
+ FWasEditing := IsEditing;
+ Chromium1.SetFocus(False);
+ {$IFDEF CEF_USE_IME}
+ FIMEHandler.Blur;
+ {$ENDIF}
+end;
+{%Endregion}
+
+{Other events}
+{%Region}
+procedure TMainForm.AddressCbEnter(Sender: TObject);
+begin
+ Chromium1.SetFocus(False);
+end;
+
+procedure TMainForm.GoBtnClick(Sender: TObject);
+begin
+ FResizeCS.Acquire;
+ FResizing := False;
+ FPendingResize := False;
+ FResizeCS.Release;
+
+ Chromium1.LoadURL(UTF8Decode(AddressCb.Text))
+end;
+
+procedure TMainForm.GoBtnEnter(Sender: TObject);
+begin
+ Chromium1.SetFocus(False);
+end;
+
+procedure TMainForm.Timer1Timer(Sender: TObject);
+begin
+ Timer1.Enabled := False;
+
+ if not(Chromium1.CreateBrowser) and not(Chromium1.Initialized) then
+ Timer1.Enabled := True;
+end;
+{%Endregion}
+
+{Getters and setters}
+{%Region}
+function TMainForm.GetPanelCursor : TCursor;
+begin
+ try
+ FBrowserCS.Acquire;
+ Result := FPanelCursor;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+function TMainForm.GetPanelHint : ustring;
+begin
+ try
+ FBrowserCS.Acquire;
+ Result := FPanelHint;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+function TMainForm.GetIsEditing : boolean;
+begin
+ try
+ FBrowserCS.Acquire;
+ Result := FIsEditing;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TMainForm.SetPanelCursor(aValue : TCursor);
+begin
+ try
+ FBrowserCS.Acquire;
+ FPanelCursor := aValue;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TMainForm.SetPanelHint(const aValue : ustring);
+begin
+ try
+ FBrowserCS.Acquire;
+ FPanelHint := aValue;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TMainForm.SetIsEditing(aValue : boolean);
+begin
+ try
+ FBrowserCS.Acquire;
+
+ if aValue then
+ FIsEditing := True
+ else
+ begin
+ FIsEditing := False;
+ FElementBounds := rect(0, 0, 0, 0);
+ end;
+
+ SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+{%Endregion}
+
+{Misc functions}
+{%Region}
+procedure TMainForm.SendCompMessage(aMsg : cardinal; aData: PtrInt);
+begin
+ case aMsg of
+ CEF_AFTERCREATED : Application.QueueAsyncCall(@BrowserCreatedMsg, aData);
+ CEF_BEFORECLOSE : Application.QueueAsyncCall(@BrowserCloseFormMsg, aData);
+ CEF_PENDINGRESIZE : Application.QueueAsyncCall(@PendingResizeMsg, aData);
+ CEF_PENDINGINVALIDATE : Application.QueueAsyncCall(@PendingInvalidateMsg, aData);
+ CEF_UPDATE_CURSOR : Application.QueueAsyncCall(@PendingCursorUpdateMsg, aData);
+ CEF_UPDATE_HINT : Application.QueueAsyncCall(@PendingHintUpdateMsg, aData);
+ CEF_FOCUSENABLED : Application.QueueAsyncCall(@FocusEnabledMsg, aData);
+ end;
+end;
+
+function TMainForm.getModifiers(Shift: TShiftState): TCefEventFlags;
+begin
+ Result := EVENTFLAG_NONE;
+
+ if (ssShift in Shift) then Result := Result or EVENTFLAG_SHIFT_DOWN;
+ if (ssAlt in Shift) then Result := Result or EVENTFLAG_ALT_DOWN;
+ if (ssCtrl in Shift) then Result := Result or EVENTFLAG_CONTROL_DOWN;
+ if (ssLeft in Shift) then Result := Result or EVENTFLAG_LEFT_MOUSE_BUTTON;
+ if (ssRight in Shift) then Result := Result or EVENTFLAG_RIGHT_MOUSE_BUTTON;
+ if (ssMiddle in Shift) then Result := Result or EVENTFLAG_MIDDLE_MOUSE_BUTTON;
+end;
+
+function TMainForm.GetButton(Button: TMouseButton): TCefMouseButtonType;
+begin
+ case Button of
+ TMouseButton.mbRight : Result := MBT_RIGHT;
+ TMouseButton.mbMiddle : Result := MBT_MIDDLE;
+ else Result := MBT_LEFT;
+ end;
+end;
+
+procedure TMainForm.DoResize;
+begin
+ try
+ FResizeCS.Acquire;
+
+ if FResizing then
+ FPendingResize := True
+ else
+ if Panel1.BufferIsResized then
+ Chromium1.Invalidate(PET_VIEW)
+ else
+ begin
+ FResizing := True;
+ Chromium1.WasResized;
+ end;
+ finally
+ FResizeCS.Release;
+ end;
+end;
+
+procedure TMainForm.UpdatePanelOffset;
+var
+ TempPoint : TPoint;
+begin
+ try
+ FBrowserCS.Acquire;
+ TempPoint.x := 0;
+ TempPoint.y := 0;
+ FPanelOffset := Panel1.ClientToScreen(TempPoint);
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TMainForm.UpdateElementBounds(const aArgumentList : ICefListValue);
+begin
+ try
+ FBrowserCS.Acquire;
+
+ if assigned(aArgumentList) and (aArgumentList.GetSize = 4) then
+ begin
+ FIsEditing := True;
+ FElementBounds.Left := aArgumentList.GetInt(0);
+ FElementBounds.Top := aArgumentList.GetInt(1);
+ FElementBounds.Right := FElementBounds.Left + aArgumentList.GetInt(2) - 1;
+ FElementBounds.Bottom := FElementBounds.Top + aArgumentList.GetInt(3) - 1;
+ end
+ else
+ begin
+ FIsEditing := False;
+ FElementBounds := rect(0, 0, 0, 0);
+ end;
+
+ SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+procedure TMainForm.UpdateElementBounds(const aRect : TRect);
+begin
+ try
+ FBrowserCS.Acquire;
+ FIsEditing := True;
+ FElementBounds := aRect;
+ SendCompMessage(CEF_FOCUSENABLED, ord(FIsEditing));
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+function TMainForm.CopyElementBounds(var aBounds : TRect) : boolean;
+begin
+ Result := False;
+ aBounds := rect(0, 0, 0, 0);
+
+ try
+ FBrowserCS.Acquire;
+
+ if FIsEditing then
+ begin
+ aBounds := FElementBounds;
+ Result := True;
+ end;
+ finally
+ FBrowserCS.Release;
+ end;
+end;
+
+function TMainForm.SetIMECursorLocation : boolean;
+{$IFDEF CEF_USE_IME}
+var
+ TempBounds : TRect;
+ TempPoint : TPoint;
+ TempScale : single;
+{$ENDIF}
+begin
+ Result := False;
+ {$IFDEF CEF_USE_IME}
+ if CopyElementBounds(TempBounds) then
+ begin
+ TempScale := Panel1.ScreenScale;
+ TempPoint.x := DeviceToLogical(TempBounds.Left, TempScale);
+ TempPoint.y := DeviceToLogical(integer(AddressPnl.Height + TempBounds.Bottom), TempScale);
+
+ FIMEHandler.SetCursorLocation(TempPoint.x, TempPoint.y);
+ Result := True;
+ end;
+ {$ENDIF}
+end;
+function TMainForm.HandleIMEKeyEvent(Event: PGdkEventKey) : boolean;
+begin
+ Result := False;
+ {$IFDEF CEF_USE_IME}
+ if SetIMECursorLocation then
+ Result := FIMEHandler.FilterKeyPress(Event);
+ {$ENDIF}
+end;
+
+function TMainForm.HandleGettingNodeIdResult(aSuccess : boolean; const aResult: ICefValue) : boolean;
+var
+ TempParams, TempRsltDict : ICefDictionaryValue;
+ TempNodeID : integer;
+begin
+ Result := False;
+
+ if aSuccess and (aResult <> nil) then
+ try
+ TempRsltDict := aResult.GetDictionary;
+
+ if TCEFJson.ReadInteger(TempRsltDict, 'backendNodeId', TempNodeID) then
+ begin
+ FDevToolsStatus := dtsGettingNodeInfo;
+
+ TempParams := TCefDictionaryValueRef.New;
+ TempParams.SetInt('backendNodeId', TempNodeID);
+
+ Chromium1.ExecuteDevToolsMethod(0, 'DOM.describeNode', TempParams);
+ Result := True;
+ end
+ else
+ FDevToolsStatus := dtsIdle;
+ finally
+ TempParams := nil;
+ end;
+end;
+
+function TMainForm.HandleGettingNodeInfoResult(aSuccess : boolean; const aResult: ICefValue) : boolean;
+var
+ TempParams, TempRsltDict, TempNode : ICefDictionaryValue;
+ TempAttribs : ICefListValue;
+ TempName : ustring;
+ TempNodeID : integer;
+
+ function HasDisabledAttrib : boolean;
+ var
+ i : NativeUInt;
+ begin
+ Result := False;
+
+ i := 0;
+ while (i < TempAttribs.GetSize) do
+ if (CompareText(TempAttribs.GetString(i), 'disabled') = 0) then
+ begin
+ Result := True;
+ exit;
+ end
+ else
+ inc(i, 2);
+ end;
+
+ function IsTextAreaNode : boolean;
+ begin
+ Result := (CompareText(TempName, 'textarea') = 0);
+ end;
+
+ function IsInputNode : boolean;
+ var
+ TempType : string;
+ i : NativeUInt;
+ begin
+ Result := False;
+
+ if (CompareText(TempName, 'input') = 0) then
+ begin
+ if assigned(TempAttribs) then
+ begin
+ i := 0;
+ while (i < TempAttribs.GetSize) do
+ if (CompareText(TempAttribs.GetString(i), 'type') = 0) then
+ begin
+ if (i + 1 < TempAttribs.GetSize) then
+ begin
+ TempType := TempAttribs.GetString(i + 1);
+
+ if (CompareText(TempType, 'text') = 0) or
+ (CompareText(TempType, 'email') = 0) or
+ (CompareText(TempType, 'month') = 0) or
+ (CompareText(TempType, 'password') = 0) or
+ (CompareText(TempType, 'search') = 0) or
+ (CompareText(TempType, 'tel') = 0) or
+ (CompareText(TempType, 'url') = 0) or
+ (CompareText(TempType, 'week') = 0) or
+ (CompareText(TempType, 'datetime') = 0) then
+ begin
+ Result := True;
+ exit;
+ end
+ else
+ exit;
+ end
+ else
+ exit;
+ end
+ else
+ inc(i, 2);
+
+ Result := True; // Default input type is text
+ end
+ else
+ Result := True; // Default input type is text
+ end;
+ end;
+begin
+ Result := False;
+
+ if aSuccess and (aResult <> nil) then
+ try
+ TempRsltDict := aResult.GetDictionary;
+
+ if TCEFJson.ReadDictionary(TempRsltDict, 'node', TempNode) and
+ TCEFJson.ReadString(TempNode, 'nodeName', TempName) and
+ TCEFJson.ReadInteger(TempNode, 'backendNodeId', TempNodeID) and
+ TCEFJson.ReadList(TempNode, 'attributes', TempAttribs) and
+ not(HasDisabledAttrib) and
+ (IsTextAreaNode or IsInputNode) then
+ begin
+ FDevToolsStatus := dtsGettingNodeRect;
+
+ TempParams := TCefDictionaryValueRef.New;
+ TempParams.SetInt('backendNodeId', TempNodeID);
+
+ Chromium1.ExecuteDevToolsMethod(0, 'DOM.getContentQuads', TempParams);
+ Result := True;
+ end
+ else
+ FDevToolsStatus := dtsIdle;
+ finally
+ TempParams := nil;
+ end;
+end;
+
+function TMainForm.HandleGettingNodeRectResult(aSuccess : boolean; const aResult: ICefValue) : boolean;
+var
+ TempRsltDict : ICefDictionaryValue;
+ TempList, TempQuads : ICefListValue;
+ TempRect : TRect;
+begin
+ Result := False;
+
+ if aSuccess and (aResult <> nil) then
+ begin
+ TempRsltDict := aResult.GetDictionary;
+
+ if TCEFJson.ReadList(TempRsltDict, 'quads', TempQuads) and
+ (TempQuads.GetSize = 1) then
+ begin
+ TempList := TempQuads.GetList(0);
+
+ if (TempList.GetSize = 8) then
+ begin
+ case TempList.GetType(0) of
+ VTYPE_INT : TempRect.Left := TempList.GetInt(0);
+ VTYPE_DOUBLE : TempRect.Left := round(TempList.GetDouble(0));
+ end;
+
+ case TempList.GetType(1) of
+ VTYPE_INT : TempRect.Top := TempList.GetInt(1);
+ VTYPE_DOUBLE : TempRect.Top := round(TempList.GetDouble(1));
+ end;
+
+ case TempList.GetType(4) of
+ VTYPE_INT : TempRect.Right := TempList.GetInt(4);
+ VTYPE_DOUBLE : TempRect.Right := round(TempList.GetDouble(4));
+ end;
+
+ case TempList.GetType(5) of
+ VTYPE_INT : TempRect.Bottom := TempList.GetInt(5);
+ VTYPE_DOUBLE : TempRect.Bottom := round(TempList.GetDouble(5));
+ end;
+
+ UpdateElementBounds(TempRect);
+
+ Result := True;
+ end;
+ end;
+
+ FDevToolsStatus := dtsIdle;
+ end;
+end;
+{%Endregion}
+
+{Message handlers}
+{%Region}
+procedure TMainForm.BrowserCreatedMsg(Data: PtrInt);
+begin
+ Caption := 'Simple OSR Browser';
+ AddressPnl.Enabled := True;
+
+ Chromium1.SetFocus(Panel1.Focused);
+ Chromium1.NotifyMoveOrResizeStarted;
+end;
+
+procedure TMainForm.BrowserCloseFormMsg(Data: PtrInt);
+begin
+ Close;
+end;
+
+procedure TMainForm.FocusEnabledMsg(Data: PtrInt);
+begin
+ {$IFDEF CEF_USE_IME}
+ if (Data <> 0) then
+ FIMEHandler.Focus // Set the client window for the input context when an editable HTML element is focused
+ else
+ FIMEHandler.Blur; // Reset the input context when the editable HTML is not focused
+ {$ENDIF}
+end;
+
+procedure TMainForm.PendingResizeMsg(Data: PtrInt);
+begin
+ DoResize;
+end;
+
+procedure TMainForm.PendingInvalidateMsg(Data: PtrInt);
+begin
+ Panel1.Invalidate;
+end;
+
+procedure TMainForm.PendingCursorUpdateMsg(Data: PtrInt);
+begin
+ Panel1.Cursor := PanelCursor;
+end;
+
+procedure TMainForm.PendingHintUpdateMsg(Data: PtrInt);
+begin
+ Panel1.hint := UTF8Encode(PanelHint);
+ Panel1.ShowHint := (length(Panel1.hint) > 0);
+end;
+
+procedure TMainForm.WMMove(var Message: TLMMove);
+begin
+ inherited;
+ UpdatePanelOffset;
+ Chromium1.NotifyMoveOrResizeStarted;
+end;
+
+procedure TMainForm.WMSize(var Message: TLMSize);
+begin
+ inherited;
+ UpdatePanelOffset;
+ Chromium1.NotifyMoveOrResizeStarted;
+end;
+
+procedure TMainForm.WMWindowPosChanged(var Message: TLMWindowPosChanged);
+begin
+ inherited;
+ UpdatePanelOffset;
+ Chromium1.NotifyMoveOrResizeStarted;
+end;
+{%Endregion}
+
+end.
+
diff --git a/packages/cef4delphi_lazarus.lpk b/packages/cef4delphi_lazarus.lpk
index 0ea300b3..8ff16adc 100644
--- a/packages/cef4delphi_lazarus.lpk
+++ b/packages/cef4delphi_lazarus.lpk
@@ -901,6 +901,10 @@
+
+
+
+
diff --git a/source/uCEFBufferPanel.pas b/source/uCEFBufferPanel.pas
index 59211b2e..8d00460a 100644
--- a/source/uCEFBufferPanel.pas
+++ b/source/uCEFBufferPanel.pas
@@ -15,10 +15,12 @@ uses
{$ELSE}
{$IFDEF MSWINDOWS}Windows, imm, {$ENDIF} Classes, Forms, Controls, Graphics,
{$IFDEF FPC}
- LCLProc, LCLType, LCLIntf, LResources, LMessages, InterfaceBase, {$IFDEF MSWINDOWS}Win32Extra,{$ENDIF}
- {$IFDEF LINUXFPC}Messages,{$ENDIF}
+ LCLProc, LCLType, LCLIntf, LResources, LMessages, InterfaceBase, {$IFDEF MSWINDOWS}Win32Extra,{$ENDIF}
+ {$IFDEF LINUXFPC}Messages,{$ENDIF}
+ {$IFDEF LCLGTK2}glib2, gdk2, gtk2,{$ENDIF}
+ {$IFDEF LCLGTK3}LazGdk3, LazGtk3, LazGObject2, LazGLib2, gtk3procs, gtk3objects, gtk3widgets,{$ENDIF}
{$ELSE}
- Messages,
+ Messages,
{$ENDIF}
ExtCtrls, SyncObjs, SysUtils,
{$ENDIF}
@@ -31,6 +33,9 @@ type
TOnIMEPreEditChangedEvent = procedure(Sender: TObject; aFlag: cardinal; const aPreEditText: ustring) of object;
TOnIMECommitEvent = procedure(Sender: TObject; const aCommitText: ustring) of object;
{$ENDIF}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ TOnGdkKeyEvent = procedure(Sender: TObject; aEvent: PGdkEventKey; var aHandled: boolean) of object;
+ {$ENDIF}
{$IFDEF MSWINDOWS}
TOnHandledMessageEvent = procedure(Sender: TObject; var aMessage: TMessage; var aHandled : boolean) of object;
{$ENDIF}
@@ -72,6 +77,10 @@ type
FOnIMEPreEditChanged : TOnIMEPreEditChangedEvent;
FOnIMECommit : TOnIMECommitEvent;
{$ENDIF}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ FOnGdkKeyPress : TOnGdkKeyEvent;
+ FOnGdkKeyRelease : TOnGdkKeyEvent;
+ {$ENDIF}
procedure CreateSyncObj;
@@ -118,7 +127,11 @@ type
procedure DoOnIMESetComposition(const aText : ustring; const underlines : TCefCompositionUnderlineDynArray; const replacement_range, selection_range : TCefRange); virtual;
{$ENDIF}
{$IFDEF LINUXFPC}
- procedure WMIMEComposition(var aMessage: TMessage); message LM_IM_COMPOSITION;
+ procedure WMIMEComposition(var aMessage : TMessage); message LM_IM_COMPOSITION;
+ {$ENDIF}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ function DoOnGdkKeyPress(aEvent : PGdkEventKey) : boolean; virtual;
+ function DoOnGdkKeyRelease(aEvent : PGdkEventKey) : boolean; virtual;
{$ENDIF}
public
@@ -187,6 +200,14 @@ type
/// Copy the contents from the original popup buffer copy to the main buffer copy.
///
procedure DrawOrigPopupBuffer(const aSrcRect, aDstRect : TRect);
+
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ ///
+ /// Connects the GTK signals used to receive key press events.
+ ///
+ function ConnectSignals: boolean;
+ {$ENDIF}
+
///
/// Returns the scanline size.
///
@@ -379,6 +400,24 @@ type
///
property OnIMECommit : TOnIMECommitEvent read FOnIMECommit write FOnIMECommit;
{$ENDIF}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ ///
+ /// Event triggered when the key-press-event signal is received.
+ ///
+ ///
+ /// See the key-press-event article.
+ /// This event only works in GTK2 and GTK3 projects.
+ ///
+ property OnGdkKeyPress : TOnGdkKeyEvent read FOnGdkKeyPress write FOnGdkKeyPress;
+ ///
+ /// Event triggered when the key-release-event signal is received.
+ ///
+ ///
+ /// See the key-release-event article.
+ /// This event only works in GTK2 and GTK3 projects.
+ ///
+ property OnGdkKeyRelease : TOnGdkKeyEvent read FOnGdkKeyRelease write FOnGdkKeyRelease;
+ {$ENDIF}
///
/// Event triggered before the AlphaBlend call that transfer the web contents from the
/// bitmap buffer to the panel when the Transparent property is True.
@@ -552,6 +591,11 @@ begin
FOnIMEPreEditChanged := nil;
FOnIMECommit := nil;
{$ENDIF}
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ FOnGdkKeyPress := nil;
+ FOnGdkKeyRelease := nil;
+ ControlStyle := ControlStyle - [csNoFocus];
+ {$ENDIF}
end;
destructor TBufferPanel.Destroy;
@@ -566,6 +610,24 @@ begin
inherited Destroy;
end;
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+function GdkKeyPressCallback(Widget: PGtkWidget; Event: PGdkEventKey; Data: gPointer) : GBoolean; cdecl;
+begin
+ if assigned(Data) then
+ Result := TBufferPanel(Data).DoOnGdkKeyPress(Event)
+ else
+ Result := False;
+end;
+
+function GdkKeyReleaseCallback(Widget: PGtkWidget; Event: PGdkEventKey; Data: gPointer) : GBoolean; cdecl;
+begin
+ if assigned(Data) then
+ Result := TBufferPanel(Data).DoOnGdkKeyRelease(Event)
+ else
+ Result := False;
+end;
+{$ENDIF}
+
procedure TBufferPanel.AfterConstruction;
begin
inherited AfterConstruction;
@@ -581,6 +643,38 @@ begin
{$ENDIF}
end;
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+function TBufferPanel.ConnectSignals: boolean;
+var
+ TempHandlerID1, TempHandlerID2 : gulong;
+begin
+ TempHandlerID1 := 0;
+ TempHandlerID2 := 0;
+ try
+ try
+ if assigned(Parent) then
+ begin
+ {$IFDEF LCLGTK2}
+ TempHandlerID1 := g_signal_connect(PGtkWidget(Handle), 'key-press-event', G_CALLBACK(@GdkKeyPressCallback), gpointer(self));
+ TempHandlerID2 := g_signal_connect(PGtkWidget(Handle), 'key-release-event', G_CALLBACK(@GdkKeyReleaseCallback), gpointer(self));
+ {$ENDIF}
+ {$IFDEF LCLGTK3}
+ gtk_widget_set_can_focus(TGtk3Widget(Handle).Widget, True);
+ gtk_widget_add_events(TGtk3Widget(Handle).Widget, gint(GDK_KEY_PRESS_MASK));
+ TempHandlerID1 := g_signal_connect_data(TGtk3Widget(Handle).Widget, 'key-press-event', TGCallback(@GdkKeyPressCallback), gpointer(self), nil, G_CONNECT_DEFAULT);
+ TempHandlerID2 := g_signal_connect_data(TGtk3Widget(Handle).Widget, 'key-release-event', TGCallback(@GdkKeyReleaseCallback), gpointer(self), nil, G_CONNECT_DEFAULT);
+ {$ENDIF}
+ end;
+ except
+ on e : exception do
+ if CustomExceptionHandler('TBufferPanel.ConnectSignals', e) then raise;
+ end;
+ finally
+ Result := (TempHandlerID1 > 0) and (TempHandlerID2 > 0);
+ end;
+end;
+{$ENDIF}
+
procedure TBufferPanel.CreateIMEHandler;
begin
{$IFDEF MSWINDOWS}
@@ -1064,7 +1158,7 @@ end;
{$IFDEF LINUXFPC}
// The LM_IM_COMPOSITION message is only used by Lazarus in GTK2 when WITH_GTK2_IM is defined.
// You need to open IDE dialog "Tools / Configure 'Build Lazarus'", and there enable the define: WITH_GTK2_IM; then recompile the IDE.
-procedure TBufferPanel.WMIMEComposition(var aMessage: TMessage);
+procedure TBufferPanel.WMIMEComposition(var aMessage : TMessage);
var
TempText : ustring;
TempCommit : string;
@@ -1095,6 +1189,21 @@ begin
end;
end;
{$ENDIF}
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+function TBufferPanel.DoOnGdkKeyPress(aEvent : PGdkEventKey) : boolean;
+begin
+ Result := True;
+ if assigned(FOnGdkKeyPress) then
+ FOnGdkKeyPress(self, aEvent, Result);
+end;
+
+function TBufferPanel.DoOnGdkKeyRelease(aEvent : PGdkEventKey) : boolean;
+begin
+ Result := True;
+ if assigned(FOnGdkKeyRelease) then
+ FOnGdkKeyRelease(self, aEvent, Result);
+end;
+{$ENDIF}
function TBufferPanel.GetBufferBits : pointer;
begin
diff --git a/source/uceflinuxosrimehandler.pas b/source/uceflinuxosrimehandler.pas
new file mode 100644
index 00000000..5c5de1e6
--- /dev/null
+++ b/source/uceflinuxosrimehandler.pas
@@ -0,0 +1,269 @@
+unit uCEFLinuxOSRIMEHandler;
+
+{$mode ObjFPC}{$H+}
+
+interface
+
+uses
+ {$IFDEF LCLGTK2}gtk2, glib2, gdk2,{$ENDIF}
+ {$IFDEF LCLGTK3}LazGdk3, LazGtk3, LazGObject2, LazGLib2, gtk3procs, gtk3widgets,{$ENDIF}
+ Classes, ExtCtrls, Forms;
+
+type
+ TCEFLinuxOSRIMEHandler = class
+ protected
+ FPanel : TCustomPanel;
+ FForm : TCustomForm;
+ FHasFocus : boolean;
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ FIMContext : PGtkIMContext;
+ {$IFEND}
+
+ function GetInitialized : boolean;
+ procedure SetPanel(aValue : TCustomPanel);
+
+ public
+ constructor Create(aPanel : TCustomPanel);
+ destructor Destroy; override;
+ procedure CreateContext;
+ procedure DestroyContext;
+ procedure SetClientWindow;
+ procedure ResetClientWindow;
+ procedure ConnectSignals;
+ procedure Focus;
+ procedure Blur;
+ procedure Reset;
+ procedure SetCursorLocation(X, Y: integer);
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ function FilterKeyPress(aEvent : PGdkEventKey) : boolean;
+ {$ENDIF}
+
+ property Initialized : boolean read GetInitialized;
+ property HasFocus : boolean read FHasFocus;
+ property Panel : TCustomPanel read FPanel write SetPanel;
+ end;
+
+implementation
+
+// https://chromium.googlesource.com/chromium/src/+/4079d37114e1dd416e99d5edc535f4214b787fc7/chrome/browser/ui/gtk/input_method_context_impl_gtk.cc
+// https://chromium.googlesource.com/chromium/src/+/refs/heads/main/ui/gtk/input_method_context_impl_gtk.cc
+
+uses
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}pango,{$ENDIF}
+ {$IFDEF FPC}LCLType, LCLIntf, LMessages,{$ENDIF}
+ SysUtils;
+
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+procedure gtk_commit_cb({%H-}context: PGtkIMContext; const Str: Pgchar; {%H-}Data: Pointer); cdecl;
+begin
+ SendMessage(HWND(Data), LM_IM_COMPOSITION, GTK_IM_FLAG_COMMIT, LPARAM(Str));
+end;
+
+procedure gtk_preedit_start_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+begin
+ SendMessage(HWND(Data), LM_IM_COMPOSITION, GTK_IM_FLAG_START, LPARAM(context));
+end;
+
+procedure gtk_preedit_end_cb({%H-}context: PGtkIMContext; {%H-}Data: Pointer); cdecl;
+begin
+ SendMessage(HWND(Data), LM_IM_COMPOSITION, GTK_IM_FLAG_END, LPARAM(context));
+end;
+
+procedure gtk_preedit_changed_cb({%H-}context:PGtkIMContext; {%H-}Data:Pointer); cdecl;
+var
+ TempStr : Pgchar;
+ TempPangoAttr : PPangoAttrList;
+ TempCurpos : gint;
+begin
+ gtk_im_context_get_preedit_string(context, @TempStr, {$IFDEF LCLGTK3}@{$ENDIF}TempPangoAttr, @TempCurpos);
+ SendMessage(HWND(Data), LM_IM_COMPOSITION, GTK_IM_FLAG_PREEDIT, LPARAM(pchar(TempStr)));
+ g_free(TempStr);
+ pango_attr_list_unref(TempPangoAttr);
+end;
+{$ENDIF}
+
+constructor TCEFLinuxOSRIMEHandler.Create(aPanel : TCustomPanel);
+begin
+ inherited Create;
+
+ FPanel := aPanel;
+ FHasFocus := False;
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ FIMContext := nil;
+ {$ENDIF}
+
+ if assigned(FPanel) then
+ FForm := GetParentForm(FPanel)
+ else
+ FForm := nil;
+end;
+
+destructor TCEFLinuxOSRIMEHandler.Destroy;
+begin
+ ResetClientWindow;
+ DestroyContext;
+
+ inherited Destroy;
+end;
+
+function TCEFLinuxOSRIMEHandler.GetInitialized : boolean;
+begin
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ Result := assigned(FPanel) and assigned(FIMContext);
+ {$ELSE}
+ Result := False;
+ {$ENDIF}
+end;
+
+procedure TCEFLinuxOSRIMEHandler.SetPanel(aValue : TCustomPanel);
+
+begin
+ FPanel := aValue;
+
+ if assigned(FPanel) then
+ FForm := GetParentForm(FPanel)
+ else
+ FForm := nil;
+end;
+
+procedure TCEFLinuxOSRIMEHandler.CreateContext;
+begin
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ if not(assigned(FIMContext)) then
+ begin
+ FIMContext := gtk_im_multicontext_new();
+ SetClientWindow;
+ ConnectSignals;
+ end;
+ {$ENDIF}
+end;
+
+procedure TCEFLinuxOSRIMEHandler.DestroyContext;
+begin
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ if assigned(FIMContext) then
+ begin
+ g_object_unref(FIMContext);
+ FIMContext := nil;
+ end;
+ {$ENDIF}
+end;
+
+procedure TCEFLinuxOSRIMEHandler.SetClientWindow;
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+var
+ TempWidget : PGtkWidget;
+{$ENDIF}
+begin
+ if Initialized then
+ begin
+ {$IFDEF LCLGTK2}
+ TempWidget := PGtkWidget(FForm.Handle);
+ gtk_im_context_set_client_window(FIMContext, TempWidget^.window);
+ {$ENDIF}
+ {$IFDEF LCLGTK3}
+ TempWidget := TGtk3Widget(FForm.Handle).Widget;
+ gtk_im_context_set_client_window(FIMContext, TempWidget^.window);
+ {$ENDIF}
+ end;
+end;
+
+procedure TCEFLinuxOSRIMEHandler.ResetClientWindow;
+begin
+ if Initialized then
+ begin
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ gtk_im_context_reset(FIMContext);
+ gtk_im_context_set_client_window(FIMContext, nil);
+ {$ENDIF}
+ end;
+end;
+
+procedure TCEFLinuxOSRIMEHandler.ConnectSignals;
+begin
+ if Initialized then
+ begin
+ {$IFDEF LCLGTK3}
+ g_signal_connect_data(PGObject(@FIMContext), 'commit', TGCallback(@gtk_commit_cb), GPointer(FPanel.Handle), nil, G_CONNECT_DEFAULT);
+ g_signal_connect_data(PGObject(@FIMContext), 'preedit-start', TGCallback(@gtk_preedit_start_cb), GPointer(FPanel.Handle), nil, G_CONNECT_DEFAULT);
+ g_signal_connect_data(PGObject(@FIMContext), 'preedit-end', TGCallback(@gtk_preedit_end_cb), GPointer(FPanel.Handle), nil, G_CONNECT_DEFAULT);
+ g_signal_connect_data(PGObject(@FIMContext), 'preedit-changed', TGCallback(@gtk_preedit_changed_cb), GPointer(FPanel.Handle), nil, G_CONNECT_DEFAULT);
+ {$ENDIF}
+ {$IFDEF LCLGTK2}
+ g_signal_connect(G_OBJECT(FIMContext), 'commit', G_CALLBACK(@gtk_commit_cb), GPointer(FPanel.Handle));
+ g_signal_connect(G_OBJECT(FIMContext), 'preedit-start', G_CALLBACK(@gtk_preedit_start_cb), GPointer(FPanel.Handle));
+ g_signal_connect(G_OBJECT(FIMContext), 'preedit-end', G_CALLBACK(@gtk_preedit_end_cb), GPointer(FPanel.Handle));
+ g_signal_connect(G_OBJECT(FIMContext), 'preedit-changed', G_CALLBACK(@gtk_preedit_changed_cb), GPointer(FPanel.Handle));
+ {$ENDIF}
+ end;
+end;
+
+procedure TCEFLinuxOSRIMEHandler.Focus;
+begin
+ if Initialized then
+ begin
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ gtk_im_context_focus_in(FIMContext);
+ {$ENDIF}
+ FHasFocus := True;
+ end;
+end;
+
+procedure TCEFLinuxOSRIMEHandler.Blur;
+begin
+ if Initialized then
+ begin
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ gtk_im_context_focus_out(FIMContext);
+ {$ENDIF}
+ FHasFocus := False;
+ end;
+end;
+
+procedure TCEFLinuxOSRIMEHandler.Reset;
+begin
+ if Initialized then
+ begin
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ gtk_im_context_reset(FIMContext);
+
+ // Some input methods may not honour the reset call.
+ // Focusing out/in the to make sure it gets reset correctly.
+ if FHasFocus then
+ begin
+ gtk_im_context_focus_out(FIMContext);
+ gtk_im_context_focus_in(FIMContext);
+ end;
+ {$ENDIF}
+ end;
+end;
+
+procedure TCEFLinuxOSRIMEHandler.SetCursorLocation(X, Y: integer);
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+var
+ TempCurPos: TGdkRectangle;
+{$ENDIF}
+begin
+ if Initialized then
+ begin
+ {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+ TempCurPos.x := x;
+ TempCurPos.y := y;
+ TempCurPos.width := 0;
+ TempCurPos.height := 0;
+
+ gtk_im_context_set_cursor_location(FIMContext, @TempCurPos);
+ {$ENDIF}
+ end;
+end;
+
+{$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)}
+function TCEFLinuxOSRIMEHandler.FilterKeyPress(aEvent : PGdkEventKey) : boolean;
+begin
+ if Initialized then
+ Result := gtk_im_context_filter_keypress(FIMContext, aEvent);
+end;
+{$ENDIF}
+
+end.
+
diff --git a/update_CEF4Delphi.json b/update_CEF4Delphi.json
index 87f16467..4d3c7112 100644
--- a/update_CEF4Delphi.json
+++ b/update_CEF4Delphi.json
@@ -2,7 +2,7 @@
"UpdateLazPackages" : [
{
"ForceNotify" : true,
- "InternalVersion" : 769,
+ "InternalVersion" : 770,
"Name" : "cef4delphi_lazarus.lpk",
"Version" : "139.0.28"
}