1package ui
2
3import (
4 "context"
5 "fmt"
6
7 "github.com/bnema/dumber/internal/application/usecase"
8 "github.com/bnema/dumber/internal/domain/entity"
9 "github.com/bnema/dumber/internal/logging"
10 "github.com/bnema/dumber/internal/ui/window"
11 "github.com/bnema/puregotk/v4/gtk"
12)
13
14func (a *App) createBrowserWindow(ctx context.Context, initialURL string) (*browserWindow, error) {
15 log := logging.FromContext(ctx)
16 log.Debug().
17 Str("url_host", logging.SafeURLHost(initialURL)).
18 Bool("using_factory", a.browserWindowFactory != nil).
19 Int("window_count_before", len(a.browserWindows)).
20 Msg("ui: create browser window started")
21
22 if a.browserWindowFactory != nil {
23 created, err := a.browserWindowFactory(ctx, initialURL)
24 if err != nil {
25 log.Warn().Err(err).
26 Str("url_host", logging.SafeURLHost(initialURL)).
27 Msg("ui: browser window factory failed")
28 return nil, err
29 }
30 created.ensureTabs()
31 a.wireBrowserWindowActivationTracking(created)
32 created.initChrome(ctx, a)
33 return created, nil
34 }
35
36 runtimeCfg := a.runtimeConfigSnapshot().UI
37 mainWindow, err := window.New(ctx, a.gtkApp, runtimeCfg.Workspace.TabBarPosition)
38 if err != nil {
39 log.Warn().Err(err).
40 Str("url_host", logging.SafeURLHost(initialURL)).
41 Msg("ui: GTK browser window shell creation failed")
42 return nil, err
43 }
44 browserWindow := &browserWindow{
45 id: a.generateWindowID(),
46 initialURL: initialURL,
47 tabs: entity.NewTabList(),
48 mainWindow: mainWindow,
49 }
50 a.wireBrowserWindowActivationTracking(browserWindow)
51 if a.mainWindow == nil {
52 a.mainWindow = mainWindow
53 }
54
55 closeRequestCb := func(_ gtk.Window) bool {
56 log.Info().Msg("browser window close requested")
57 a.removeBrowserWindow(browserWindow.id)
58 return false
59 }
60 mainWindow.Window().ConnectCloseRequest(&closeRequestCb)
61
62 a.initBrowserWindowOverlays(mainWindow, browserWindow, runtimeCfg)
63 a.wireBrowserWindowPermissionIndicator(browserWindow)
64 if a.kbDispatcher != nil {
65 a.initBrowserWindowInput(ctx, browserWindow)
66 }
67 if a.tabCoord != nil {
68 a.wireBrowserWindowTabBar(ctx, browserWindow)
69 }
70 browserWindow.initChrome(ctx, a)
71
72 // Apply GTK CSS styling from theme manager.
73 if a.deps == nil || a.deps.Theme == nil {
74 return browserWindow, nil
75 }
76 if display := mainWindow.Window().GetDisplay(); display != nil {
77 a.deps.Theme.ApplyToDisplay(ctx, display)
78 }
79 log.Debug().
80 Str("window_id", browserWindow.id).
81 Str("url_host", logging.SafeURLHost(initialURL)).
82 Msg("ui: create browser window completed")
83 return browserWindow, nil
84}
85
86func (a *App) openInitialBrowserWindowShell(ctx context.Context, initialURL string) error {
87 log := logging.FromContext(ctx)
88 created, err := a.createBrowserWindow(ctx, initialURL)
89 if err != nil {
90 log.Warn().Err(err).
91 Str("url_host", logging.SafeURLHost(initialURL)).
92 Int("window_count_after", len(a.browserWindows)).
93 Msg("ui: initial browser window shell creation failed")
94 return err
95 }
96 a.registerBrowserWindow(created)
97 log.Debug().
98 Str("window_id", created.id).
99 Str("url_host", logging.SafeURLHost(initialURL)).
100 Msg("ui: initial browser window shell registered")
101 a.activateBrowserWindow(created)
102 return nil
103}
104
105func (a *App) openFreshWindow(ctx context.Context, url string) error {
106 log := logging.FromContext(ctx)
107 log.Debug().
108 Str("url_host", logging.SafeURLHost(url)).
109 Int("window_count_before", len(a.browserWindows)).
110 Bool("has_tab_coord", a.tabCoord != nil).
111 Bool("has_tabs_uc", a.tabsUC != nil).
112 Msg("ui: open fresh window started")
113
114 created, err := a.createBrowserWindow(ctx, url)
115 if err != nil {
116 log.Warn().Err(err).
117 Str("url_host", logging.SafeURLHost(url)).
118 Int("window_count_after", len(a.browserWindows)).
119 Msg("ui: open fresh window could not create browser window")
120 return err
121 }
122 log.Debug().
123 Str("window_id", created.id).
124 Str("url_host", logging.SafeURLHost(url)).
125 Msg("ui: open fresh window created browser window shell")
126 a.registerBrowserWindow(created)
127 log.Debug().
128 Str("window_id", created.id).
129 Int("window_count_after_register", len(a.browserWindows)).
130 Msg("ui: open fresh window registered browser window")
131
132 var openErr error
133 path := "tabs_uc"
134 if a.tabCoord != nil {
135 path = "tab_coord"
136 log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tab coordinator")
137 openErr = a.openFreshWindowWithTabCoord(ctx, url, created)
138 } else if a.tabsUC == nil {
139 log.Debug().Str("window_id", created.id).Msg("ui: activating fresh browser window without tab use case")
140 a.activateBrowserWindow(created)
141 return nil
142 } else {
143 log.Debug().Str("window_id", created.id).Msg("ui: creating initial tab for fresh window via tabs use case")
144 openErr = a.openFreshWindowWithTabsUC(ctx, url, created)
145 }
146 if openErr != nil {
147 log.Warn().Err(openErr).
148 Str("window_id", created.id).
149 Str("path", path).
150 Int("window_count_after", len(a.browserWindows)).
151 Msg("ui: open fresh window initial tab creation failed")
152 return openErr
153 }
154 log.Debug().
155 Str("window_id", created.id).
156 Str("path", path).
157 Msg("ui: activating fresh browser window after tab creation")
158 a.activateBrowserWindow(created)
159 return nil
160}
161
162func (a *App) openFreshWindowWithTabCoord(ctx context.Context, url string, created *browserWindow) error {
163 log := logging.FromContext(ctx)
164 previousTarget := a.tabCoord.CurrentTarget()
165 target := a.ensureTabTargetForBrowserWindow(created)
166 a.tabCoord.SetCurrentTarget(target)
167 defer func() {
168 a.tabCoord.SetCurrentTarget(previousTarget)
169 }()
170
171 log.Debug().
172 Str("window_id", created.id).
173 Str("url_host", logging.SafeURLHost(url)).
174 Msg("ui: tab coordinator create started for fresh window")
175 createdTab, createErr := a.tabCoord.Create(ctx, target, url)
176 if createErr != nil {
177 log.Warn().Err(createErr).
178 Str("window_id", created.id).
179 Msg("ui: tab coordinator create failed for fresh window; rolling back window")
180 a.removeBrowserWindow(created.id)
181 if created.mainWindow != nil {
182 created.mainWindow.Destroy()
183 }
184 return createErr
185 }
186 log.Debug().
187 Str("window_id", created.id).
188 Str("tab_id", string(createdTab.ID)).
189 Msg("ui: tab coordinator create completed for fresh window")
190 if a.workspaceViews[createdTab.ID] == nil {
191 log.Warn().
192 Str("window_id", created.id).
193 Str("tab_id", string(createdTab.ID)).
194 Msg("ui: fresh window tab has no workspace view; rolling back window")
195 if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {
196 createdTabs.Remove(createdTab.ID)
197 }
198 if created.mainWindow != nil {
199 if tabBar := created.mainWindow.TabBar(); tabBar != nil {
200 tabBar.RemoveTab(createdTab.ID)
201 if activeTab := a.activeTabForBrowserWindow(created); activeTab != nil {
202 tabBar.SetActive(activeTab.ID)
203 }
204 }
205 }
206 a.removeBrowserWindow(created.id)
207 if created.mainWindow != nil {
208 created.mainWindow.Destroy()
209 }
210 return fmt.Errorf("workspace view not created for tab %s", createdTab.ID)
211 }
212 a.setBrowserWindowForTab(createdTab.ID, created)
213 // Tab was already created in the per-window TabList via tabCoord.Create;
214 // active state is managed by TabList.SetActive.
215 if created.mainWindow != nil {
216 created.mainWindow.Show()
217 }
218 return nil
219}
220
221func (a *App) openFreshWindowWithTabsUC(ctx context.Context, url string, created *browserWindow) error {
222 log := logging.FromContext(ctx)
223 log.Debug().
224 Str("window_id", created.id).
225 Str("url_host", logging.SafeURLHost(url)).
226 Msg("ui: tabs use case create started for fresh window")
227 output, err := a.tabsUC.Create(ctx, usecase.CreateTabInput{
228 TabList: a.tabs,
229 InitialURL: url,
230 })
231 if err != nil {
232 log.Warn().Err(err).
233 Str("window_id", created.id).
234 Msg("ui: tabs use case create failed for fresh window; rolling back window")
235 a.removeBrowserWindow(created.id)
236 if created.mainWindow != nil {
237 created.mainWindow.Destroy()
238 }
239 return err
240 }
241 log.Debug().
242 Str("window_id", created.id).
243 Str("tab_id", string(output.Tab.ID)).
244 Msg("ui: tabs use case create completed for fresh window")
245 a.setBrowserWindowForTab(output.Tab.ID, created)
246 // Mirror the tabsUC-created tab into the new browser window's tab list.
247 // This path uses the tabs usecase directly instead of tabCoord.Create,
248 // so the per-window TabList must be populated explicitly.
249 a.ensureTabListForBrowserWindow(created).Add(output.Tab)
250 if a.widgetFactory != nil || a.workspaceViewCreateOverride != nil {
251 a.createWorkspaceView(ctx, output.Tab)
252 a.switchWorkspaceView(ctx, output.Tab.ID)
253 }
254 if a.workspaceViews[output.Tab.ID] == nil {
255 log.Warn().
256 Str("window_id", created.id).
257 Str("tab_id", string(output.Tab.ID)).
258 Msg("ui: fresh window tabs use case tab has no workspace view; rolling back window")
259 if createdTabs := a.tabListForBrowserWindow(created); createdTabs != nil {
260 createdTabs.Remove(output.Tab.ID)
261 }
262 if a.tabs != nil {
263 a.tabs.Remove(output.Tab.ID)
264 }
265 if created.mainWindow != nil {
266 if tabBar := created.mainWindow.TabBar(); tabBar != nil {
267 tabBar.RemoveTab(output.Tab.ID)
268 if activeTab := a.activeTabForBrowserWindow(created); activeTab != nil {
269 tabBar.SetActive(activeTab.ID)
270 }
271 }
272 }
273 a.removeBrowserWindow(created.id)
274 if created.mainWindow != nil {
275 created.mainWindow.Destroy()
276 }
277 return fmt.Errorf("workspace view not created for tab %s", output.Tab.ID)
278 }
279 if created.mainWindow != nil {
280 created.mainWindow.Show()
281 }
282 return nil
283}