Class: Depot::GUI::MainWindow

Inherits:
RubyQt6::Bando::QMainWindow
  • Object
show all
Defined in:
lib/depot/gui/main_window.rb

Instance Method Summary collapse

Constructor Details

#initializeMainWindow

Returns a new instance of MainWindow.



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
# File 'lib/depot/gui/main_window.rb', line 160

def initialize
  super()
  @store = ManifestStore.new
  @customizer = AppCustomizer.new(store: @store)
  @settings = Settings.new
  @current_path = nil
  @current_inspection = nil

  set_window_title("Depot")
  resize(980, 660)
  set_window_icon(QIcon.new(Assets.logo_path)) if File.exist?(Assets.logo_path)

  build_ui
  load_settings_controls
  apply_theme(@settings.load.fetch("theme", "system"))
  refresh_installed
end

Instance Method Details

#browse_fileObject



178
179
180
181
182
183
184
185
186
# File 'lib/depot/gui/main_window.rb', line 178

def browse_file
  file = QFileDialog.get_open_file_name(
    self,
    "Choose Software",
    Dir.home,
    "Software packages (*.AppImage *.appimage *.deb *.rpm *.flatpakref *.tar.gz *.tgz *.tar.xz *.txz *.tar.zst *.tzst *.zip);;All files (*)"
  )
  load_input(file) if file && !file.empty?
end

#change_icon_selectedObject



317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
# File 'lib/depot/gui/main_window.rb', line 317

def change_icon_selected
  manifest = selected_manifest
  return warn_dialog("Select an installed app first.") unless manifest

  file = QFileDialog.get_open_file_name(
    self,
    "Choose Icon",
    Dir.home,
    "Icons (*.png *.svg *.xpm);;All files (*)"
  )
  return unless file && !file.empty?

  result = @customizer.change_icon(manifest.fetch("app_id"), file)
  if result.ok?
    refresh_installed
    status_bar.show_message(QString.new("Updated icon for #{result.value.fetch("display_name")}"))
  else
    warn_dialog(result.error)
  end
end

#change_page(index) ⇒ Object



544
545
546
547
548
# File 'lib/depot/gui/main_window.rb', line 544

def change_page(index)
  @pages.set_current_index(index)
  refresh_installed if index == 1
  refresh_updates if index == 2
end

#change_title_selectedObject



291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
# File 'lib/depot/gui/main_window.rb', line 291

def change_title_selected
  manifest = selected_manifest
  return warn_dialog("Select an installed app first.") unless manifest

  ok = QBool.new
  title = QInputDialog.get_text(
    self,
    QString.new("Change Title"),
    QString.new("Application title:"),
    QLineEdit::Normal,
    QString.new(manifest.fetch("display_name")),
    ok
  )
  return unless ok.ok? && title

  result = @customizer.rename(manifest.fetch("app_id"), title.to_s)
  if result.ok?
    refresh_installed
    status_bar.show_message(QString.new("Updated #{result.value.fetch("display_name")}"))
  else
    warn_dialog(result.error)
  end
rescue StandardError => e
  unexpected_error(e)
end

#info_selectedObject



264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
# File 'lib/depot/gui/main_window.rb', line 264

def info_selected
  manifest = selected_manifest
  return warn_dialog("Select an installed app first.") unless manifest

  custom = manifest.fetch("customizations", {})
  custom_icon = manifest["custom_icon"]
  QMessageBox.information(
    self,
    manifest.fetch("display_name"),
    [
      "App ID: #{manifest.fetch("app_id")}",
      "Name: #{manifest.fetch("display_name")}",
      "Default name: #{manifest["default_display_name"] || manifest.fetch("display_name")}",
      "Backend: #{manifest.fetch("backend")}",
      "Executable: #{manifest.fetch("installed_executable")}",
      "Desktop entry: #{manifest["desktop_entry"] || "none"}",
      "Icon: #{active_icon_summary(manifest)}",
      "Sandbox: #{Sandbox.summary(manifest, @settings.load)}",
      "Custom title: #{custom["display_name"] ? "yes" : "no"}",
      "Custom icon: #{custom_icon ? custom_icon["path"] : "no"}",
      "Source: #{manifest.fetch("install_source")}",
      "Update source: #{manifest.dig("update", "source") || "none"}",
      "Installed: #{manifest.fetch("installed_at", "unknown")}"
    ].join("\n")
  )
