v0.28.0 3cb3770

migrate_engine.go RawHistoryFolder

1package config
2
3import (
4	"fmt"
5	"github.com/pelletier/go-toml/v2"
6	"io/fs"
7	"os"
8)
9
10// configFilePermissions is the file permission used when writing migrated config files.
11const configFilePermissions fs.FileMode = 0o644
12
13// engineMigrationMapping defines old key -> new key mappings for the engine migration.
14// Keys with empty new key ("") are dropped (not migrated).
15// The source section is the first part of the old key.
16type engineFieldMapping struct {
17	oldSection string
18	oldField   string
19	newSection string // "engine" or "engine.webkit"
20	newField   string // empty means drop
21}
22
23// engineUniversalMappings are fields that go into [engine] (not [engine.webkit]).
24var engineUniversalMappings = []engineFieldMapping{
25	// [performance] -> [engine]
26	{oldSection: "performance", oldField: "profile", newSection: "engine", newField: "profile"},
27	{oldSection: "performance", oldField: "zoom_cache_size", newSection: "engine", newField: "zoom_cache_size"},
28	{oldSection: "performance", oldField: "webview_pool_prewarm_count", newSection: "engine", newField: "pool_prewarm_count"},
29	// [privacy] -> [engine]
30	{oldSection: "privacy", oldField: "cookie_policy", newSection: "engine", newField: "cookie_policy"},
31}
32
33// engineWebkitMappings are fields that go into [engine.webkit].
34var engineWebkitMappings = []engineFieldMapping{
35	// [privacy] -> [engine.webkit]
36	{oldSection: "privacy", oldField: "itp_enabled", newSection: "engine.webkit", newField: "itp_enabled"},
37
38	// [rendering] -> [engine.webkit] (rendering.mode is dropped)
39	{oldSection: "rendering", oldField: "disable_dmabuf_renderer", newSection: "engine.webkit", newField: "disable_dmabuf_renderer"},
40	{oldSection: "rendering", oldField: "force_compositing_mode", newSection: "engine.webkit", newField: "force_compositing_mode"},
41	{oldSection: "rendering", oldField: "disable_compositing_mode", newSection: "engine.webkit", newField: "disable_compositing_mode"},
42	{oldSection: "rendering", oldField: "gsk_renderer", newSection: "engine.webkit", newField: "gsk_renderer"},
43	{oldSection: "rendering", oldField: "disable_mipmaps", newSection: "engine.webkit", newField: "disable_mipmaps"},
44	{oldSection: "rendering", oldField: "prefer_gl", newSection: "engine.webkit", newField: "prefer_gl"},
45	{oldSection: "rendering", oldField: "draw_compositing_indicators", newSection: "engine.webkit", newField: "draw_compositing_indicators"},
46	{oldSection: "rendering", oldField: "show_fps", newSection: "engine.webkit", newField: "show_fps"},
47	{oldSection: "rendering", oldField: "sample_memory", newSection: "engine.webkit", newField: "sample_memory"},
48	{oldSection: "rendering", oldField: "debug_frames", newSection: "engine.webkit", newField: "debug_frames"},
49
50	// [performance] -> [engine.webkit] (webkit-specific) — field names are identical
51	{oldSection: "performance", oldField: "skia_cpu_painting_threads",
52		newSection: "engine.webkit", newField: "skia_cpu_painting_threads"},
53	{oldSection: "performance", oldField: "skia_gpu_painting_threads",
54		newSection: "engine.webkit", newField: "skia_gpu_painting_threads"},
55	{oldSection: "performance", oldField: "skia_enable_cpu_rendering",
56		newSection: "engine.webkit", newField: "skia_enable_cpu_rendering"},
57	{oldSection: "performance", oldField: "web_process_memory_limit_mb",
58		newSection: "engine.webkit", newField: "web_process_memory_limit_mb"},
59	{oldSection: "performance", oldField: "web_process_memory_poll_interval_sec",
60		newSection: "engine.webkit", newField: "web_process_memory_poll_interval_sec"},
61	{oldSection: "performance", oldField: "web_process_memory_conservative_threshold",
62		newSection: "engine.webkit", newField: "web_process_memory_conservative_threshold"},
63	{oldSection: "performance", oldField: "web_process_memory_strict_threshold",
64		newSection: "engine.webkit", newField: "web_process_memory_strict_threshold"},
65	{oldSection: "performance", oldField: "network_process_memory_limit_mb",
66		newSection: "engine.webkit", newField: "network_process_memory_limit_mb"},
67	{oldSection: "performance", oldField: "network_process_memory_poll_interval_sec",
68		newSection: "engine.webkit", newField: "network_process_memory_poll_interval_sec"},
69	{oldSection: "performance", oldField: "network_process_memory_conservative_threshold",
70		newSection: "engine.webkit", newField: "network_process_memory_conservative_threshold"},
71	{oldSection: "performance", oldField: "network_process_memory_strict_threshold",
72		newSection: "engine.webkit", newField: "network_process_memory_strict_threshold"},
73
74	// [media] GStreamer fields -> [engine.webkit]
75	{oldSection: "media", oldField: "force_vsync", newSection: "engine.webkit", newField: "force_vsync"},
76	{oldSection: "media", oldField: "gl_rendering_mode", newSection: "engine.webkit", newField: "gl_rendering_mode"},
77	{oldSection: "media", oldField: "gstreamer_debug_level", newSection: "engine.webkit", newField: "gstreamer_debug_level"},
78
79	// [runtime] -> [engine.webkit]
80	{oldSection: "runtime", oldField: "prefix", newSection: "engine.webkit", newField: "prefix"},
81}
82
83// oldSectionsToRemove are the sections that are fully removed after migration
84// (media is not here because it has fields that stay).
85var oldSectionsToRemove = []string{"rendering", "performance", "privacy", "runtime"}
86
87// MigrateToEngineConfig restructures old top-level config sections into [engine]/[engine.webkit].
88// Returns true if migration was performed, false if already migrated or no old sections found.
89func MigrateToEngineConfig(configFile string) (bool, error) {
90	raw, err := readRawTOML(configFile)
91	if err != nil {
92		return false, err
93	}
94
95	if _, exists := raw["engine"]; exists {
96		for _, section := range []string{"rendering", "performance", "privacy", "runtime"} {
97			if _, hasLegacy := raw[section]; hasLegacy {
98				return false, fmt.Errorf(
99					"mixed old/new config: [engine] coexists with legacy [%s]; "+
100						"remove [rendering], [performance], [privacy], [runtime]",
101					section,
102				)
103			}
104		}
105		// Check for deprecated media keys that moved to [engine.webkit].
106		if mediaRaw, ok := raw["media"].(map[string]any); ok {
107			for _, key := range []string{"force_vsync", "gl_rendering_mode", "gstreamer_debug_level"} {
108				if _, has := mediaRaw[key]; has {
109					return false, fmt.Errorf(
110						"mixed old/new config: [engine] coexists with deprecated "+
111							"media.%s; remove it from [media] (now in [engine.webkit])",
112						key,
113					)
114				}
115			}
116		}
117		return false, nil // already migrated
118	}
119
120	if !hasOldEngineSections(raw) {
121		return false, nil
122	}
123
124	applyEngineMappings(raw)
125
126	out, marshalErr := toml.Marshal(raw)
127	if marshalErr != nil {
128		return false, marshalErr
129	}
130
131	if err := atomicWriteFile(configFile, string(out), configFilePermissions); err != nil {
132		return false, err
133	}
134
135	return true, nil
136}
137
138// readRawTOML reads a TOML file into a generic map.
139func readRawTOML(path string) (map[string]any, error) {
140	data, err := os.ReadFile(path)
141	if err != nil {
142		return nil, err
143	}
144	var raw map[string]any
145	if err := toml.Unmarshal(data, &raw); err != nil {
146		return nil, err
147	}
148	return raw, nil
149}
150
151// hasOldEngineSections checks if the raw config has old sections that need migration.
152func hasOldEngineSections(raw map[string]any) bool {
153	for _, section := range []string{"rendering", "performance", "privacy", "runtime"} {
154		if _, exists := raw[section]; exists {
155			return true
156		}
157	}
158	if mediaAny, exists := raw["media"]; exists {
159		if media, ok := mediaAny.(map[string]any); ok {
160			for _, f := range []string{"force_vsync", "gl_rendering_mode", "gstreamer_debug_level"} {
161				if _, has := media[f]; has {
162					return true
163				}
164			}
165		}
166	}
167	return false
168}
169
170// applyEngineMappings builds [engine]/[engine.webkit] from old sections and cleans up.
171func applyEngineMappings(raw map[string]any) {
172	engineMap := make(map[string]any)
173	webkitMap := make(map[string]any)
174
175	for _, m := range engineUniversalMappings {
176		if val := getSectionField(raw, m.oldSection, m.oldField); val != nil {
177			engineMap[m.newField] = val
178		}
179	}
180	for _, m := range engineWebkitMappings {
181		if val := getSectionField(raw, m.oldSection, m.oldField); val != nil {
182			webkitMap[m.newField] = val
183		}
184	}
185
186	engineMap["type"] = "webkit"
187	if len(webkitMap) > 0 {
188		engineMap["webkit"] = webkitMap
189	}
190	raw["engine"] = engineMap
191
192	for _, section := range oldSectionsToRemove {
193		delete(raw, section)
194	}
195
196	stripMigratedMediaFields(raw)
197}
198
199// stripMigratedMediaFields removes GStreamer fields from [media] while keeping the rest.
200func stripMigratedMediaFields(raw map[string]any) {
201	mediaAny, exists := raw["media"]
202	if !exists {
203		return
204	}
205	media, ok := mediaAny.(map[string]any)
206	if !ok {
207		return
208	}
209	delete(media, "force_vsync")
210	delete(media, "gl_rendering_mode")
211	delete(media, "gstreamer_debug_level")
212	if len(media) == 0 {
213		delete(raw, "media")
214	}
215}
216
217// getSectionField retrieves a field value from a named section in the raw config map.
218func getSectionField(raw map[string]any, section, field string) any {
219	sectionAny, exists := raw[section]
220	if !exists {
221		return nil
222	}
223	sectionMap, ok := sectionAny.(map[string]any)
224	if !ok {
225		return nil
226	}
227	val, exists := sectionMap[field]
228	if !exists {
229		return nil
230	}
231	return val
232}

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