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 @@ + + + + + + + + + + + <Scaled Value="True"/> + <ResourceType Value="res"/> + <UseXPManifest Value="True"/> + <XPManifest> + <DpiAware Value="True"/> + </XPManifest> + <Icon Value="0"/> + </General> + <MacroValues Count="1"> + <Macro1 Name="LCLWidgetType" Value="gtk3"/> + </MacroValues> + <BuildModes Count="1"> + <Item1 Name="Default" Default="True"/> + <SharedMatrixOptions Count="1"> + <Item1 ID="996268604706" Modes="Default" Type="IDEMacro" MacroName="LCLWidgetType" Value="gtk3"/> + </SharedMatrixOptions> + </BuildModes> + <PublishOptions> + <Version Value="2"/> + <UseFileFilters Value="True"/> + </PublishOptions> + <RunParams> + <FormatVersion Value="2"/> + </RunParams> + <RequiredPackages Count="3"> + <Item1> + <PackageName Value="lazmouseandkeyinput"/> + </Item1> + <Item2> + <PackageName Value="CEF4Delphi_Lazarus"/> + </Item2> + <Item3> + <PackageName Value="LCL"/> + </Item3> + </RequiredPackages> + <Units Count="3"> + <Unit0> + <Filename Value="SimpleOSRBrowser.lpr"/> + <IsPartOfProject Value="True"/> + </Unit0> + <Unit1> + <Filename Value="usimpleosrbrowser.pas"/> + <IsPartOfProject Value="True"/> + <ComponentName Value="MainForm"/> + <HasResources Value="True"/> + <ResourceBaseClass Value="Form"/> + <UnitName Value="uSimpleOSRBrowser"/> + </Unit1> + <Unit2> + <Filename Value="interfaces.pas"/> + <IsPartOfProject Value="True"/> + </Unit2> + </Units> + </ProjectOptions> + <CompilerOptions> + <Version Value="11"/> + <Target> + <Filename Value="../../../bin/SimpleOSRBrowser"/> + </Target> + <SearchPaths> + <IncludeFiles Value="$(ProjOutDir)"/> + <UnitOutputDirectory Value="lib/$(TargetCPU)-$(TargetOS)"/> + </SearchPaths> + <Linking> + <Debugging> + <DebugInfoType Value="dsDwarf3"/> + </Debugging> + <Options> + <Win32> + <GraphicApplication Value="True"/> + </Win32> + </Options> + </Linking> + <Other> + <CustomOptions Value="-dUseCthreads"/> + </Other> + </CompilerOptions> + <Debugging> + <Exceptions Count="3"> + <Item1> + <Name Value="EAbort"/> + </Item1> + <Item2> + <Name Value="ECodetoolError"/> + </Item2> + <Item3> + <Name Value="EFOpenError"/> + </Item3> + </Exceptions> + </Debugging> +</CONFIG> 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 @@ <Filename Value="..\source\uCEFSettingObserver.pas"/> <UnitName Value="uCEFSettingObserver"/> </Item215> + <Item216> + <Filename Value="..\source\uceflinuxosrimehandler.pas"/> + <UnitName Value="uCEFLinuxOSRIMEHandler"/> + </Item216> </Files> <CompatibilityMode Value="True"/> <RequiredPkgs Count="5"> 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. /// </summary> procedure DrawOrigPopupBuffer(const aSrcRect, aDstRect : TRect); + + {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)} + /// <summary> + /// Connects the GTK signals used to receive key press events. + /// </summary> + function ConnectSignals: boolean; + {$ENDIF} + /// <summary> /// Returns the scanline size. /// </summary> @@ -379,6 +400,24 @@ type /// </remarks> property OnIMECommit : TOnIMECommitEvent read FOnIMECommit write FOnIMECommit; {$ENDIF} + {$IF DEFINED(LCLGTK2) or DEFINED(LCLGTK3)} + /// <summary> + /// Event triggered when the key-press-event signal is received. + /// </summary> + /// <remarks> + /// <para><see href="https://docs.gtk.org/gtk3/signal.Widget.key-press-event.html">See the key-press-event article.</see></para> + /// <para>This event only works in GTK2 and GTK3 projects.</para> + /// </remarks> + property OnGdkKeyPress : TOnGdkKeyEvent read FOnGdkKeyPress write FOnGdkKeyPress; + /// <summary> + /// Event triggered when the key-release-event signal is received. + /// </summary> + /// <remarks> + /// <para><see href="https://docs.gtk.org/gtk3/signal.Widget.key-release-event.html">See the key-release-event article.</see></para> + /// <para>This event only works in GTK2 and GTK3 projects.</para> + /// </remarks> + property OnGdkKeyRelease : TOnGdkKeyEvent read FOnGdkKeyRelease write FOnGdkKeyRelease; + {$ENDIF} /// <summary> /// 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" }