end

#inspect_currentObject



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
# File 'lib/depot/gui/main_window.rb', line 188

def inspect_current
  return warn_dialog("Choose software first.") unless @current_path

  result = Inspector.inspect(@current_path, checksum: false)
  unless result.ok?
    @current_inspection = nil
    @detected_label.set_text("Could not inspect this file")
    @detected_label.set_visible(true)
    @summary.set_plain_text(result.error)
    @install_button.set_enabled(false)
    return
  end

  @current_inspection = result.value
  @detected_label.set_text(detected_summary(@current_inspection))
  @detected_label.set_visible(true)
  @summary.set_plain_text(summary_for(@current_inspection))
  @install_button.set_enabled(installable_inspection?(@current_inspection))
end

#install_currentObject



208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
# File 'lib/depot/gui/main_window.rb', line 208

def install_current
  return warn_dialog("Choose software first.") unless @current_path

  inspect_current unless @current_inspection
  return warn_dialog("No installer backend is available for this format yet.") unless installable_inspection?(@current_inspection)

  answer = QMessageBox.question(
    self,
    "Install Software",
    install_prompt(@current_inspection),
    QMessageBox::Yes | QMessageBox::No
  )
  return unless answer == QMessageBox::Yes

  result = with_install_progress("Installing #{install_progress_name(@current_inspection)}...\n\nThis may take a while.") do
    Installer.install(@current_path, settings: @settings.load)
  end
  if result.ok?
    manifest = result.value
    QMessageBox.information(self, "Installed", "Installed #{manifest.fetch("display_name")} as #{manifest.fetch("app_id")}.")
    @current_path = nil
    @current_inspection = nil
    @path_edit.set_text("")
    @detected_label.set_text("")
    @detected_label.set_visible(false)
    @summary.set_plain_text("")
    @install_button.set_enabled(false)
    refresh_installed
    @sidebar.set_current_row(1)
  else
    warn_dialog(result.error)
  end
end

#launch_selectedObject



254
255
256
257
258
259
260
261
262
# File 'lib/depot/gui/main_window.rb', line 254

def launch_selected
  manifest = selected_manifest
  return warn_dialog("Select an installed app first.") unless manifest

  executable = Sandbox.launch_path(manifest, @settings.load)
  Process.spawn(executable, pgroup: true)
rescue SystemCallError => e
  warn_dialog("Could not launch app: #{e.message}")
end

#load_input(path) ⇒ Object



550
551
552
553
554
# File 'lib/depot/gui/main_window.rb', line 550

def load_input(path)
  @current_path = path
  @path_edit.set_text(path)
  inspect_current
end

#refresh_installedObject



242
243
244
245
246
247
248
249
250
251
252
# File 'lib/depot/gui/main_window.rb', line 242

def refresh_installed
  @apps_table.set_row_count(0)
  @store.all.each_with_index do |manifest, row|
    @apps_table.insert_row(row)
    @apps_table.set_item(row, 0, QTableWidgetItem.new(manifest.fetch("app_id")))
    @apps_table.set_item(row, 1, QTableWidgetItem.new(manifest.fetch("display_name")))
    @apps_table.set_item(row, 2, QTableWidgetItem.new(manifest.fetch("backend")))
    @apps_table.set_item(row, 3, QTableWidgetItem.new(manifest.fetch("installed_at", "")))
  end
  @apps_table.resize_columns_to_contents
end

#refresh_updatesObject



441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
# File 'lib/depot/gui/main_window.rb', line 441

