v0.29.0 21b3150

cef2gtk_adapter.go RawHistoryFolder

1package cef
2
3import (
4	"errors"
5	"sync"
6	"sync/atomic"
7
8	purecef "github.com/bnema/purego-cef/cef"
9	cef2gtk "github.com/bnema/purego-cef2gtk"
10	"github.com/bnema/puregotk/v4/gtk"
11)
12
13// ErrAdapterDestroyed is returned when an operation is attempted on a destroyed
14// Cef2gtkAdapter.
15var ErrAdapterDestroyed = errors.New("cef2gtk_adapter: adapter is destroyed")
16
17// Cef2gtkAdapter is a thin infrastructure adapter around *cef2gtk.View. It
18// keeps purego-cef2gtk imports confined to internal/infrastructure/cef.
19//
20// GTK-thread affinity is a hard constraint: most methods touch GTK/CEF view
21// lifecycle or input and MUST be called from Dumber's GTK scheduling path
22// (e.g. runOnGTK or runOnGTKSync) unless the caller is already executing on
23// the GTK main context. Methods are annotated below.
24//
25// The adapter does NOT own GL/PBO staging, dirty-rect uploads, or GTK input
26// controller implementation — those belong to purego-cef2gtk.
27type Cef2gtkAdapter struct {
28	viewMu            sync.RWMutex
29	view              *cef2gtk.View
30	inputTargetWidget *gtk.Widget
31	destroyed         atomic.Bool
32	destroyCnt        atomic.Uint64
33}
34
35// NewCef2gtkAdapter creates an accelerated CEF view and wraps it in a thin
36// Dumber adapter.
37func NewCef2gtkAdapter(renderStackPlan cef2gtk.RenderStackPlan) *Cef2gtkAdapter {
38	v := cef2gtk.NewViewWithOptions(cef2gtk.ViewOptions{RenderStackPlan: renderStackPlan})
39	if v == nil {
40		return nil
41	}
42	return &Cef2gtkAdapter{view: v}
43}
44
45// Widget returns the native GTK widget for embedding into containers.
46// The returned pointer must be used from the GTK main thread for packing.
47func (a *Cef2gtkAdapter) Widget() *gtk.Widget {
48	if a == nil || a.destroyed.Load() {
49		return nil
50	}
51	a.viewMu.RLock()
52	defer a.viewMu.RUnlock()
53	if a.view == nil {
54		return nil
55	}
56	return a.view.Widget()
57}
58
59// GLArea returns the underlying GtkGLArea when the GLArea backend is active.
60// It returns nil for the GDK DMABUF backend.
61func (a *Cef2gtkAdapter) GLArea() *gtk.GLArea {
62	if a == nil || a.destroyed.Load() {
63		return nil
64	}
65	a.viewMu.RLock()
66	defer a.viewMu.RUnlock()
67	if a.view == nil {
68		return nil
69	}
70	return a.view.GLArea()
71}
72
73// NativeWidget returns the uintptr of the active GTK widget for embedding via
74// port.NativeWidgetProvider. Call on the GTK main thread.
75func (a *Cef2gtkAdapter) NativeWidget() uintptr {
76	widget := a.Widget()
77	if widget == nil {
78		return 0
79	}
80	return widget.GoPointer()
81}
82
83// Size returns the bridge's last observed positive widget size, or 1x1 before
84// the GTK widget has been allocated. It is safe to call off the GTK thread.
85func (a *Cef2gtkAdapter) Size() (int32, int32) {
86	if a == nil || a.destroyed.Load() {
87		return 1, 1
88	}
89	a.viewMu.RLock()
90	defer a.viewMu.RUnlock()
91	if a.view == nil {
92		return 1, 1
93	}
94	return a.view.Size()
95}
96
97// DeviceScaleFactor returns the bridge's last observed GTK scale factor, or 1
98// before the widget has reported a scale. It is safe to call off the GTK thread.
99func (a *Cef2gtkAdapter) DeviceScaleFactor() float32 {
100	if a == nil || a.destroyed.Load() {
101		return 1
102	}
103	a.viewMu.RLock()
104	defer a.viewMu.RUnlock()
105	if a.view == nil {
106		return 1
107	}
108	return a.view.DeviceScaleFactor()
109}
110
111// OSRBackingScaleFactor returns the device backing scale used for CEF OSR
112// coordinates. It returns 1 when purego-cef2gtk is using CEF's normal logical
113// OSR contract.
114func (a *Cef2gtkAdapter) OSRBackingScaleFactor() float64 {
115	if a == nil || a.destroyed.Load() {
116		return 1
117	}
118	a.viewMu.RLock()
119	defer a.viewMu.RUnlock()
120	if a.view == nil {
121		return 1
122	}
123	return cef2gtk.OSRBackingScaleFactorForScale(float64(a.view.DeviceScaleFactor()))
124}
125
126// AddSizeObserver registers a positive-size change callback. Register and
127// unregister from the GTK main thread; callbacks are invoked by the bridge from
128// GTK size notifications.
129func (a *Cef2gtkAdapter) AddSizeObserver(fn func(width, height int32)) func() {
130	if a == nil || a.destroyed.Load() {
131		return func() {}
132	}
133	a.viewMu.RLock()
134	defer a.viewMu.RUnlock()
135	if a.view == nil {
136		return func() {}
137	}
138	return a.view.AddSizeObserver(fn)
139}
140
141// HasFocus reports whether the effective GTK input target currently has focus.
142// Must be called on the GTK main thread.
143func (a *Cef2gtkAdapter) HasFocus() bool {
144	if a == nil || a.destroyed.Load() {
145		return false
146	}
147	a.viewMu.RLock()
148	defer a.viewMu.RUnlock()
149	if a.view == nil {
150		return false
151	}
152	if a.inputTargetWidget != nil {
153		return a.inputTargetWidget.HasFocus()
154	}
155	return a.view.HasFocus()
156}
157
158// SetCursorFromName applies a named cursor to the bridge widget. Must be called
159// on the GTK main thread.
160func (a *Cef2gtkAdapter) SetCursorFromName(name string) {
161	if a == nil || a.destroyed.Load() {
162		return
163	}
164	a.viewMu.RLock()
165	defer a.viewMu.RUnlock()
166	if a.view == nil {
167		return
168	}
169	a.view.SetCursorFromName(name)
170}
171
172// RefreshObservedSizeOnGTKThread synchronously refreshes the bridge's cached
173// observed size from the current GTK allocation. Must be called on the GTK
174// main thread.
175func (a *Cef2gtkAdapter) RefreshObservedSizeOnGTKThread() (int32, int32) {
176	if a == nil || a.destroyed.Load() {
177		return 1, 1
178	}
179	a.viewMu.RLock()
180	defer a.viewMu.RUnlock()
181	if a.view == nil {
182		return 1, 1
183	}
184	return a.view.RefreshObservedSizeOnGTKThread()
185}
186
187// PrepareOnGTKThread initializes renderer resources owned by purego-cef2gtk.
188// Must be called on the GTK main thread.
189func (a *Cef2gtkAdapter) PrepareOnGTKThread() error {
190	if a == nil || a.destroyed.Load() {
191		return ErrAdapterDestroyed
192	}
193	a.viewMu.RLock()
194	defer a.viewMu.RUnlock()
195	if a.view == nil {
196		return ErrAdapterDestroyed
197	}
198	return a.view.PrepareOnGTKThread()
199}
200
201// RenderHandler returns a CEF render handler that delegates accelerated paint
202// handling to purego-cef2gtk. The returned handler satisfies cef.RenderHandler
203// and uses the bridge's selected accelerated DMABUF import path.
204//
205// hooks may be a zero-value cef2gtk.Hooks if no custom callbacks are needed.
206func (a *Cef2gtkAdapter) RenderHandler(hooks cef2gtk.Hooks) purecef.RenderHandler {
207	if a == nil || a.destroyed.Load() {
208		return nil
209	}
210	a.viewMu.RLock()
211	defer a.viewMu.RUnlock()
212	if a.view == nil {
213		return nil
214	}
215	return a.view.RenderHandler(hooks)
216}
217
218// ConfigureProfiling enables or disables purego-cef2gtk render profiling for this view.
219func (a *Cef2gtkAdapter) ConfigureProfiling(opts cef2gtk.ProfileOptions) error {
220	if a == nil || a.destroyed.Load() {
221		return ErrAdapterDestroyed
222	}
223	a.viewMu.RLock()
224	defer a.viewMu.RUnlock()
225	if a.view == nil {
226		return ErrAdapterDestroyed
227	}
228	return a.view.ConfigureProfiling(opts)
229}
230
231// RecordExternalBeginFrameSent records one externally-driven CEF BeginFrame in
232// the bridge profiler when profiling is enabled. It is a no-op otherwise.
233func (a *Cef2gtkAdapter) RecordExternalBeginFrameSent() {
234	if a == nil || a.destroyed.Load() {
235		return
236	}
237	a.viewMu.RLock()
238	defer a.viewMu.RUnlock()
239	if a.view == nil {
240		return
241	}
242	a.view.RecordExternalBeginFrameSent()
243}
244
245// AttachInput attaches GTK event controllers to the view and forwards input
246// to the given CEF browser host. Must be called on the GTK main thread.
247//
248// opts.Scale configures HiDPI scale for pointer coordinate translation.
249func (a *Cef2gtkAdapter) AttachInput(host purecef.BrowserHost, opts cef2gtk.InputOptions) error {
250	return a.AttachInputToWidget(host, nil, opts)
251}
252
253// AttachInputToWidget attaches GTK event controllers to the specified target
254// widget and forwards input to the given CEF browser host. When targetWidget is
255// nil, it falls back to the bridge render widget. Must be called on the GTK
256// main thread.
257func (a *Cef2gtkAdapter) AttachInputToWidget(host purecef.BrowserHost, targetWidget *gtk.Widget, opts cef2gtk.InputOptions) error {
258	if a == nil || a.destroyed.Load() {
259		return ErrAdapterDestroyed
260	}
261	a.viewMu.Lock()
262	defer a.viewMu.Unlock()
263	if a.view == nil {
264		return ErrAdapterDestroyed
265	}
266	effectiveTarget := targetWidget
267	if effectiveTarget == nil {
268		effectiveTarget = a.view.Widget()
269	}
270	type inputWidgetAttacher interface {
271		AttachInputToWidget(purecef.BrowserHost, *gtk.Widget, cef2gtk.InputOptions) error
272	}
273	var err error
274	if attacher, ok := any(a.view).(inputWidgetAttacher); ok {
275		err = attacher.AttachInputToWidget(host, effectiveTarget, opts)
276	} else {
277		err = a.view.AttachInput(host, opts)
278	}
279	if err != nil {
280		a.inputTargetWidget = nil
281		return err
282	}
283	a.inputTargetWidget = effectiveTarget
284	return nil
285}
286
287// SetInputHost updates the CEF browser host used by the attached input bridge.
288// Must be called on the GTK main thread. Callers must call AttachInput first or
289// this returns an error from the underlying bridge.
290func (a *Cef2gtkAdapter) SetInputHost(host purecef.BrowserHost) error {
291	if a == nil || a.destroyed.Load() {
292		return ErrAdapterDestroyed
293	}
294	a.viewMu.RLock()
295	defer a.viewMu.RUnlock()
296	if a.view == nil {
297		return ErrAdapterDestroyed
298	}
299	return a.view.SetInputHost(host)
300}
301
302// DetachInput removes GTK input controllers attached by AttachInput.
303// Must be called on the GTK main thread.
304func (a *Cef2gtkAdapter) DetachInput() error {
305	if a == nil || a.destroyed.Load() {
306		return ErrAdapterDestroyed
307	}
308	a.viewMu.Lock()
309	defer a.viewMu.Unlock()
310	if a.view == nil {
311		return ErrAdapterDestroyed
312	}
313	err := a.view.DetachInput()
314	a.inputTargetWidget = nil
315	return err
316}
317
318// Diagnostics returns a point-in-time snapshot of bridge rendering diagnostics
319// including accelerated paint counts, import/render failures, and events.
320func (a *Cef2gtkAdapter) Diagnostics() cef2gtk.Diagnostics {
321	if a == nil || a.destroyed.Load() {
322		return cef2gtk.Diagnostics{}
323	}
324	a.viewMu.RLock()
325	defer a.viewMu.RUnlock()
326	if a.view == nil {
327		return cef2gtk.Diagnostics{}
328	}
329	return a.view.Diagnostics()
330}
331
332// Destroy releases renderer resources owned by purego-cef2gtk and disconnects GTK
333// signal handlers. Must be called on the GTK main thread. After Destroy,
334// operations return ErrAdapterDestroyed or a zero-value result.
335func (a *Cef2gtkAdapter) Destroy() error {
336	if a == nil {
337		return ErrAdapterDestroyed
338	}
339	a.viewMu.Lock()
340	defer a.viewMu.Unlock()
341	if !a.destroyed.CompareAndSwap(false, true) || a.view == nil {
342		return ErrAdapterDestroyed
343	}
344	err := a.view.Destroy()
345	a.view = nil
346	a.inputTargetWidget = nil
347	a.destroyCnt.Add(1)
348	return err
349}
350
351// IsDestroyed returns true if the adapter has been destroyed.
352func (a *Cef2gtkAdapter) IsDestroyed() bool {
353	return a == nil || a.destroyed.Load()
354}

Keyboard shortcuts

Single-key shortcuts are on. ctrl k and esc work whatever this says.

ctrl k :
go to a section, branch, tag, file or repository
?
this list
j k
move the row cursor down / up
enter
open the row under the cursor; on Files, a directory opens or closes in place
↑ ↓ ← → home end
in the file tree (focus on a row): previous / next row, close / open a directory or go to its parent / first entry, first / last row. They never open a file
l h
on Files: open a directory or a file / close a directory or go up to its parent, then to the parent page. Elsewhere: into the side panel (on a merge request, its files) / back to the content; there j k move between its controls
g g G
first / last row
/
filter the list; on Files, find a file anywhere in the repository (esc clears, enter opens the best match)
b
switch branch or tag
g s g f g c g b g m g i g r g a
tabs: summary, files, commits, branches & tags, merge requests, issues, releases, actions (when available)
g h
home
w
wide mode: use the whole width of the screen, or not (remembered)
y
copy a permalink pinned to the commit
d u x
diff: split / unified / collapse or expand all
e E
diff: the whole file in view / every file, with the lines the hunks hide
n
merge request: next note in the diff
esc
close a dialog, leave a field

Branches 8

Tags 42

↑↓ moveenter openesc close