def refresh_updates
  return unless @updates_table

  enabled = @settings.load.fetch("updates_enabled", true)
  @updates_status.set_text(enabled ? "Updates are enabled." : "Updates are disabled in Settings.")
  @updates_table.set_row_count(0)
  Updater.new(store: @store, settings: @settings).records.each_with_index do |record, row|
    @updates_table.insert_row(row)
    @updates_table.set_item(row, 0, QTableWidgetItem.new(record.fetch("app_id")))
    @updates_table.set_item(row, 1, QTableWidgetItem.new(record.fetch("display_name")))
    @updates_table.set_item(row, 2, QTableWidgetItem.new(record.fetch("method")))
    @updates_table.set_item(row, 3, QTableWidgetItem.new(record.fetch("status")))
    @updates_table.set_item(row, 4, QTableWidgetItem.new(record.fetch("last_updated_at").to_s))
  end
  @updates_table.resize_columns_to_contents
end

#reinstall_selectedObject



404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
# File 'lib/depot/gui/main_window.rb', line 404

def reinstall_selected
  manifest = selected_manifest
  return warn_dialog("Select an installed app first.") unless manifest

  source = manifest["install_source"]
  return warn_dialog("Depot does not know the original installer path for this app.") if source.to_s.empty?
  source = resolved_install_source(source)
  return warn_dialog("Original installer is missing: #{manifest["install_source"]}") unless source

  answer = QMessageBox.question(
    self,
    "Reinstall",
    "Reinstall #{manifest.fetch("display_name")} from its original installer?\n\nDepot will remove and recreate its managed files, desktop entry, icons, and manifest.",
    QMessageBox::Yes | QMessageBox::No
  )
  return unless answer == QMessageBox::Yes

  uninstall = nil
  install = nil
  with_install_progress("Reinstalling #{manifest.fetch("display_name")}...\n\nThis may take a while.") do
    uninstall = Uninstaller.uninstall(manifest.fetch("app_id"))
    install = Installer.install(source, settings: @settings.load) if uninstall.ok?
  end
  return warn_dialog(uninstall.error) unless uninstall.ok?

  if install.ok?
    refreshed = install.value
    QMessageBox.information(self, "Reinstalled", "Reinstalled #{refreshed.fetch("display_name")} as #{refreshed.fetch("app_id")}.")
    refresh_installed
  else
    warn_dialog("Depot removed the old install, but reinstall failed: #{install.error}")
    refresh_installed
  end
rescue StandardError => e
  unexpected_error(e)
end

#reset_selectedObject



362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
# File 'lib/depot/gui/main_window.rb', line 362

def reset_selected
  manifest = selected_manifest
  return warn_dialog("Select an installed app first.") unless manifest

  answer = QMessageBox.question(
    self,
    "Reset Properties",
    "Reset #{manifest.fetch("display_name")} to its original title and icon?",
    QMessageBox::Yes | QMessageBox::No
  )
  return unless answer == QMessageBox::Yes

  result = @customizer.reset(manifest.fetch("app_id"))
  if result.ok?
    refresh_installed
    status_bar.show_message(QString.new("Reset properties for #{result.value.fetch("display_name")}"))
  else
    warn_dialog(result.error)
  end
end

#sandbox_selectedObject



338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
# File 'lib/depot/gui/main_window.rb', line 338

def sandbox_selected
  manifest = selected_manifest
  return warn_dialog("Select an installed app first.") unless manifest

  if manifest["backend"] == "flatpak"
    QMessageBox.information(self, "Sandbox", "Flatpak manages sandboxing for #{manifest.fetch("display_name")}.\n\nDepot will show Flatpak permissions in a later permission editor.")
    return
  end
  return warn_dialog("Depot sandboxing is available for AppImage, portable Debian, portable RPM, and portable archive apps.") unless Sandbox.portable?(manifest)

  dialog = SandboxDialog.new(self, manifest, @settings.load)
  return unless dialog.exec == QDialog::Accepted

  result = Sandbox.set(manifest.fetch("app_id"), dialog.values, store: @store, settings: @settings.load)
  if result.ok?
    refresh_installed
    status_bar.show_message(QString.new("Updated sandbox for #{manifest.fetch("display_name")}"))
  else
    warn_dialog(result.error)
  end
rescue StandardError => e
  unexpected_error(e)
end

#save_settingsObject



527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
# File 'lib/depot/gui/main_window.rb', line 527

def save_settings
  values = {
    "warning_verbosity" => @warning_combo.current_text,
    "theme" => @theme_combo.current_text,
    "default_install_location" => "user",
    "sandbox_preference" => @sandbox_combo.current_text,
    "sandbox_profile" => @sandbox_profile_combo.current_text,
    "sandbox_home_access" => @sandbox_home_combo.current_text,
    "sandbox_network" => @sandbox_network_check.checked?,
    "desktop_integration" => @desktop_check.checked?,
    "updates_enabled" => @updates_check.checked?
  }
  @settings.save(values)
  apply_theme(values.fetch("theme"))
  QMessageBox.information(self, "Settings", "Depot settings were saved.")
end

#set_update_source_selectedObject



458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
# File 'lib/depot/gui/main_window.rb', line 458

def set_update_source_selected
  app_id = selected_update_app_id
  return warn_dialog("Select an app first.") unless app_id

  manifest = @store.find(app_id)
  current = manifest&.dig("update", "source").to_s
  ok = QBool.new
  url = QInputDialog.get_text(
    self,
    QString.new("Update Source"),
    QString.new("HTTPS update URL:"),
    QLineEdit::Normal,
    QString.new(current.start_with?("https://") ? current : ""),
    ok
  )
  return unless ok.ok? && url

  result = Updater.new(store: @store, settings: @settings).set_source(app_id, url.to_s.strip)
  if result.ok?
    refresh_updates
    status_bar.show_message(QString.new("Updated source for #{app_id}"))
  else
    warn_dialog(result.error)
  end
rescue StandardError => e
  unexpected_error(e)
end

#uninstall_selectedObject



383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
# File 'lib/depot/gui/main_window.rb', line 383

def uninstall_selected
  manifest = selected_manifest
  return warn_dialog("Select an installed app first.") unless manifest

  answer = QMessageBox.question(
    self,
    "Uninstall",
    "Remove #{manifest.fetch("display_name")} and its Depot-created desktop integration?",
    QMessageBox::Yes | QMessageBox::No
  )
  return unless answer == QMessageBox::Yes

  result = Uninstaller.uninstall(manifest.fetch("app_id"))
  if result.ok?
    QMessageBox.information(self, "Uninstalled", "Removed #{manifest.fetch("display_name")}.")
    refresh_installed
  else
    warn_dialog(result.error)
  end
end

#update_allObject



504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
# File 'lib/depot/gui/main_window.rb', line 504

def update_all
  return warn_dialog("Updates are disabled in Settings.") unless @settings.load.fetch("updates_enabled", true)

  answer = QMessageBox.question(
    self,
    "Update All",
    "Update every app that Depot knows how to update?",
    QMessageBox::Yes | QMessageBox::No
  )
  return unless answer == QMessageBox::Yes

  result = with_install_progress("Updating apps...\n\nThis may take a while.") do
    Updater.new(store: @store, settings: @settings).update_all
  end
  if result.ok?
    QMessageBox.information(self, "Updates", "Finished updating apps.")
  else
    warn_dialog(([result.error] + result.warnings).join("\n"))
  end
  refresh_updates
  refresh_installed
end

#update_selectedObject



486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
# File 'lib/depot/gui/main_window.rb', line 486

def update_selected
  return warn_dialog("Updates are disabled in Settings.") unless @settings.load.fetch("updates_enabled", true)

  app_id = selected_update_app_id
  return warn_dialog("Select an app to update first.") unless app_id

  result = with_install_progress("Updating #{app_id}...\n\nThis may take a while.") do
    Updater.new(store: @store, settings: @settings).update(app_id)
  end
  if result.ok?
    QMessageBox.information(self, "Updated", "Updated #{app_id}.")
    refresh_updates
    refresh_installed
  else
    warn_dialog(result.error)
  end
end