mirror of
https://github.com/morgan9e/warehouse
synced 2026-04-14 00:04:08 +09:00
Fresh start
Remove pretty much everything in preparation of complete overhaul for 2.X
This commit is contained in:
@@ -1,84 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $DowngradeWindow: Adw.Dialog {
|
||||
content-width: 500;
|
||||
content-height: 450;
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {
|
||||
}
|
||||
|
||||
[bottom]
|
||||
ActionBar action_bar {
|
||||
revealed: false;
|
||||
|
||||
[center]
|
||||
Button apply_button {
|
||||
visible: false;
|
||||
valign: end;
|
||||
halign: center;
|
||||
margin-top: 6;
|
||||
margin-bottom: 6;
|
||||
Adw.ButtonContent {
|
||||
label: _("Downgrade");
|
||||
icon-name: "arrow-turn-left-down-symbolic";
|
||||
}
|
||||
|
||||
styles [
|
||||
"suggested-action",
|
||||
"pill"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
content: Adw.ToastOverlay toast_overlay {
|
||||
Stack main_stack {
|
||||
Box loading {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label loading_label {
|
||||
label: _("Fetching Releases");
|
||||
styles [
|
||||
"title-1",
|
||||
"title"
|
||||
]
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("This could take a while");
|
||||
styles ["description", "body"]
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesPage outerbox {
|
||||
Adw.PreferencesGroup {
|
||||
Adw.SwitchRow mask_row {
|
||||
title: _("Disable Updates");
|
||||
active: true;
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup versions_group {
|
||||
title: _("Select a Release");
|
||||
description: _("This will uninstall the current release and install the chosen one instead. Note that downgrading can cause issues.");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,83 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $FilterWindow: Adw.Dialog {
|
||||
title: _("Set Filters");
|
||||
content-width: 500;
|
||||
content-height: 450;
|
||||
|
||||
child:
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {
|
||||
}
|
||||
|
||||
content: Adw.ToastOverlay toast_overlay {
|
||||
Stack main_stack {
|
||||
Overlay main_overlay {
|
||||
ScrolledWindow scrolled_window {
|
||||
vexpand: true;
|
||||
|
||||
Adw.Clamp {
|
||||
Box outerbox {
|
||||
orientation: vertical;
|
||||
|
||||
ListBox install_type_list {
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ActionRow apps_row {
|
||||
title: _("Show Apps");
|
||||
|
||||
Switch show_apps_switch {
|
||||
valign: center;
|
||||
}
|
||||
|
||||
activatable-widget: show_apps_switch;
|
||||
}
|
||||
|
||||
Adw.ActionRow show_runtimes_row {
|
||||
title: _("Show Runtimes");
|
||||
|
||||
Switch show_runtimes_switch {
|
||||
valign: center;
|
||||
}
|
||||
|
||||
activatable-widget: show_runtimes_switch;
|
||||
}
|
||||
|
||||
Adw.ExpanderRow remotes_expander {
|
||||
enable-expansion: true;
|
||||
title: _("Filter by Remotes");
|
||||
}
|
||||
|
||||
Adw.ExpanderRow runtimes_expander {
|
||||
enable-expansion: true;
|
||||
title: _("Filter by Runtimes");
|
||||
}
|
||||
}
|
||||
|
||||
Button reset_button {
|
||||
visible: true;
|
||||
margin-bottom: 18;
|
||||
halign: center;
|
||||
label: _("Reset Filters");
|
||||
styles ["pill"]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -1,135 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $OrphansWindow: Adw.Dialog {
|
||||
content-width: 500;
|
||||
content-height: 450;
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {
|
||||
[start]
|
||||
ToggleButton search_button {
|
||||
icon-name: "system-search-symbolic";
|
||||
tooltip-text: _("Search List");
|
||||
}
|
||||
|
||||
[end]
|
||||
Button oepn_folder_button {
|
||||
icon-name: "document-open-symbolic";
|
||||
tooltip-text: _("Open Data Folder");
|
||||
}
|
||||
}
|
||||
|
||||
[top]
|
||||
SearchBar search_bar {
|
||||
search-mode-enabled: bind search_button.active bidirectional;
|
||||
key-capture-widget: main_toolbar_view;
|
||||
|
||||
Adw.Clamp {
|
||||
maximum-size: 577;
|
||||
hexpand: true;
|
||||
|
||||
SearchEntry search_entry {}
|
||||
}
|
||||
}
|
||||
|
||||
content: Adw.ToastOverlay toast_overlay {
|
||||
Overlay main_overlay {
|
||||
Stack main_stack {
|
||||
Box main_box {
|
||||
orientation: vertical;
|
||||
|
||||
ScrolledWindow scrolled_window {
|
||||
vexpand: true;
|
||||
|
||||
Adw.Clamp {
|
||||
ListBox list_of_data {
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box installing {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("Installing");
|
||||
|
||||
styles [
|
||||
"title-1",
|
||||
"title"
|
||||
]
|
||||
}
|
||||
|
||||
Label installing_status {
|
||||
label: "";
|
||||
justify: center;
|
||||
|
||||
styles [
|
||||
"description",
|
||||
"body"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Adw.StatusPage no_data {
|
||||
icon-name: "check-plain-symbolic";
|
||||
title: _("No Leftover Data");
|
||||
description: _("There is no leftover user data");
|
||||
}
|
||||
|
||||
Adw.StatusPage no_results {
|
||||
icon-name: "system-search-symbolic";
|
||||
title: _("No Results Found");
|
||||
description: _("Try a different search term");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[bottom]
|
||||
ActionBar action_bar {
|
||||
[start]
|
||||
ToggleButton select_all_button {
|
||||
label: _("Select All");
|
||||
}
|
||||
|
||||
[end]
|
||||
Button trash_button {
|
||||
label: _("Trash");
|
||||
sensitive: false;
|
||||
}
|
||||
|
||||
[end]
|
||||
Button install_button {
|
||||
label: _("Install");
|
||||
sensitive: false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $PopularRemotesWindow: Adw.Window {
|
||||
default-width: 450;
|
||||
default-height: 530;
|
||||
title: "";
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
HeaderBar header_bar {}
|
||||
|
||||
content: Adw.ToastOverlay toast_overlay {
|
||||
vexpand: true;
|
||||
|
||||
Adw.StatusPage {
|
||||
valign: start;
|
||||
title: _("Add Remote");
|
||||
description: _("Choose from a list of popular remotes or add a new one");
|
||||
|
||||
Adw.Clamp {
|
||||
Box {
|
||||
orientation: vertical;
|
||||
|
||||
ListBox list_of_remotes {
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
}
|
||||
|
||||
ListBox custom_list {
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
|
||||
Adw.ActionRow add_from_file {
|
||||
title: _("Add a Repo File");
|
||||
activatable: true;
|
||||
}
|
||||
|
||||
Adw.ActionRow custom_remote {
|
||||
title: _("Add a Custom Remote");
|
||||
activatable: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,164 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $PropertiesWindow: Adw.Dialog {
|
||||
content-width: 350;
|
||||
content-height: 999999;
|
||||
title: _("Properties");
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {}
|
||||
|
||||
content: Adw.ToastOverlay toast_overlay {
|
||||
Box {
|
||||
orientation: vertical;
|
||||
|
||||
Adw.Banner eol_app_banner {}
|
||||
|
||||
Adw.Banner eol_runtime_banner {}
|
||||
|
||||
Adw.Banner mask_banner {}
|
||||
|
||||
ScrolledWindow {
|
||||
Adw.Clamp {
|
||||
Box {
|
||||
orientation: vertical;
|
||||
hexpand: false;
|
||||
vexpand: true;
|
||||
spacing: 12;
|
||||
margin-top: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
margin-bottom: 12;
|
||||
|
||||
Image app_icon {
|
||||
pixel-size: 100;
|
||||
|
||||
styles [
|
||||
"icon-dropshadow"
|
||||
]
|
||||
}
|
||||
|
||||
Label name {
|
||||
wrap: true;
|
||||
|
||||
styles [
|
||||
"title-1"
|
||||
]
|
||||
}
|
||||
|
||||
Button description_button {
|
||||
styles [
|
||||
"title-4",
|
||||
"flat"
|
||||
]
|
||||
|
||||
Box {
|
||||
spacing: 12;
|
||||
|
||||
Label description {
|
||||
halign: start;
|
||||
wrap: true;
|
||||
hexpand: true;
|
||||
}
|
||||
Image {
|
||||
icon-name: "edit-copy-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup upper {
|
||||
Adw.ActionRow data_row {
|
||||
title: _("Loading User Data");
|
||||
|
||||
[suffix]
|
||||
Button open_data {
|
||||
icon-name: "document-open-symbolic";
|
||||
tooltip-text: _("Open User Data Folder");
|
||||
valign: center;
|
||||
visible: false;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
}
|
||||
|
||||
[suffix]
|
||||
Button trash_data {
|
||||
icon-name: "user-trash-symbolic";
|
||||
tooltip-text: _("Trash User Data");
|
||||
valign: center;
|
||||
visible: false;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
}
|
||||
|
||||
[suffix]
|
||||
Spinner spinner {
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
styles["property"]
|
||||
}
|
||||
|
||||
Adw.ActionRow view_apps {
|
||||
title: _("Show Apps Using This Runtime");
|
||||
activatable: true;
|
||||
visible: false;
|
||||
|
||||
[suffix]
|
||||
Image {
|
||||
icon-name: "funnel-symbolic";
|
||||
}
|
||||
}
|
||||
|
||||
Adw.ActionRow runtime {
|
||||
title: _("Runtime");
|
||||
|
||||
[suffix]
|
||||
Button runtime_properties {
|
||||
icon-name: "info-symbolic";
|
||||
tooltip-text: _("View Properties");
|
||||
valign: center;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
}
|
||||
|
||||
[suffix]
|
||||
Button runtime_copy {
|
||||
icon-name: "edit-copy-symbolic";
|
||||
tooltip-text: _("Copy");
|
||||
valign: center;
|
||||
|
||||
styles [
|
||||
"flat"
|
||||
]
|
||||
}
|
||||
|
||||
styles["property"]
|
||||
}
|
||||
|
||||
Adw.ActionRow details {
|
||||
title: _("Show Details in Store");
|
||||
activatable: true;
|
||||
|
||||
[suffix]
|
||||
Image {
|
||||
icon-name: "adw-external-link-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Adw.PreferencesGroup lower {}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,95 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $RemotesWindow: Adw.Dialog {
|
||||
title: _("Manage Remotes");
|
||||
content-width: 500;
|
||||
content-height: 450;
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {
|
||||
Button refresh {
|
||||
icon-name: "view-refresh-symbolic";
|
||||
tooltip-text: _("Refresh List of Remotes");
|
||||
}
|
||||
}
|
||||
|
||||
content: Adw.ToastOverlay toast_overlay {
|
||||
Stack stack {
|
||||
|
||||
Adw.PreferencesPage main_group {
|
||||
Adw.PreferencesGroup remotes_list {
|
||||
title: _("Installed Remotes");
|
||||
header-suffix: ToggleButton show_disabled_button {
|
||||
Adw.ButtonContent show_disabled_button_button_content {
|
||||
icon-name: "eye-not-looking-symbolic";
|
||||
label: _("Show Disabled");
|
||||
styles["flat"]
|
||||
}
|
||||
// spacing: 6;
|
||||
// margin-end: 6;
|
||||
// Label {
|
||||
// label: _("Show Disabled");
|
||||
// styles["heading", "h4"]
|
||||
// }
|
||||
// Switch show_disabled {
|
||||
// valign: center;
|
||||
// }
|
||||
};
|
||||
Adw.ActionRow no_remotes {
|
||||
title: _("No Remotes Found");
|
||||
}
|
||||
}
|
||||
Adw.PreferencesGroup popular_remotes_list {
|
||||
title: _("Add a Popular Remote");
|
||||
visible: false;
|
||||
}
|
||||
Adw.PreferencesGroup manual_remotes_list {
|
||||
title: _("Add Other Remotes");
|
||||
Adw.ActionRow add_from_file {
|
||||
title: _("Add a Repo File");
|
||||
activatable: true;
|
||||
}
|
||||
|
||||
Adw.ActionRow custom_remote {
|
||||
title: _("Add a Custom Remote");
|
||||
activatable: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box adding {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("Adding Remote");
|
||||
|
||||
styles [
|
||||
"title-1",
|
||||
"title"
|
||||
]
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("This should only take a moment");
|
||||
styles ["description", "body"]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,193 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $SearchInstallWindow: Adw.Dialog {
|
||||
content-width: 500;
|
||||
content-height: 450;
|
||||
title: "";
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
content:
|
||||
Stack outer_stack {
|
||||
Adw.NavigationView nav_view {
|
||||
Adw.NavigationPage search_page {
|
||||
title: _("Search Criteria");
|
||||
Adw.ToastOverlay toast_overlay {
|
||||
Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
}
|
||||
content:
|
||||
Adw.StatusPage {
|
||||
title: _("Choose a Remote to Search");
|
||||
valign: start;
|
||||
child:
|
||||
Adw.Clamp {
|
||||
ListBox remotes_list {
|
||||
selection-mode: none;
|
||||
styles ["boxed-list"]
|
||||
}
|
||||
};
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Adw.NavigationPage results_page {
|
||||
title: _("Results");
|
||||
Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
}
|
||||
[bottom]
|
||||
ActionBar action_bar {
|
||||
revealed: false;
|
||||
[center]
|
||||
Button install_button {
|
||||
margin-top: 6;
|
||||
margin-bottom: 6;
|
||||
styles[
|
||||
"pill",
|
||||
"suggested-action"
|
||||
]
|
||||
|
||||
Adw.ButtonContent {
|
||||
label: _("Install");
|
||||
icon-name: "plus-large-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
[top]
|
||||
Adw.Clamp {
|
||||
Box search_box {
|
||||
margin-top: 4;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
margin-bottom: 6;
|
||||
SearchEntry search_entry {
|
||||
hexpand: true;
|
||||
}
|
||||
Button search_button {
|
||||
icon-name: "right-large-symbolic";
|
||||
tooltip-text: _("Start Search");
|
||||
}
|
||||
styles ["linked"]
|
||||
}
|
||||
}
|
||||
content:
|
||||
Stack inner_stack {
|
||||
|
||||
Adw.StatusPage blank_page {
|
||||
title: _("Search for Flatpaks");
|
||||
icon-name: "flatpak-symbolic";
|
||||
description: _("Search for Flatpaks that you want to install");
|
||||
}
|
||||
|
||||
Adw.StatusPage no_results {
|
||||
icon-name: "system-search-symbolic";
|
||||
title: _("No Results Found");
|
||||
description: _("Try a different search term");
|
||||
}
|
||||
|
||||
Box loading_page {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("Searching");
|
||||
|
||||
styles [
|
||||
"title-1",
|
||||
"title"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Adw.StatusPage too_many {
|
||||
icon-name: "error-symbolic";
|
||||
title: _("Too Many Results");
|
||||
description: _("Try being more specific with your search");
|
||||
}
|
||||
|
||||
ScrolledWindow results_scroll {
|
||||
vexpand: true;
|
||||
Adw.Clamp {
|
||||
ListBox results_list {
|
||||
margin-top: 6;
|
||||
margin-bottom: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
styles ["boxed-list"]
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Adw.ToolbarView installing {
|
||||
[top]
|
||||
Adw.HeaderBar {
|
||||
}
|
||||
content:
|
||||
Overlay overlay {
|
||||
[overlay]
|
||||
ProgressBar progress_bar {
|
||||
visible: false;
|
||||
can-target: false;
|
||||
styles ["osd"]
|
||||
}
|
||||
Box {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("Installing");
|
||||
|
||||
styles [
|
||||
"title-1",
|
||||
"title"
|
||||
]
|
||||
}
|
||||
|
||||
Label installing_status {
|
||||
label: "";
|
||||
justify: center;
|
||||
|
||||
styles [
|
||||
"description",
|
||||
"body"
|
||||
]
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $SnapshotsWindow: Adw.Dialog {
|
||||
content-width: 500;
|
||||
content-height: 455;
|
||||
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {
|
||||
[end]
|
||||
Button open_folder_button {
|
||||
icon-name: "document-open-symbolic";
|
||||
tooltip-text: _("Open Snapshots Folder");
|
||||
}
|
||||
}
|
||||
|
||||
[bottom]
|
||||
ActionBar action_bar {
|
||||
[center]
|
||||
Button new_snapshot {
|
||||
halign: center;
|
||||
sensitive: bind action_bar.revealed;
|
||||
margin-top: 6;
|
||||
margin-bottom: 6;
|
||||
styles[
|
||||
"pill",
|
||||
"suggested-action"
|
||||
]
|
||||
|
||||
Adw.ButtonContent {
|
||||
label: _("New Snapshot");
|
||||
icon-name: "plus-large-symbolic";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
content: Adw.ToastOverlay toast_overlay {
|
||||
Stack main_stack {
|
||||
ScrolledWindow outerbox {
|
||||
Adw.Clamp {
|
||||
ListBox snapshots_group {
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box loading {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label loading_label {
|
||||
styles [
|
||||
"title-1",
|
||||
"title"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Adw.StatusPage no_snapshots {
|
||||
title: _("No Snapshots");
|
||||
description: _("Snapshots are backups of the app's user data. They can be reapplied at any time.");
|
||||
icon-name: "clock-alt-symbolic";
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,299 +0,0 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $WarehouseWindow: Adw.ApplicationWindow {
|
||||
title: "Warehouse";
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
HeaderBar header_bar {
|
||||
[start]
|
||||
ToggleButton search_button {
|
||||
icon-name: "system-search-symbolic";
|
||||
tooltip-text: _("Search List");
|
||||
}
|
||||
|
||||
[start]
|
||||
Button filter_button {
|
||||
icon-name: "funnel-symbolic";
|
||||
tooltip-text: _("Filter List");
|
||||
}
|
||||
|
||||
[end]
|
||||
MenuButton main_menu {
|
||||
icon-name: "open-menu-symbolic";
|
||||
tooltip-text: _("Main Menu");
|
||||
menu-model: primary_menu;
|
||||
}
|
||||
|
||||
[end]
|
||||
ToggleButton batch_mode_button {
|
||||
icon-name: "selection-mode-symbolic";
|
||||
tooltip-text: _("Toggle Selection Mode");
|
||||
}
|
||||
}
|
||||
|
||||
[top]
|
||||
SearchBar search_bar {
|
||||
search-mode-enabled: bind search_button.active bidirectional;
|
||||
key-capture-widget: main_toolbar_view;
|
||||
|
||||
Adw.Clamp {
|
||||
maximum-size: 577;
|
||||
hexpand: true;
|
||||
|
||||
SearchEntry search_entry {}
|
||||
}
|
||||
}
|
||||
|
||||
content: Adw.ToastOverlay toast_overlay {
|
||||
Overlay main_overlay {
|
||||
Stack main_stack {
|
||||
Adw.StatusPage loading_flatpaks {
|
||||
icon-name: "clock-alt-symbolic";
|
||||
title: _("Loading Flatpaks");
|
||||
description: _("This should only take a moment");
|
||||
}
|
||||
|
||||
Box main_box {
|
||||
orientation: vertical;
|
||||
|
||||
ScrolledWindow scrolled_window {
|
||||
vexpand: true;
|
||||
|
||||
Adw.Clamp {
|
||||
ListBox flatpaks_list_box {
|
||||
margin-top: 12;
|
||||
margin-bottom: 12;
|
||||
margin-start: 12;
|
||||
margin-end: 12;
|
||||
hexpand: true;
|
||||
valign: start;
|
||||
selection-mode: none;
|
||||
|
||||
styles [
|
||||
"boxed-list"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Box installing {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("Installing");
|
||||
|
||||
styles [
|
||||
"title-1",
|
||||
"title"
|
||||
]
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("This could take a while");
|
||||
styles ["description", "body"]
|
||||
}
|
||||
}
|
||||
|
||||
Box uninstalling {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("Uninstalling");
|
||||
styles ["title-1", "title"]
|
||||
}
|
||||
|
||||
Label uninstalling_status {
|
||||
label: "";
|
||||
justify: center;
|
||||
styles ["description", "body"]
|
||||
}
|
||||
}
|
||||
|
||||
Box snapshotting {
|
||||
orientation: vertical;
|
||||
spacing: 10;
|
||||
margin-top: 40;
|
||||
margin-bottom: 20;
|
||||
halign: center;
|
||||
valign: center;
|
||||
|
||||
Spinner {
|
||||
margin-bottom: 35;
|
||||
width-request: 30;
|
||||
height-request: 30;
|
||||
opacity: 0.5;
|
||||
spinning: true;
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("Creating Snapshots");
|
||||
styles ["title-1", "title"]
|
||||
}
|
||||
|
||||
Label {
|
||||
label: _("This could take a while");
|
||||
styles ["description", "body"]
|
||||
}
|
||||
}
|
||||
|
||||
Adw.StatusPage no_flatpaks {
|
||||
icon-name: "error-symbolic";
|
||||
title: _("No Flatpaks Found");
|
||||
description: _("Warehouse cannot see the list of installed Flatpaks or the system has no Flatpaks installed");
|
||||
}
|
||||
|
||||
Adw.StatusPage no_matches {
|
||||
icon-name: "funnel-symbolic";
|
||||
title: _("No Flatpaks Match Filters");
|
||||
description: _("No installed Flatpak matches all of the currently applied filters");
|
||||
[child]
|
||||
Button reset_filters_button {
|
||||
label: _("Reset Filters");
|
||||
halign: center;
|
||||
styles["pill"]
|
||||
}
|
||||
}
|
||||
|
||||
Adw.StatusPage no_results {
|
||||
icon-name: "system-search-symbolic";
|
||||
title: _("No Results Found");
|
||||
description: _("Try a different search term");
|
||||
}
|
||||
|
||||
Adw.StatusPage refreshing {
|
||||
icon-name: "arrow-circular-top-right-symbolic";
|
||||
title: _("Refreshing List");
|
||||
description: _("This should only take a moment");
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
[bottom]
|
||||
ActionBar batch_mode_bar {
|
||||
revealed: false;
|
||||
|
||||
[start]
|
||||
ToggleButton batch_select_all_button {
|
||||
label: _("Select All");
|
||||
}
|
||||
|
||||
[end]
|
||||
Button batch_uninstall_button {
|
||||
icon-name: "cross-filled-symbolic";
|
||||
tooltip-text: _("Uninstall Selected Apps");
|
||||
}
|
||||
|
||||
[end]
|
||||
Button batch_clean_button {
|
||||
icon-name: "user-trash-symbolic";
|
||||
tooltip-text: _("Send Selected Apps' Data to the Trash");
|
||||
}
|
||||
|
||||
[end]
|
||||
MenuButton batch_copy_button {
|
||||
icon-name: "edit-copy-symbolic";
|
||||
tooltip-text: _("Open Copy Menu");
|
||||
menu-model: copy_menu;
|
||||
}
|
||||
|
||||
[end]
|
||||
Button batch_snapshot_button {
|
||||
icon-name: "clock-alt-symbolic";
|
||||
tooltip-text: _("Snapshot Selected Apps' Data");
|
||||
visible: true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menu primary_menu {
|
||||
section {
|
||||
item {
|
||||
label: _("Manage Leftover Data…");
|
||||
action: "app.manage-data-folders";
|
||||
}
|
||||
|
||||
/*item {
|
||||
label: _("_Preferences");
|
||||
action: "app.preferences";
|
||||
}*/
|
||||
item {
|
||||
label: _("Manage Remotes…");
|
||||
action: "app.show-remotes-window";
|
||||
}
|
||||
}
|
||||
section {
|
||||
item {
|
||||
label: _("Install From File…");
|
||||
action: "app.install-from-file";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Install From The Web…");
|
||||
action: "app.open-search-install";
|
||||
}
|
||||
}
|
||||
section {
|
||||
item {
|
||||
label: _("Refresh List");
|
||||
action: "app.refresh-list";
|
||||
}
|
||||
item {
|
||||
label: _("_Keyboard Shortcuts");
|
||||
action: "win.show-help-overlay";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_About Warehouse");
|
||||
action: "app.about";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
menu copy_menu {
|
||||
section {
|
||||
item {
|
||||
label: _("Copy Names");
|
||||
action: "win.copy-names";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Copy IDs");
|
||||
action: "win.copy-ids";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Copy Refs");
|
||||
action: "win.copy-refs";
|
||||
}
|
||||
}
|
||||
}
|
||||
21
po/POTFILES
21
po/POTFILES
@@ -1,23 +1,8 @@
|
||||
data/io.github.flattool.Warehouse.desktop.in
|
||||
data/io.github.flattool.Warehouse.metainfo.xml.in
|
||||
data/io.github.flattool.Warehouse.gschema.xml
|
||||
data/ui/downgrade.blp
|
||||
data/ui/filter.blp
|
||||
data/ui/orphans.blp
|
||||
data/ui/popular_remotes.blp
|
||||
data/ui/properties.blp
|
||||
data/ui/remotes.blp
|
||||
data/ui/search_install.blp
|
||||
data/ui/snapshots.blp
|
||||
data/ui/window.blp
|
||||
src/app_row_widget.py
|
||||
src/main_window/window.blp
|
||||
src/main_window/window.py
|
||||
src/common.py
|
||||
src/downgrade_window.py
|
||||
src/filter_window.py
|
||||
src/gtk/help-overlay.blp
|
||||
src/main.py
|
||||
src/orphans_window.py
|
||||
src/properties_window.py
|
||||
src/remotes_window.py
|
||||
src/snapshots_window.py
|
||||
src/window.py
|
||||
src/main.py
|
||||
923
po/warehouse.pot
923
po/warehouse.pot
@@ -8,7 +8,7 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: io.github.flattool.heliguy.Warehouse\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2024-07-03 14:05-0400\n"
|
||||
"POT-Creation-Date: 2024-07-03 16:27-0400\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -65,488 +65,44 @@ msgstr ""
|
||||
msgid "Reinstall apps that have leftover data"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/downgrade.blp:25 src/app_row_widget.py:360
|
||||
msgid "Downgrade"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/downgrade.blp:55
|
||||
msgid "Fetching Releases"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/downgrade.blp:63 data/ui/window.blp:107 data/ui/window.blp:162
|
||||
msgid "This could take a while"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/downgrade.blp:71 src/app_row_widget.py:299 src/window.py:494
|
||||
msgid "Disable Updates"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/downgrade.blp:77
|
||||
msgid "Select a Release"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/downgrade.blp:78
|
||||
msgid ""
|
||||
"This will uninstall the current release and install the chosen one instead. "
|
||||
"Note that downgrading can cause issues."
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/filter.blp:5 src/gtk/help-overlay.blp:19
|
||||
msgid "Set Filters"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/filter.blp:39
|
||||
msgid "Show Apps"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/filter.blp:49
|
||||
msgid "Show Runtimes"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/filter.blp:60
|
||||
msgid "Filter by Remotes"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/filter.blp:65
|
||||
msgid "Filter by Runtimes"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/filter.blp:73 data/ui/window.blp:179
|
||||
msgid "Reset Filters"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:14 data/ui/window.blp:12
|
||||
msgid "Search List"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:20
|
||||
msgid "Open Data Folder"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:81 data/ui/search_install.blp:171 data/ui/window.blp:98
|
||||
msgid "Installing"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:102
|
||||
msgid "No Leftover Data"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:103
|
||||
msgid "There is no leftover user data"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:108 data/ui/search_install.blp:87 data/ui/window.blp:187
|
||||
msgid "No Results Found"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:109 data/ui/search_install.blp:88 data/ui/window.blp:188
|
||||
msgid "Try a different search term"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:119 data/ui/window.blp:206
|
||||
msgid "Select All"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:124 src/window.py:199 src/window.py:281
|
||||
msgid "Trash"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/orphans.blp:130 data/ui/search_install.blp:54
|
||||
#: src/orphans_window.py:122 src/window.py:839
|
||||
msgid "Install"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/popular_remotes.blp:18
|
||||
msgid "Add Remote"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/popular_remotes.blp:19
|
||||
msgid "Choose from a list of popular remotes or add a new one"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/popular_remotes.blp:45 data/ui/remotes.blp:51
|
||||
msgid "Add a Repo File"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/popular_remotes.blp:50 data/ui/remotes.blp:56
|
||||
msgid "Add a Custom Remote"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:7
|
||||
msgid "Properties"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:73
|
||||
msgid "Loading User Data"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:78 src/app_row_widget.py:268
|
||||
#: src/orphans_window.py:258
|
||||
msgid "Open User Data Folder"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:90 src/app_row_widget.py:281
|
||||
msgid "Trash User Data"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:108
|
||||
msgid "Show Apps Using This Runtime"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:119
|
||||
msgid "Runtime"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:124 src/app_row_widget.py:156
|
||||
msgid "View Properties"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:135 src/app_row_widget.py:234 src/window.py:562
|
||||
msgid "Copy"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/properties.blp:147
|
||||
msgid "Show Details in Store"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:5 src/gtk/help-overlay.blp:42
|
||||
msgid "Manage Remotes"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:14
|
||||
msgid "Refresh List of Remotes"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:23
|
||||
msgid "Installed Remotes"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:27
|
||||
msgid "Show Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:41
|
||||
msgid "No Remotes Found"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:45
|
||||
msgid "Add a Popular Remote"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:49
|
||||
msgid "Add Other Remotes"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:79
|
||||
msgid "Adding Remote"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/remotes.blp:88 data/ui/window.blp:54 data/ui/window.blp:194
|
||||
msgid "This should only take a moment"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:14
|
||||
msgid "Search Criteria"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:22
|
||||
msgid "Choose a Remote to Search"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:36
|
||||
msgid "Results"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:71
|
||||
msgid "Start Search"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:80
|
||||
msgid "Search for Flatpaks"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:82
|
||||
msgid "Search for Flatpaks that you want to install"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:108
|
||||
msgid "Searching"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:119
|
||||
msgid "Too Many Results"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/search_install.blp:120
|
||||
msgid "Try being more specific with your search"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/snapshots.blp:14
|
||||
msgid "Open Snapshots Folder"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/snapshots.blp:32
|
||||
msgid "New Snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/snapshots.blp:83
|
||||
msgid "No Snapshots"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/snapshots.blp:84
|
||||
msgid ""
|
||||
"Snapshots are backups of the app's user data. They can be reapplied at any "
|
||||
"time."
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:18
|
||||
msgid "Filter List"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:24
|
||||
#: src/main_window/window.blp:15
|
||||
msgid "Main Menu"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:31 src/gtk/help-overlay.blp:29
|
||||
msgid "Toggle Selection Mode"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:53
|
||||
msgid "Loading Flatpaks"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:129
|
||||
msgid "Uninstalling"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:157
|
||||
msgid "Creating Snapshots"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:169
|
||||
msgid "No Flatpaks Found"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:170
|
||||
msgid ""
|
||||
"Warehouse cannot see the list of installed Flatpaks or the system has no "
|
||||
"Flatpaks installed"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:175
|
||||
msgid "No Flatpaks Match Filters"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:176
|
||||
msgid "No installed Flatpak matches all of the currently applied filters"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:193
|
||||
msgid "Refreshing List"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:212
|
||||
msgid "Uninstall Selected Apps"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:218
|
||||
msgid "Send Selected Apps' Data to the Trash"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:224
|
||||
msgid "Open Copy Menu"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:231
|
||||
msgid "Snapshot Selected Apps' Data"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:241
|
||||
#: src/main_window/window.blp:34
|
||||
msgid "Manage Leftover Data…"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:250
|
||||
#: src/main_window/window.blp:43
|
||||
msgid "Manage Remotes…"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:256
|
||||
#: src/main_window/window.blp:49
|
||||
msgid "Install From File…"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:261
|
||||
#: src/main_window/window.blp:54
|
||||
msgid "Install From The Web…"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:267
|
||||
#: src/main_window/window.blp:60
|
||||
msgid "Refresh List"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:271
|
||||
#: src/main_window/window.blp:64
|
||||
msgid "_Keyboard Shortcuts"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:276
|
||||
#: src/main_window/window.blp:69
|
||||
msgid "_About Warehouse"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:285
|
||||
msgid "Copy Names"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:290
|
||||
msgid "Copy IDs"
|
||||
msgstr ""
|
||||
|
||||
#: data/ui/window.blp:295
|
||||
msgid "Copy Refs"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:82
|
||||
msgid "Updates Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:87 src/properties_window.py:212
|
||||
msgid "{} is masked and will not be updated"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:94
|
||||
msgid "Auto Removal Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:99
|
||||
msgid ""
|
||||
"{} is pinned and will not be auto removed even when it's required by no app"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:106
|
||||
msgid "App EOL"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:112 src/properties_window.py:195
|
||||
msgid ""
|
||||
"{} has reached its End of Life and will not receive any security updates"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:123
|
||||
msgid "Runtime EOL"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:129 src/properties_window.py:203
|
||||
msgid ""
|
||||
"{}'s runtime has reached its End of Life and will not receive any security "
|
||||
"updates"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:165 src/orphans_window.py:266
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:176 src/remotes_window.py:227
|
||||
msgid "View More"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:197
|
||||
msgid "Copied name"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:201
|
||||
msgid "Copy Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:206
|
||||
msgid "Copied ID"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:211
|
||||
msgid "Copy ID"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:216
|
||||
msgid "Copied ref"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:221
|
||||
msgid "Copy Ref"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:227
|
||||
msgid "Copied launch command"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:231
|
||||
msgid "Copy Launch Command"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:243
|
||||
msgid "Opened {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:246
|
||||
msgid "Open"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:256 src/window.py:229 src/window.py:302
|
||||
msgid "Uninstall"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:311
|
||||
msgid "Enable Updates"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:324 src/window.py:546
|
||||
msgid "Disable Auto Removal"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:336
|
||||
msgid "Enable Auto Removal"
|
||||
msgstr ""
|
||||
|
||||
#: src/app_row_widget.py:350
|
||||
msgid "Manage Snapshots"
|
||||
msgstr ""
|
||||
|
||||
#: src/common.py:325 src/common.py:399
|
||||
msgid ""
|
||||
"Working on {}\n"
|
||||
"{} out of {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/downgrade_window.py:88
|
||||
msgid "Commit Hash: {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/downgrade_window.py:110
|
||||
msgid "Could not downgrade {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/downgrade_window.py:128
|
||||
msgid "Downgrading…"
|
||||
msgstr ""
|
||||
|
||||
#: src/downgrade_window.py:151
|
||||
msgid "Downgrade {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/downgrade_window.py:159
|
||||
msgid "Ensure that {} will never be updated to a newer version"
|
||||
msgstr ""
|
||||
|
||||
#: src/filter_window.py:43 src/filter_window.py:49
|
||||
msgid "{} selected"
|
||||
msgstr ""
|
||||
|
||||
#: src/filter_window.py:135 src/orphans_window.py:150 src/remotes_window.py:307
|
||||
msgid "User wide"
|
||||
msgstr ""
|
||||
|
||||
#: src/filter_window.py:137 src/orphans_window.py:152 src/remotes_window.py:309
|
||||
msgid "System wide"
|
||||
msgstr ""
|
||||
|
||||
#: src/filter_window.py:139 src/orphans_window.py:154 src/remotes_window.py:311
|
||||
msgid "Unknown install type"
|
||||
msgstr ""
|
||||
|
||||
#: src/gtk/help-overlay.blp:11
|
||||
msgid "App Management"
|
||||
msgstr ""
|
||||
@@ -555,18 +111,30 @@ msgstr ""
|
||||
msgid "Search"
|
||||
msgstr ""
|
||||
|
||||
#: src/gtk/help-overlay.blp:19
|
||||
msgid "Set Filters"
|
||||
msgstr ""
|
||||
|
||||
#: src/gtk/help-overlay.blp:24
|
||||
msgid "Refresh"
|
||||
msgstr ""
|
||||
|
||||
#: src/gtk/help-overlay.blp:29
|
||||
msgid "Toggle Selection Mode"
|
||||
msgstr ""
|
||||
|
||||
#: src/gtk/help-overlay.blp:34
|
||||
msgid "More Functions"
|
||||
msgstr ""
|
||||
|
||||
#: src/gtk/help-overlay.blp:37 src/orphans_window.py:30
|
||||
#: src/gtk/help-overlay.blp:37
|
||||
msgid "Manage Leftover Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/gtk/help-overlay.blp:42
|
||||
msgid "Manage Remotes"
|
||||
msgstr ""
|
||||
|
||||
#: src/gtk/help-overlay.blp:47
|
||||
msgid "Install From File"
|
||||
msgstr ""
|
||||
@@ -587,458 +155,19 @@ msgstr ""
|
||||
msgid "Quit"
|
||||
msgstr ""
|
||||
|
||||
#: src/main.py:149
|
||||
#: src/main.py:145
|
||||
msgid "Flatpaks"
|
||||
msgstr ""
|
||||
|
||||
#. Translators: do one of the following, one per line: Your Name, Your Name <email@email.org>, Your Name https://websi.te
|
||||
#: src/main.py:191
|
||||
#: src/main.py:187
|
||||
msgid "translator-credits"
|
||||
msgstr ""
|
||||
|
||||
#: src/main.py:197
|
||||
#: src/main.py:193
|
||||
msgid "Donate"
|
||||
msgstr ""
|
||||
|
||||
#: src/main.py:199
|
||||
#: src/main.py:195
|
||||
msgid "Contributors"
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:80 src/window.py:810
|
||||
msgid "Installed successfully"
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:83
|
||||
msgid "Could not install some apps"
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:117
|
||||
msgid "Attempt to Install?"
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:118
|
||||
msgid "Warehouse will attempt to install apps matching the selected data."
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:121 src/orphans_window.py:189
|
||||
#: src/properties_window.py:156 src/remotes_window.py:86
|
||||
#: src/remotes_window.py:167 src/remotes_window.py:450
|
||||
#: src/remotes_window.py:621 src/snapshots_window.py:128
|
||||
#: src/snapshots_window.py:234 src/window.py:228 src/window.py:301
|
||||
#: src/window.py:446 src/window.py:492 src/window.py:543 src/window.py:645
|
||||
#: src/window.py:715 src/window.py:838
|
||||
msgid "Cancel"
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:185
|
||||
msgid "Trash folders?"
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:185
|
||||
msgid "These folders will be sent to the trash."
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:190
|
||||
msgid "Continue"
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:198 src/properties_window.py:46
|
||||
#: src/snapshots_window.py:245 src/window.py:424
|
||||
msgid "Could not open folder"
|
||||
msgstr ""
|
||||
|
||||
#: src/orphans_window.py:216
|
||||
msgid "Could not manage data"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:40 src/remotes_window.py:212
|
||||
msgid "Copied {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:52
|
||||
msgid "Could not show details"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:58
|
||||
msgid "User Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:69
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:84 src/properties_window.py:144
|
||||
msgid "No User Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:135
|
||||
msgid "Could not show properties"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:143 src/window.py:438
|
||||
msgid "Trashed user data"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:150 src/window.py:433
|
||||
msgid "Could not trash user data"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:154 src/window.py:441
|
||||
msgid "Send {}'s User Data to the Trash?"
|
||||
msgstr ""
|
||||
|
||||
#: src/properties_window.py:158 src/window.py:448 src/window.py:646
|
||||
msgid "Trash Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:71
|
||||
msgid "Could not remove {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:81 src/remotes_window.py:162
|
||||
msgid "Any installed apps from {} will stop receiving updates"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:84
|
||||
msgid "Remove {}?"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:87 src/remotes_window.py:272
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:114
|
||||
msgid "Could not enable {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:142
|
||||
msgid "Could not disable {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:165
|
||||
msgid "Disable {}?"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:168 src/remotes_window.py:263
|
||||
msgid "Disable"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:179
|
||||
msgid "Could not view apps"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:240
|
||||
msgid "Set Filter"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:254
|
||||
msgid "Enable"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:288
|
||||
msgid "Copy remote name"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:302
|
||||
msgid "Disabled"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:339
|
||||
msgid "The open source, pay-what-you-want app store from elementary"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:345
|
||||
msgid "Central repository of Flatpak applications"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:351
|
||||
msgid "Beta builds of Flatpak applications"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:357
|
||||
msgid "Flatpaks packaged by Fedora Linux"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:363
|
||||
msgid "The latest beta GNOME Apps and Runtimes"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:369
|
||||
msgid "Beta KDE Apps and Runtimes"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:375
|
||||
msgid "Central repository of the WebKit Developer and Runtime SDK"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:411 src/remotes_window.py:573
|
||||
msgid "Could not add {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:448
|
||||
msgid "Add Flatpak Remote"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:451 src/remotes_window.py:622
|
||||
msgid "Add"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:505 src/remotes_window.py:630
|
||||
msgid "Name"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:509
|
||||
msgid "URL"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:521 src/remotes_window.py:633 src/window.py:847
|
||||
msgid "User"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:521 src/remotes_window.py:633
|
||||
msgid "Remote will be available to only you"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:529 src/remotes_window.py:636 src/window.py:850
|
||||
msgid "System"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:530 src/remotes_window.py:637
|
||||
msgid "Remote will be available to every user on the system"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:569
|
||||
msgid "{} successfully added"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:619
|
||||
msgid "Add {}?"
|
||||
msgstr ""
|
||||
|
||||
#: src/remotes_window.py:667
|
||||
msgid "Flatpak Repos"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:48
|
||||
msgid "There is no User Data to Snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:87
|
||||
msgid "Version {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:95 src/snapshots_window.py:236
|
||||
msgid "Apply Snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:101 src/snapshots_window.py:130
|
||||
msgid "Trash Snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:121
|
||||
msgid "Could not trash snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:125
|
||||
msgid "Trash Snapshot?"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:126
|
||||
msgid "This snapshot and its contents will be sent to the trash."
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:148
|
||||
msgid "Could not create snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:157
|
||||
msgid "Creating Snapshot…"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:191 src/snapshots_window.py:208
|
||||
#: src/snapshots_window.py:215
|
||||
msgid "Could not apply snapshot"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:194
|
||||
msgid "Snapshot applied"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:221
|
||||
msgid "Applying Snapshot…"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:229
|
||||
msgid "Apply Snapshot?"
|
||||
msgstr ""
|
||||
|
||||
#: src/snapshots_window.py:230
|
||||
msgid "Applying this snapshot will trash any current user data for {}."
|
||||
msgstr ""
|
||||
|
||||
#. Window stuffs
|
||||
#: src/snapshots_window.py:280
|
||||
msgid "{} Snapshots"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:111
|
||||
msgid "Uninstalled successfully"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:114
|
||||
msgid "Could not uninstall some apps"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:170
|
||||
msgid "Uninstall Selected Apps?"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:171
|
||||
msgid "It will not be possible to use these apps after removal."
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:191 src/window.py:273
|
||||
msgid "App Settings & Data"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:195 src/window.py:277
|
||||
msgid "Keep"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:196
|
||||
msgid "Allow restoring these apps' settings and content"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:200
|
||||
msgid "Send these apps' settings and content to the trash"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:236
|
||||
msgid "Cannot uninstall while already uninstalling"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:265
|
||||
msgid "Uninstall {}?"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:266
|
||||
msgid "It will not be possible to use {} after removal."
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:278
|
||||
msgid "Allow restoring this app's settings and content"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:282
|
||||
msgid "Send this app's settings and content to the trash"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:444
|
||||
msgid "Your files and data for this app will be sent to the trash."
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:463
|
||||
msgid "Could not disable updates for {}"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:485
|
||||
msgid "Disable Updates for {}?"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:489
|
||||
msgid ""
|
||||
"This will mask {} ensuring it will never recieve any feature or security "
|
||||
"updates."
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:511
|
||||
msgid "Could not enable auto removal"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:514
|
||||
msgid "Could not disable auto removal"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:537
|
||||
msgid "Disable Automatic Removal for {}?"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:540
|
||||
msgid ""
|
||||
"This will pin {} ensuring it well never be removed automatically, even if no "
|
||||
"app depends on it."
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:560
|
||||
msgid "Could not Run App"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:568
|
||||
msgid "OK"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:620
|
||||
msgid "{} has no data to trash"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:626
|
||||
msgid "Could not trash {}'s data"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:641
|
||||
msgid "Trash Selected Apps' User Data?"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:642
|
||||
msgid "Your files and data for these apps will be sent to the trash."
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:692
|
||||
msgid "Could not snapshot some apps"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:709
|
||||
msgid "Create Snapshots?"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:711
|
||||
msgid ""
|
||||
"Snapshots are backups of the app's user data. They can be reapplied at any "
|
||||
"time. This could take a while."
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:716
|
||||
msgid "Create Snapshots"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:771
|
||||
msgid "Copied selected app names"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:786
|
||||
msgid "Copied selected app IDs"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:801
|
||||
msgid "Copied selected app refs"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:812
|
||||
msgid "Could not install app"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:836
|
||||
msgid "Install {}?"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:847
|
||||
msgid "The app will be available to only you"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:851
|
||||
msgid "The app will be available to every user on the system"
|
||||
msgstr ""
|
||||
|
||||
#: src/window.py:881
|
||||
msgid "File type not supported"
|
||||
msgstr ""
|
||||
|
||||
@@ -1,381 +0,0 @@
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import re
|
||||
|
||||
from gi.repository import Adw, Gdk, Gio, GLib, Gtk
|
||||
from .properties_window import PropertiesWindow
|
||||
from .downgrade_window import DowngradeWindow
|
||||
from .snapshots_window import SnapshotsWindow
|
||||
from .filter_window import FilterWindow
|
||||
from .common import myUtils
|
||||
|
||||
|
||||
class AppRow(Adw.ActionRow):
|
||||
def set_selectable(self, is_selectable):
|
||||
self.tickbox.set_active(False)
|
||||
self.tickbox.set_visible(is_selectable)
|
||||
self.row_menu.set_visible(not is_selectable)
|
||||
self.set_activatable(is_selectable)
|
||||
|
||||
def set_is_visible(self, is_visible):
|
||||
self.set_visible(is_visible)
|
||||
self.set_selectable(False)
|
||||
|
||||
def info_button_show_or_hide(self):
|
||||
self.info_button.set_visible(False)
|
||||
|
||||
if self.mask_label.get_visible() == True:
|
||||
self.info_button.set_visible(True)
|
||||
return
|
||||
|
||||
if self.pin_label.get_visible() == True:
|
||||
self.info_button.set_visible(True)
|
||||
return
|
||||
|
||||
if self.eol_app_label.get_visible() == True:
|
||||
self.info_button.set_visible(True)
|
||||
return
|
||||
|
||||
if self.eol_runtime_label.get_visible() == True:
|
||||
self.info_button.set_visible(True)
|
||||
return
|
||||
|
||||
def set_masked(self, is_masked):
|
||||
self.mask_label.set_visible(is_masked)
|
||||
self.info_button_show_or_hide()
|
||||
|
||||
def __init__(self, parent_window, host_flatpaks, index, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.my_utils = myUtils(parent_window)
|
||||
|
||||
current_flatpak = host_flatpaks[index]
|
||||
|
||||
self.index = index
|
||||
self.app_name = current_flatpak[0]
|
||||
self.app_id = current_flatpak[2]
|
||||
self.app_version = current_flatpak[3]
|
||||
self.origin_remote = current_flatpak[6]
|
||||
self.install_type = current_flatpak[7]
|
||||
self.app_ref = current_flatpak[8]
|
||||
self.dependent_runtime = current_flatpak[13]
|
||||
|
||||
self.set_title(self.app_name)
|
||||
self.set_subtitle(self.app_id)
|
||||
self.add_prefix(self.my_utils.find_app_icon(self.app_id))
|
||||
|
||||
self.is_runtime = False
|
||||
if len(current_flatpak[13]) == 0:
|
||||
self.is_runtime = True
|
||||
|
||||
info_box = Gtk.Box(
|
||||
orientation=Gtk.Orientation.VERTICAL,
|
||||
valign=Gtk.Align.CENTER,
|
||||
halign=Gtk.Align.CENTER,
|
||||
hexpand=True,
|
||||
vexpand=True,
|
||||
spacing=6,
|
||||
)
|
||||
|
||||
# justify=Gtk.Justification.RIGHT
|
||||
self.mask_label = Gtk.Label(
|
||||
label=_("Updates Disabled"),
|
||||
visible=False,
|
||||
hexpand=True,
|
||||
wrap=True,
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_("{} is masked and will not be updated").format(
|
||||
self.app_name
|
||||
),
|
||||
)
|
||||
self.mask_label.add_css_class("warning")
|
||||
|
||||
self.pin_label = Gtk.Label(
|
||||
label=_("Auto Removal Disabled"),
|
||||
visible=False,
|
||||
hexpand=True,
|
||||
wrap=True,
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_("{} is pinned and will not be auto removed even when it's required by no app").format(
|
||||
self.app_name
|
||||
)
|
||||
)
|
||||
self.pin_label.add_css_class("warning")
|
||||
|
||||
self.eol_app_label = Gtk.Label(
|
||||
label=_("App EOL"),
|
||||
visible=False,
|
||||
hexpand=True,
|
||||
wrap=True,
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_(
|
||||
"{} has reached its End of Life and will not receive any security updates"
|
||||
).format(self.app_name),
|
||||
)
|
||||
self.eol_app_label.add_css_class("error")
|
||||
info_box.append(self.eol_app_label)
|
||||
if "eol" in parent_window.host_flatpaks[index][12]:
|
||||
# EOL = End Of Life, meaning the app will not be updated
|
||||
# justify=Gtk.Justification.RIGHT
|
||||
self.eol_app_label.set_visible(True)
|
||||
|
||||
self.eol_runtime_label = Gtk.Label(
|
||||
label=_("Runtime EOL"),
|
||||
visible=False,
|
||||
hexpand=True,
|
||||
wrap=True,
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_(
|
||||
"{}'s runtime has reached its End of Life and will not receive any security updates"
|
||||
).format(self.app_name),
|
||||
)
|
||||
self.eol_runtime_label.add_css_class("error")
|
||||
info_box.append(self.eol_runtime_label)
|
||||
if current_flatpak[13] in parent_window.eol_list:
|
||||
# EOL = End Of Life, meaning the runtime will not be updated
|
||||
# justify=Gtk.Justification.RIGHT
|
||||
self.eol_runtime_label.set_visible(True)
|
||||
|
||||
info_pop = Gtk.Popover()
|
||||
info_pop.set_child(info_box)
|
||||
self.info_button = Gtk.MenuButton(
|
||||
visible=False,
|
||||
valign=Gtk.Align.CENTER,
|
||||
popover=info_pop,
|
||||
icon_name="software-update-urgent-symbolic",
|
||||
)
|
||||
self.info_button.add_css_class("flat")
|
||||
|
||||
info_box.append(self.mask_label)
|
||||
info_box.append(self.pin_label)
|
||||
self.add_suffix(self.info_button)
|
||||
|
||||
properties_button = Gtk.Button(
|
||||
icon_name="info-symbolic",
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_("View Properties"),
|
||||
)
|
||||
properties_button.add_css_class("flat")
|
||||
properties_button.connect(
|
||||
"clicked", lambda *_: PropertiesWindow(index, host_flatpaks, parent_window)
|
||||
)
|
||||
self.add_suffix(properties_button)
|
||||
|
||||
self.tickbox = Gtk.CheckButton(
|
||||
visible=False, tooltip_text=_("Select")
|
||||
) # visible=self.in_batch_mode
|
||||
self.tickbox.add_css_class("selection-mode")
|
||||
self.tickbox.connect("toggled", parent_window.row_select_handler)
|
||||
self.add_suffix(self.tickbox)
|
||||
self.set_activatable_widget(self.tickbox)
|
||||
self.set_activatable(False)
|
||||
|
||||
self.row_menu = Gtk.MenuButton(
|
||||
icon_name="view-more-symbolic",
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_("View More"),
|
||||
)
|
||||
self.row_menu.add_css_class("flat")
|
||||
row_menu_model = Gio.Menu()
|
||||
copy_menu_model = Gio.Menu()
|
||||
advanced_menu_model = Gio.Menu()
|
||||
self.add_suffix(self.row_menu)
|
||||
|
||||
self.is_pinned = False
|
||||
|
||||
if "user" in self.install_type:
|
||||
if f"runtime/{self.app_ref}" in parent_window.user_pins:
|
||||
self.is_pinned = True
|
||||
|
||||
if "system" in self.install_type:
|
||||
if f"runtime/{self.app_ref}" in parent_window.system_pins:
|
||||
self.is_pinned = True
|
||||
|
||||
parent_window.create_action(
|
||||
("copy-name" + str(index)),
|
||||
lambda *_, name=self.app_name, toast=_(
|
||||
"Copied name"
|
||||
): parent_window.copy_item(name, toast),
|
||||
)
|
||||
copy_menu_model.append_item(
|
||||
Gio.MenuItem.new(_("Copy Name"), f"win.copy-name{index}")
|
||||
)
|
||||
|
||||
parent_window.create_action(
|
||||
("copy-id" + str(index)),
|
||||
lambda *_, id=self.app_id, toast=_("Copied ID"): parent_window.copy_item(
|
||||
id, toast
|
||||
),
|
||||
)
|
||||
copy_menu_model.append_item(
|
||||
Gio.MenuItem.new(_("Copy ID"), f"win.copy-id{index}")
|
||||
)
|
||||
|
||||
parent_window.create_action(
|
||||
("copy-ref" + str(index)),
|
||||
lambda *_, ref=self.app_ref, toast=_("Copied ref"): parent_window.copy_item(
|
||||
ref, toast
|
||||
),
|
||||
)
|
||||
copy_menu_model.append_item(
|
||||
Gio.MenuItem.new(_("Copy Ref"), f"win.copy-ref{index}")
|
||||
)
|
||||
|
||||
parent_window.create_action(
|
||||
("copy-command" + str(index)),
|
||||
lambda *_, ref=self.app_ref, toast=_(
|
||||
"Copied launch command"
|
||||
): parent_window.copy_item(f"flatpak run {ref}", toast),
|
||||
)
|
||||
copy_menu_model.append_item(
|
||||
Gio.MenuItem.new(_("Copy Launch Command"), f"win.copy-command{index}")
|
||||
)
|
||||
|
||||
row_menu_model.append_submenu(_("Copy"), copy_menu_model)
|
||||
|
||||
if (
|
||||
"runtime" not in parent_window.host_flatpaks[index][12]
|
||||
and self.app_id != "io.github.flattool.Warehouse"
|
||||
):
|
||||
parent_window.create_action(
|
||||
("run" + str(index)),
|
||||
lambda *_a, ref=self.app_ref, name=self.app_name: parent_window.run_app_thread(
|
||||
ref, _("Opened {}").format(name)
|
||||
),
|
||||
)
|
||||
run_item = Gio.MenuItem.new(_("Open"), f"win.run{index}")
|
||||
row_menu_model.append_item(run_item)
|
||||
|
||||
if self.app_id != "io.github.flattool.Warehouse":
|
||||
parent_window.create_action(
|
||||
("uninstall" + str(index)),
|
||||
lambda *_: parent_window.uninstall_button_handler(
|
||||
self, self.app_name, self.app_ref, self.app_id
|
||||
),
|
||||
)
|
||||
uninstall_item = Gio.MenuItem.new(_("Uninstall"), f"win.uninstall{index}")
|
||||
row_menu_model.append_item(uninstall_item)
|
||||
|
||||
data_menu_model = Gio.Menu()
|
||||
|
||||
parent_window.create_action(
|
||||
("open-data" + str(index)),
|
||||
lambda *_, path=(
|
||||
parent_window.user_data_path + self.app_id
|
||||
): parent_window.open_data_folder(path),
|
||||
)
|
||||
open_data_item = Gio.MenuItem.new(
|
||||
_("Open User Data Folder"), f"win.open-data{index}"
|
||||
)
|
||||
open_data_item.set_attribute_value(
|
||||
"hidden-when", GLib.Variant.new_string("action-disabled")
|
||||
)
|
||||
data_menu_model.append_item(open_data_item)
|
||||
|
||||
parent_window.create_action(
|
||||
("trash" + str(index)),
|
||||
lambda *_, name=self.app_name, id=self.app_id, index=index: parent_window.trash_data(
|
||||
name, id, index
|
||||
),
|
||||
)
|
||||
trash_item = Gio.MenuItem.new(_("Trash User Data"), f"win.trash{index}")
|
||||
trash_item.set_attribute_value(
|
||||
"hidden-when", GLib.Variant.new_string("action-disabled")
|
||||
)
|
||||
data_menu_model.append_item(trash_item)
|
||||
|
||||
row_menu_model.append_section(None, data_menu_model)
|
||||
|
||||
if not os.path.exists(parent_window.user_data_path + self.app_id):
|
||||
parent_window.lookup_action(f"open-data{self.index}").set_enabled(False)
|
||||
parent_window.lookup_action(f"trash{self.index}").set_enabled(False)
|
||||
|
||||
parent_window.create_action(
|
||||
("mask" + str(index)),
|
||||
lambda *_, id=self.app_id, type=self.install_type, index=index: parent_window.mask_flatpak(
|
||||
self
|
||||
),
|
||||
)
|
||||
mask_item = Gio.MenuItem.new(_("Disable Updates"), f"win.mask{index}")
|
||||
mask_item.set_attribute_value(
|
||||
"hidden-when", GLib.Variant.new_string("action-disabled")
|
||||
)
|
||||
advanced_menu_model.append_item(mask_item)
|
||||
|
||||
parent_window.create_action(
|
||||
("unmask" + str(index)),
|
||||
lambda *_, id=self.app_id, type=self.install_type, index=index: parent_window.mask_flatpak(
|
||||
self
|
||||
),
|
||||
)
|
||||
unmask_item = Gio.MenuItem.new(_("Enable Updates"), f"win.unmask{index}")
|
||||
unmask_item.set_attribute_value(
|
||||
"hidden-when", GLib.Variant.new_string("action-disabled")
|
||||
)
|
||||
advanced_menu_model.append_item(unmask_item)
|
||||
|
||||
if "runtime" in parent_window.host_flatpaks[index][12]:
|
||||
parent_window.create_action(
|
||||
("pin" + str(index)),
|
||||
lambda *_, d=self.app_id, type=self.install_type, index=index: parent_window.pin_flatpak(
|
||||
self
|
||||
),
|
||||
)
|
||||
pin_item = Gio.MenuItem.new(_("Disable Auto Removal"), f"win.pin{index}")
|
||||
pin_item.set_attribute_value(
|
||||
"hidden-when", GLib.Variant.new_string("action-disabled")
|
||||
)
|
||||
advanced_menu_model.append_item(pin_item)
|
||||
|
||||
parent_window.create_action(
|
||||
("unpin" + str(index)),
|
||||
lambda *_, d=self.app_id, type=self.install_type, index=index: parent_window.pin_flatpak(
|
||||
self
|
||||
),
|
||||
)
|
||||
unpin_item = Gio.MenuItem.new(_("Enable Auto Removal"), f"win.unpin{index}")
|
||||
unpin_item.set_attribute_value(
|
||||
"hidden-when", GLib.Variant.new_string("action-disabled")
|
||||
)
|
||||
advanced_menu_model.append_item(unpin_item)
|
||||
|
||||
if "runtime" not in parent_window.host_flatpaks[index][12]:
|
||||
parent_window.create_action(
|
||||
("snapshot" + str(index)),
|
||||
lambda *_, row=current_flatpak: SnapshotsWindow(
|
||||
parent_window, row
|
||||
),
|
||||
)
|
||||
snapshot_item = Gio.MenuItem.new(
|
||||
_("Manage Snapshots"), f"win.snapshot{index}"
|
||||
)
|
||||
advanced_menu_model.append_item(snapshot_item)
|
||||
|
||||
parent_window.create_action(
|
||||
("downgrade" + str(index)),
|
||||
lambda *_, row=current_flatpak, index=index: DowngradeWindow(
|
||||
parent_window, row, index
|
||||
),
|
||||
)
|
||||
downgrade_item = Gio.MenuItem.new(_("Downgrade"), f"win.downgrade{index}")
|
||||
advanced_menu_model.append_item(downgrade_item)
|
||||
|
||||
if (
|
||||
self.app_id in parent_window.system_mask_list
|
||||
or self.app_id in parent_window.user_mask_list
|
||||
):
|
||||
self.mask_label.set_visible(True)
|
||||
parent_window.lookup_action(f"mask{index}").set_enabled(False)
|
||||
else:
|
||||
parent_window.lookup_action(f"unmask{index}").set_enabled(False)
|
||||
|
||||
if self.is_runtime and self.is_pinned:
|
||||
parent_window.lookup_action(f"pin{index}").set_enabled(False)
|
||||
elif self.is_runtime:
|
||||
parent_window.lookup_action(f"unpin{index}").set_enabled(False)
|
||||
self.pin_label.set_visible(self.is_pinned)
|
||||
|
||||
row_menu_model.append_section(None, advanced_menu_model)
|
||||
self.row_menu.set_menu_model(row_menu_model)
|
||||
|
||||
self.info_button_show_or_hide()
|
||||
527
src/common.py
527
src/common.py
@@ -1,527 +0,0 @@
|
||||
from gi.repository import GLib, Gtk, Adw, Gio # , Gdk
|
||||
import os
|
||||
import subprocess
|
||||
import pathlib
|
||||
import time
|
||||
|
||||
|
||||
class myUtils:
|
||||
def __init__(self, window, **kwargs):
|
||||
self.parent_window = window
|
||||
self.host_home = str(pathlib.Path.home())
|
||||
self.user_data_path = self.host_home + "/.var/app/"
|
||||
self.install_success = True
|
||||
self.uninstall_success = True
|
||||
self.new_env = dict(os.environ)
|
||||
self.new_env["LC_ALL"] = "C"
|
||||
self.is_dialog_open = False
|
||||
|
||||
def trash_folder(self, path):
|
||||
if not os.path.exists(path):
|
||||
print("error in common.trashFolder: path does not exists. path =", path)
|
||||
return 1
|
||||
try:
|
||||
subprocess.run(
|
||||
["gio", "trash", path],
|
||||
capture_output=False,
|
||||
check=True,
|
||||
env=self.new_env,
|
||||
)
|
||||
return 0
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("error in common.trashFolder: CalledProcessError:", e)
|
||||
return 2
|
||||
|
||||
def get_size_with_format(self, path):
|
||||
return self.get_size_format(self.get_dir_size(path))
|
||||
|
||||
def get_size_format(self, b):
|
||||
factor = 1000
|
||||
suffix = "B"
|
||||
for unit in ["", "K", "M", "G", "T", "P", "E", "Z"]:
|
||||
if b < factor:
|
||||
return f"{b:.1f}{unit}{suffix}"
|
||||
b /= factor
|
||||
return f"{b:.1f}{suffix}"
|
||||
|
||||
def get_dir_size(self, directory):
|
||||
"""Returns the `directory` size in bytes."""
|
||||
total = 0
|
||||
try:
|
||||
# print("[+] Getting the size of", directory)
|
||||
for entry in os.scandir(directory):
|
||||
if entry.is_symlink():
|
||||
continue # Skip symlinks
|
||||
if entry.is_file():
|
||||
# if it's a file, use stat() function
|
||||
total += entry.stat().st_size
|
||||
elif entry.is_dir():
|
||||
# if it's a directory, recursively call this function
|
||||
try:
|
||||
total += self.get_dir_size(entry.path)
|
||||
except FileNotFoundError:
|
||||
pass
|
||||
except NotADirectoryError:
|
||||
# if `directory` isn't a directory, get the file size then
|
||||
return os.path.getsize(directory)
|
||||
except PermissionError:
|
||||
# if for whatever reason we can't open the folder, return 0
|
||||
return 0
|
||||
if total == 0:
|
||||
return 0
|
||||
# Adding 4000 seems to make it more accurate to whatever data we can't scan from within the sandbox
|
||||
return total + 4000
|
||||
|
||||
def find_app_icon(self, app_id):
|
||||
icon_theme = Gtk.IconTheme.new()
|
||||
icon_theme.add_search_path("/var/lib/flatpak/exports/share/icons/")
|
||||
icon_theme.add_search_path(
|
||||
self.host_home + "/.local/share/flatpak/exports/share/icons"
|
||||
)
|
||||
|
||||
try:
|
||||
icon_path = (
|
||||
icon_theme.lookup_icon(
|
||||
app_id, None, 512, 1, self.parent_window.get_direction(), 0
|
||||
)
|
||||
.get_file()
|
||||
.get_path()
|
||||
)
|
||||
except GLib.GError:
|
||||
icon_path = None
|
||||
if icon_path:
|
||||
image = Gtk.Image.new_from_file(icon_path)
|
||||
image.set_icon_size(Gtk.IconSize.LARGE)
|
||||
image.add_css_class("icon-dropshadow")
|
||||
else:
|
||||
image = Gtk.Image.new_from_icon_name("application-x-executable-symbolic")
|
||||
image.set_icon_size(Gtk.IconSize.LARGE)
|
||||
return image
|
||||
|
||||
def get_host_updates(self):
|
||||
list = []
|
||||
output = subprocess.run(
|
||||
["flatpak-spawn", "--host", "flatpak", "update"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=self.new_env,
|
||||
).stdout
|
||||
lines = output.strip().split("\n")
|
||||
columns = lines[0].split("\t")
|
||||
data = [columns]
|
||||
for line in lines[1:]:
|
||||
row = line.split("\t")
|
||||
data.append(row)
|
||||
|
||||
for i in range(len(data)):
|
||||
if data[i][0].find(".") == 2:
|
||||
list.append(data[i][2])
|
||||
|
||||
return list
|
||||
|
||||
def get_host_system_pins(self):
|
||||
output = subprocess.run(
|
||||
["flatpak-spawn", "--host", "flatpak", "pin"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=self.new_env,
|
||||
).stdout
|
||||
data = output.strip().split("\n")
|
||||
for i in range(len(data)):
|
||||
data[i] = data[i].strip()
|
||||
return data
|
||||
|
||||
def get_host_user_pins(self):
|
||||
output = subprocess.run(
|
||||
["flatpak-spawn", "--host", "flatpak", "pin", "--user"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=self.new_env,
|
||||
).stdout
|
||||
data = output.strip().split("\n")
|
||||
for i in range(len(data)):
|
||||
data[i] = data[i].strip()
|
||||
return data
|
||||
|
||||
def get_host_remotes(self):
|
||||
output = subprocess.run(
|
||||
[
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"remotes",
|
||||
"--columns=all",
|
||||
"--show-disabled",
|
||||
],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=self.new_env,
|
||||
).stdout
|
||||
lines = output.strip().split("\n")
|
||||
columns = lines[0].split("\t")
|
||||
data = [columns]
|
||||
for line in lines[1:]:
|
||||
row = line.split("\t")
|
||||
data.append(row)
|
||||
return data
|
||||
|
||||
def get_host_flatpaks(self):
|
||||
output = subprocess.run(
|
||||
["flatpak-spawn", "--host", "flatpak", "list", "--columns=all"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=self.new_env,
|
||||
).stdout
|
||||
lines = output.strip().split("\n")
|
||||
columns = lines[0].split("\t")
|
||||
data = [columns]
|
||||
for line in lines[1:]:
|
||||
row = line.split("\t")
|
||||
data.append(row)
|
||||
|
||||
output = subprocess.run(
|
||||
["flatpak-spawn", "--host", "flatpak", "list", "--columns=runtime"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=self.new_env,
|
||||
).stdout
|
||||
lines = output.split("\n")
|
||||
for i in range(len(data)):
|
||||
data[i].append(lines[i])
|
||||
sorted_array = sorted(data, key=lambda item: item[0].lower())
|
||||
return sorted_array
|
||||
|
||||
def get_flatpak_info(self, ref, install_type):
|
||||
output = subprocess.run(
|
||||
[
|
||||
"flatpak-spawn", "--host", "sh", "-c",
|
||||
f"flatpak info {ref} --{install_type}"
|
||||
],
|
||||
capture_output=True,
|
||||
text=True
|
||||
).stdout
|
||||
lines = output.strip().split("\n")
|
||||
columns = lines[0].split("\t")
|
||||
data = [columns]
|
||||
for line in lines[1:]:
|
||||
row = line.split(": ", 1)
|
||||
for i in range(len(row)):
|
||||
row[i] = row[i].strip()
|
||||
data.append(row)
|
||||
info = {}
|
||||
info["name"] = data[0][0]
|
||||
for i in range(2, len(data)):
|
||||
if data[i][0] == '':
|
||||
continue
|
||||
info[data[i][0]] = data[i][1]
|
||||
return info
|
||||
|
||||
def get_dependent_runtimes(self):
|
||||
paks = self.get_host_flatpaks()
|
||||
dependent_runtimes = []
|
||||
for i in range(len(paks)):
|
||||
current = paks[i]
|
||||
try:
|
||||
if current[13] not in dependent_runtimes and current[13] != "":
|
||||
dependent_runtimes.append(current[13])
|
||||
except:
|
||||
print("Could not get dependent runtime")
|
||||
return sorted(dependent_runtimes)
|
||||
|
||||
def get_host_masks(self, user_or_system):
|
||||
output = subprocess.run(
|
||||
["flatpak-spawn", "--host", "flatpak", "mask", f"--{user_or_system}"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=self.new_env,
|
||||
).stdout
|
||||
lines = output.strip().split("\n")
|
||||
for i in range(len(lines)):
|
||||
lines[i] = lines[i].strip()
|
||||
return lines
|
||||
|
||||
def mask_flatpak(self, app_id, user_or_system, remove=False):
|
||||
command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"mask",
|
||||
f"--{user_or_system}",
|
||||
app_id,
|
||||
]
|
||||
if remove:
|
||||
command.append("--remove")
|
||||
response = ""
|
||||
try:
|
||||
response = subprocess.run(
|
||||
command, capture_output=True, text=True, env=self.new_env
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f"Error setting mask for {app_id}:\n", e)
|
||||
return 1
|
||||
if len(response.stderr) > 0:
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def downgrade_flatpak(self, id, ref, commit, install_type="system", mask=False, mask_list=None):
|
||||
unmask_cmd = f"flatpak mask --remove --{install_type} {id} && "
|
||||
update_cmd = f"flatpak updated {ref} --commit={commit} --{install_type} -y"
|
||||
to_run_cmd = ""
|
||||
if id in mask_list:
|
||||
to_run_cmd += unmask_cmd
|
||||
to_run_cmd += update_cmd
|
||||
if mask:
|
||||
to_run_cmd += f" && flatpak mask --{install_type} {id}"
|
||||
command = [
|
||||
"flatpak-spawn",
|
||||
"--host", "pkexec",
|
||||
"sh", "-c",
|
||||
to_run_cmd,
|
||||
]
|
||||
if install_type == "user":
|
||||
command.remove("pkexec")
|
||||
try:
|
||||
subprocess.run(
|
||||
command, capture_output=True, text=True, env=self.new_env, check=True
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("Error in common.downgrade_flatpak:", e.stderr)
|
||||
return 1
|
||||
return 0
|
||||
|
||||
def uninstall_flatpak(
|
||||
self, ref_arr, type_arr, should_trash, progress_bar=None, status_label=None
|
||||
):
|
||||
self.uninstall_success = True
|
||||
print(ref_arr)
|
||||
to_uninstall = []
|
||||
for i in range(len(ref_arr)):
|
||||
to_uninstall.append([ref_arr[i], type_arr[i]])
|
||||
|
||||
apps = []
|
||||
fails = []
|
||||
for i in range(len(to_uninstall)):
|
||||
ref = to_uninstall[i][0]
|
||||
id = to_uninstall[i][0].split("/")[0]
|
||||
app_type = to_uninstall[i][1]
|
||||
apps.append([ref, id, app_type])
|
||||
# apps array guide: [app_ref, app_id, user_or_system_install]
|
||||
|
||||
for i in range(len(apps)):
|
||||
command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"remove",
|
||||
"-y",
|
||||
f"--{apps[i][2]}",
|
||||
apps[i][0],
|
||||
]
|
||||
try:
|
||||
print(apps)
|
||||
if status_label:
|
||||
GLib.idle_add(
|
||||
status_label.set_label,
|
||||
_("Working on {}\n{} out of {}").format(
|
||||
apps[i][0], i + 1, len(apps)
|
||||
),
|
||||
)
|
||||
subprocess.run(
|
||||
command, capture_output=False, check=True, env=self.new_env
|
||||
)
|
||||
if progress_bar:
|
||||
GLib.idle_add(progress_bar.set_visible, True)
|
||||
GLib.idle_add(progress_bar.set_fraction, (i + 1.0) / len(ref_arr))
|
||||
except subprocess.CalledProcessError:
|
||||
fails.append(apps[i])
|
||||
|
||||
if len(fails) > 0: # Run this only if there is 1 or more non uninstalled apps
|
||||
pk_command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"pkexec",
|
||||
"flatpak",
|
||||
"remove",
|
||||
"-y",
|
||||
"--system",
|
||||
]
|
||||
print("second uninstall process")
|
||||
for i in range(len(fails)):
|
||||
if fails[i][2] == "user":
|
||||
self.uninstall_success = False
|
||||
continue # Skip if app is a user install app
|
||||
|
||||
pk_command.append(fails[i][0])
|
||||
try:
|
||||
print(pk_command)
|
||||
if progress_bar:
|
||||
GLib.idle_add(progress_bar.set_visible, True)
|
||||
GLib.idle_add(progress_bar.set_fraction, 0.9)
|
||||
subprocess.run(
|
||||
pk_command, capture_output=False, check=True, env=self.new_env
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
self.uninstall_success = False
|
||||
|
||||
if should_trash:
|
||||
host_paks = self.get_host_flatpaks()
|
||||
host_refs = []
|
||||
for i in range(len(host_paks)):
|
||||
host_refs.append(host_paks[i][8])
|
||||
|
||||
for i in range(len(apps)):
|
||||
if apps[i][0] in host_refs:
|
||||
print(f"{apps[i][1]} is still installed")
|
||||
else:
|
||||
self.trash_folder(f"{self.user_data_path}{apps[i][1]}")
|
||||
|
||||
if progress_bar:
|
||||
GLib.idle_add(progress_bar.set_visible, False)
|
||||
GLib.idle_add(progress_bar.set_fraction, 0.0)
|
||||
|
||||
def install_flatpak(
|
||||
self, app_arr, remote, user_or_system, progress_bar=None, status_label=None
|
||||
):
|
||||
self.install_success = True
|
||||
fails = []
|
||||
|
||||
for i in range(len(app_arr)):
|
||||
command = ["flatpak-spawn", "--host", "flatpak", "install"]
|
||||
if remote != None:
|
||||
command.append(remote)
|
||||
command.append(f"--{user_or_system}")
|
||||
command.append("-y")
|
||||
command.append(app_arr[i])
|
||||
try:
|
||||
if status_label:
|
||||
GLib.idle_add(
|
||||
status_label.set_label,
|
||||
_("Working on {}\n{} out of {}").format(
|
||||
app_arr[i], i + 1, len(app_arr)
|
||||
),
|
||||
)
|
||||
subprocess.run(
|
||||
command, capture_output=False, check=True, env=self.new_env
|
||||
)
|
||||
if progress_bar:
|
||||
GLib.idle_add(progress_bar.set_visible, True)
|
||||
GLib.idle_add(progress_bar.set_fraction, (i + 1.0) / len(app_arr))
|
||||
except subprocess.CalledProcessError:
|
||||
fails.append(app_arr[i])
|
||||
|
||||
if (len(fails) > 0) and (user_or_system == "system"):
|
||||
pk_command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"pkexec",
|
||||
"flatpak",
|
||||
"install",
|
||||
remote,
|
||||
f"--{user_or_system}",
|
||||
"-y",
|
||||
]
|
||||
for i in range(len(fails)):
|
||||
pk_command.append(fails[i])
|
||||
try:
|
||||
if progress_bar:
|
||||
GLib.idle_add(progress_bar.set_visible, True)
|
||||
GLib.idle_add(progress_bar.set_fraction, 0.9)
|
||||
subprocess.run(
|
||||
pk_command, capture_output=False, check=True, env=self.new_env
|
||||
)
|
||||
except subprocess.CalledProcessError:
|
||||
self.install_success = False
|
||||
|
||||
if (len(fails) > 0) and (user_or_system == "user"):
|
||||
self.install_success = False
|
||||
|
||||
if progress_bar:
|
||||
GLib.idle_add(progress_bar.set_visible, False)
|
||||
GLib.idle_add(progress_bar.set_fraction, 0.0)
|
||||
|
||||
def run_app(self, ref):
|
||||
self.run_app_error = False
|
||||
self.run_app_error_message = ""
|
||||
try:
|
||||
subprocess.run(
|
||||
["flatpak-spawn", "--host", "flatpak", "run", ref],
|
||||
check=True,
|
||||
env=self.new_env,
|
||||
start_new_session=True,
|
||||
capture_output=True,
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.run_app_error_message = e.stderr.decode()
|
||||
self.run_app_error = True
|
||||
|
||||
def get_install_type(self, type_arr):
|
||||
if "disabled" in type_arr:
|
||||
return "disabled"
|
||||
if "user" in type_arr:
|
||||
return "user"
|
||||
if "system" in type_arr:
|
||||
return "system"
|
||||
|
||||
def snapshot_apps(
|
||||
self,
|
||||
epoch,
|
||||
app_snapshot_path_arr,
|
||||
app_version_arr,
|
||||
app_user_data_arr,
|
||||
progress_bar=None,
|
||||
):
|
||||
if not (
|
||||
len(app_snapshot_path_arr) == len(app_version_arr) == len(app_user_data_arr)
|
||||
):
|
||||
print(
|
||||
"error in common.snapshotApp: the lengths of app_snapshot_path_arr, app_version_arr, and app_user_data_arr do not match."
|
||||
)
|
||||
return 1
|
||||
|
||||
fails = []
|
||||
|
||||
for i in range(len(app_snapshot_path_arr)):
|
||||
snapshot_path = app_snapshot_path_arr[i]
|
||||
version = app_version_arr[i]
|
||||
user_data = app_user_data_arr[i]
|
||||
command = [
|
||||
"tar",
|
||||
"cafv",
|
||||
f"{snapshot_path}{epoch}_{version}.tar.zst",
|
||||
"-C",
|
||||
f"{user_data}",
|
||||
".",
|
||||
]
|
||||
|
||||
try:
|
||||
if not os.path.exists(snapshot_path):
|
||||
file = Gio.File.new_for_path(snapshot_path)
|
||||
file.make_directory()
|
||||
subprocess.run(command, check=True, env=self.new_env)
|
||||
if progress_bar:
|
||||
GLib.idle_add(progress_bar.set_visible, True)
|
||||
GLib.idle_add(
|
||||
progress_bar.set_fraction,
|
||||
(i + 1.0) / len(app_snapshot_path_arr),
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
print("error in common.snapshotApp:", e)
|
||||
fails.append(user_data)
|
||||
|
||||
if (
|
||||
int(time.time()) == epoch
|
||||
): # Wait 1s if the snapshot is made too quickly, to prevent overriding a snapshot file
|
||||
subprocess.run(["sleep", "1s"])
|
||||
|
||||
if progress_bar:
|
||||
GLib.idle_add(progress_bar.set_visible, False)
|
||||
GLib.idle_add(progress_bar.set_fraction, 0.0)
|
||||
|
||||
if len(fails) > 0:
|
||||
print("These paths could not be archived:")
|
||||
for i in range(fails):
|
||||
print(fails[i])
|
||||
print("")
|
||||
return 1
|
||||
else:
|
||||
return 0
|
||||
@@ -1,168 +0,0 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/downgrade.ui")
|
||||
class DowngradeWindow(Adw.Dialog):
|
||||
__gtype_name__ = "DowngradeWindow"
|
||||
|
||||
new_env = dict(os.environ)
|
||||
new_env["LC_ALL"] = "C"
|
||||
|
||||
apply_button = Gtk.Template.Child()
|
||||
versions_group = Gtk.Template.Child()
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
mask_row = Gtk.Template.Child()
|
||||
main_toolbar_view = Gtk.Template.Child()
|
||||
loading = Gtk.Template.Child()
|
||||
loading_label = Gtk.Template.Child()
|
||||
main_stack = Gtk.Template.Child()
|
||||
outerbox = Gtk.Template.Child()
|
||||
action_bar = Gtk.Template.Child()
|
||||
|
||||
def selection_handler(self, button, index):
|
||||
self.action_bar.set_revealed(True)
|
||||
if button.get_active():
|
||||
self.commit_to_use = self.versions[index][0]
|
||||
|
||||
def get_commits(self):
|
||||
output = subprocess.run(
|
||||
[
|
||||
"flatpak-spawn", "--host", "sh", "-c",
|
||||
f"LC_ALL=C flatpak remote-info --log {self.remote} {self.app_ref} --{self.install_type}"
|
||||
],
|
||||
capture_output=True,
|
||||
text=True
|
||||
).stdout
|
||||
lines = output.strip().split("\n")
|
||||
columns = lines[0].split("\t")
|
||||
data = [columns]
|
||||
for line in lines[1:]:
|
||||
row = line.split("\t")
|
||||
data.append(row[0].strip())
|
||||
|
||||
commits = []
|
||||
changes = []
|
||||
dates = []
|
||||
for i in range(len(data)):
|
||||
line = data[i]
|
||||
|
||||
if "Commit:" in line:
|
||||
commits.append(line.replace("Commit: ", ""))
|
||||
|
||||
if "Subject:" in line:
|
||||
changes.append(line.replace("Subject: ", ""))
|
||||
|
||||
if "Date:" in line:
|
||||
dates.append(line.replace("Date: ", ""))
|
||||
|
||||
for i in range(len(commits)):
|
||||
self.versions.append([commits[i], changes[i], dates[i]])
|
||||
|
||||
def commits_callback(self):
|
||||
group_button = Gtk.CheckButton(visible=False)
|
||||
self.versions_group.add(group_button)
|
||||
for i in range(len(self.versions)):
|
||||
version = self.versions[i]
|
||||
date_time = version[2].split(" ")
|
||||
date = date_time[0].split("-")
|
||||
offset = date_time[2][:3] + ":" + date_time[2][3:]
|
||||
time = date_time[1].split(":")
|
||||
display_time = GLib.DateTime.new(
|
||||
GLib.TimeZone.new(offset),
|
||||
int(date[0]),
|
||||
int(date[1]),
|
||||
int(date[2]),
|
||||
int(time[0]),
|
||||
int(time[1]),
|
||||
int(time[2]),
|
||||
)
|
||||
display_time = display_time.format("%x %X")
|
||||
change = version[1].split("(")
|
||||
row = Adw.ActionRow(
|
||||
title=GLib.markup_escape_text(change[0]), subtitle=str(display_time)
|
||||
)
|
||||
row.set_tooltip_text(_("Commit Hash: {}").format(version[0]))
|
||||
select = Gtk.CheckButton()
|
||||
select.connect("toggled", self.selection_handler, i)
|
||||
select.set_group(group_button)
|
||||
|
||||
version.append(select)
|
||||
row.set_activatable_widget(select)
|
||||
row.add_prefix(select)
|
||||
self.versions_group.add(row)
|
||||
self.main_stack.set_visible_child(self.outerbox)
|
||||
self.apply_button.set_visible(True)
|
||||
self.mask_row.grab_focus() # Don't know why, but I need this in order for escape to close the window
|
||||
|
||||
def generate_list(self):
|
||||
task = Gio.Task.new(None, None, lambda *_: self.commits_callback())
|
||||
task.run_in_thread(lambda *_: self.get_commits())
|
||||
|
||||
def downgrade_callack(self):
|
||||
self.set_can_close(True)
|
||||
|
||||
if self.response != 0:
|
||||
self.parent_window.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not downgrade {}").format(self.app_name))
|
||||
)
|
||||
self.apply_button.set_sensitive(True)
|
||||
|
||||
self.parent_window.refresh_list_of_flatpaks(self)
|
||||
self.close()
|
||||
|
||||
def downgrade_thread(self):
|
||||
mask_list = None
|
||||
if self.install_type == "system":
|
||||
mask_list = self.parent_window.system_mask_list
|
||||
if self.install_type == "user":
|
||||
mask_list = self.parent_window.user_mask_list
|
||||
self.response = self.my_utils.downgrade_flatpak(
|
||||
self.app_id, self.app_ref, self.commit_to_use, self.install_type, self.mask_row.get_active(), mask_list
|
||||
)
|
||||
|
||||
def on_apply(self):
|
||||
self.loading_label.set_label(_("Downgrading…"))
|
||||
self.set_can_close(False)
|
||||
self.main_stack.set_visible_child(self.loading)
|
||||
self.apply_button.set_visible(False)
|
||||
|
||||
task = Gio.Task.new(None, None, lambda *_: self.downgrade_callack())
|
||||
task.run_in_thread(lambda *_: self.downgrade_thread())
|
||||
|
||||
def __init__(self, parent_window, flatpak_row, index, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Create Variables
|
||||
self.my_utils = myUtils(self)
|
||||
self.app_name = flatpak_row[0]
|
||||
self.app_id = flatpak_row[2]
|
||||
self.remote = flatpak_row[6]
|
||||
self.install_type = flatpak_row[7]
|
||||
self.app_ref = flatpak_row[8]
|
||||
self.versions = []
|
||||
self.commit_to_use = ""
|
||||
self.parent_window = parent_window
|
||||
self.flatpak_row = flatpak_row
|
||||
self.response = 0
|
||||
self.window_title = _("Downgrade {}").format(self.app_name)
|
||||
self.index = index
|
||||
|
||||
# Connections
|
||||
self.apply_button.connect("clicked", lambda *_: self.on_apply())
|
||||
|
||||
# Apply
|
||||
self.mask_row.set_subtitle(
|
||||
_("Ensure that {} will never be updated to a newer version").format(
|
||||
self.app_name
|
||||
)
|
||||
)
|
||||
|
||||
self.set_title(self.window_title)
|
||||
|
||||
self.generate_list()
|
||||
|
||||
self.present(parent_window)
|
||||
@@ -1,226 +0,0 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/filter.ui")
|
||||
class FilterWindow(Adw.Dialog):
|
||||
__gtype_name__ = "FilterWindow"
|
||||
|
||||
show_apps_switch = Gtk.Template.Child()
|
||||
show_runtimes_switch = Gtk.Template.Child()
|
||||
remotes_expander = Gtk.Template.Child()
|
||||
runtimes_expander = Gtk.Template.Child()
|
||||
reset_button = Gtk.Template.Child()
|
||||
|
||||
is_open = False
|
||||
|
||||
def gsettings_bool_set(self, key, value):
|
||||
self.settings.set_boolean(key, value)
|
||||
self.check_is_resetable()
|
||||
self.main_window.apply_filter()
|
||||
|
||||
def check_is_resetable(self):
|
||||
if not self.show_apps_switch.get_active():
|
||||
self.reset_button.set_sensitive(True)
|
||||
return
|
||||
if self.show_runtimes_switch.get_active():
|
||||
self.reset_button.set_sensitive(True)
|
||||
return
|
||||
if self.total_remotes_selected != 0:
|
||||
self.reset_button.set_sensitive(True)
|
||||
return
|
||||
if self.total_runtimes_selected != 0:
|
||||
self.reset_button.set_sensitive(True)
|
||||
return
|
||||
self.reset_button.set_sensitive(False)
|
||||
|
||||
def row_subtitle_updater(self):
|
||||
if self.total_runtimes_selected > 0:
|
||||
self.runtimes_expander.set_subtitle(
|
||||
_("{} selected").format(self.total_runtimes_selected)
|
||||
)
|
||||
else:
|
||||
self.runtimes_expander.set_subtitle("")
|
||||
if self.total_remotes_selected > 0:
|
||||
self.remotes_expander.set_subtitle(
|
||||
_("{} selected").format(self.total_remotes_selected)
|
||||
)
|
||||
else:
|
||||
self.remotes_expander.set_subtitle("")
|
||||
|
||||
def reset_filter_gsettings(self):
|
||||
self.show_apps_switch.set_active(True)
|
||||
self.show_runtimes_switch.set_active(False)
|
||||
for button in self.remote_checkboxes:
|
||||
button.set_active(False)
|
||||
for button in self.runtime_checkboxes:
|
||||
button.set_active(False)
|
||||
for key in self.settings.list_keys():
|
||||
self.settings.reset(key)
|
||||
self.total_remotes_selected = 0
|
||||
self.total_runtimes_selected = 0
|
||||
self.row_subtitle_updater()
|
||||
self.reset_button.set_sensitive(False)
|
||||
|
||||
def runtime_handler(self, button, runtime):
|
||||
if button.get_active():
|
||||
self.total_runtimes_selected += 1
|
||||
self.runtimes_string = self.runtimes_string.replace("all", "")
|
||||
self.runtimes_string += f"{runtime},"
|
||||
else:
|
||||
self.total_runtimes_selected -= 1
|
||||
self.runtimes_string = self.runtimes_string.replace(f"{runtime},", "")
|
||||
if len(self.runtimes_string) < 1:
|
||||
self.runtimes_string += "all"
|
||||
self.settings.set_string("runtimes-list", self.runtimes_string)
|
||||
self.check_is_resetable()
|
||||
self.row_subtitle_updater()
|
||||
self.main_window.apply_filter()
|
||||
|
||||
def remote_handler(self, button, remote, install_type):
|
||||
if button.get_active():
|
||||
self.total_remotes_selected += 1
|
||||
self.remotes_string = self.remotes_string.replace("all", "")
|
||||
self.remotes_string += f"{remote}<>{install_type};"
|
||||
else:
|
||||
self.total_remotes_selected -= 1
|
||||
self.remotes_string = self.remotes_string.replace(
|
||||
f"{remote}<>{install_type};", ""
|
||||
)
|
||||
if len(self.remotes_string) < 1:
|
||||
self.remotes_string += "all"
|
||||
self.settings.set_string("remotes-list", self.remotes_string)
|
||||
self.check_is_resetable()
|
||||
self.row_subtitle_updater()
|
||||
self.main_window.apply_filter()
|
||||
|
||||
def generate_remotes(self):
|
||||
if (
|
||||
len(self.host_remotes) < 2
|
||||
): # Don't give the ability to filter by remotes if there is only 1
|
||||
self.remotes_expander.set_visible(False)
|
||||
|
||||
total = 0
|
||||
for i in range(len(self.host_remotes)):
|
||||
try:
|
||||
name = self.host_remotes[i][0]
|
||||
title = self.host_remotes[i][1]
|
||||
url = self.host_remotes[i][2]
|
||||
install_type = self.my_utils.get_install_type(self.host_remotes[i][7])
|
||||
remote_row = Adw.ActionRow(title=title)
|
||||
if "disabled" in install_type:
|
||||
continue
|
||||
total += 1
|
||||
if title == "-":
|
||||
remote_row.set_title(name)
|
||||
self.remotes_expander.add_row(remote_row)
|
||||
label = Gtk.Label(label=("{} wide").format(install_type))
|
||||
label.add_css_class("subtitle")
|
||||
remote_check = Gtk.CheckButton()
|
||||
if name in self.remotes_string:
|
||||
remote_check.set_active(True)
|
||||
self.total_remotes_selected += 1
|
||||
remote_check.connect(
|
||||
"toggled",
|
||||
lambda button=remote_check, remote=name, install_type=install_type: self.remote_handler(
|
||||
button, remote, install_type
|
||||
),
|
||||
)
|
||||
self.remote_checkboxes.append(remote_check)
|
||||
|
||||
if "user" in install_type:
|
||||
remote_row.set_subtitle(_("User wide"))
|
||||
elif "system" in install_type:
|
||||
remote_row.set_subtitle(_("System wide"))
|
||||
else:
|
||||
remote_row.set_subtitle(_("Unknown install type"))
|
||||
|
||||
remote_row.add_suffix(remote_check)
|
||||
remote_row.set_activatable_widget(remote_check)
|
||||
except Exception as e:
|
||||
print(
|
||||
"error at filter_window.generate_remotes: Could not make remote row. error",
|
||||
e,
|
||||
)
|
||||
|
||||
self.row_subtitle_updater()
|
||||
if total < 2:
|
||||
self.remotes_expander.set_visible(False)
|
||||
|
||||
def generate_runtimes(self):
|
||||
if (
|
||||
len(self.dependent_runtimes) < 2
|
||||
): # Don't give the ability to filter by runtimes if there is only 1
|
||||
self.runtimes_expander.set_visible(False)
|
||||
|
||||
for current in self.dependent_runtimes:
|
||||
runtime_row = Adw.ActionRow(title=current)
|
||||
runtime_check = Gtk.CheckButton()
|
||||
if current in self.runtimes_string:
|
||||
runtime_check.set_active(True)
|
||||
self.total_runtimes_selected += 1
|
||||
runtime_check.connect(
|
||||
"toggled",
|
||||
lambda button=runtime_check, runtime=current: self.runtime_handler(
|
||||
button, runtime
|
||||
),
|
||||
)
|
||||
self.runtime_checkboxes.append(runtime_check)
|
||||
runtime_row.add_suffix(runtime_check)
|
||||
runtime_row.set_activatable_widget(runtime_check)
|
||||
self.runtimes_expander.add_row(runtime_row)
|
||||
self.row_subtitle_updater()
|
||||
|
||||
def __init__(self, main_window, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Create Variables
|
||||
self.main_window = main_window
|
||||
self.my_utils = myUtils(self)
|
||||
self.host_remotes = self.my_utils.get_host_remotes()
|
||||
self.dependent_runtimes = self.my_utils.get_dependent_runtimes()
|
||||
self.settings = Gio.Settings.new("io.github.flattool.Warehouse.filter")
|
||||
self.remotes_string = self.settings.get_string("remotes-list")
|
||||
self.runtimes_string = self.settings.get_string("runtimes-list")
|
||||
self.remote_checkboxes = []
|
||||
self.runtime_checkboxes = []
|
||||
self.total_remotes_selected = 0
|
||||
self.total_runtimes_selected = 0
|
||||
|
||||
self.show_apps_switch.set_active(self.settings.get_boolean("show-apps"))
|
||||
self.show_runtimes_switch.set_active(self.settings.get_boolean("show-runtimes"))
|
||||
|
||||
# Connections
|
||||
self.show_apps_switch.connect(
|
||||
"state-set",
|
||||
lambda button, state: self.gsettings_bool_set("show-apps", state),
|
||||
)
|
||||
self.show_runtimes_switch.connect(
|
||||
"state-set",
|
||||
lambda button, state: self.gsettings_bool_set("show-runtimes", state),
|
||||
)
|
||||
self.reset_button.connect("clicked", lambda *_: self.reset_filter_gsettings())
|
||||
|
||||
# Calls
|
||||
if self.host_remotes[0][0] == "":
|
||||
self.remotes_expander.set_visible(False)
|
||||
else:
|
||||
self.generate_remotes()
|
||||
|
||||
if self.dependent_runtimes == []:
|
||||
self.runtimes_expander.set_visible(False)
|
||||
else:
|
||||
self.generate_runtimes()
|
||||
self.check_is_resetable()
|
||||
|
||||
def set_is_open_false(*args):
|
||||
self.__class__.is_open = False
|
||||
self.connect("closed", set_is_open_false)
|
||||
if self.__class__.is_open:
|
||||
return
|
||||
else:
|
||||
self.present(main_window)
|
||||
self.__class__.is_open = True
|
||||
14
src/main.py
14
src/main.py
@@ -27,13 +27,7 @@ gi.require_version("Adw", "1")
|
||||
|
||||
from gi.repository import Gtk, Gio, Adw, GLib
|
||||
from .window import WarehouseWindow
|
||||
from .remotes_window import RemotesWindow
|
||||
from .orphans_window import OrphansWindow
|
||||
from .filter_window import FilterWindow
|
||||
from .search_install_window import SearchInstallWindow
|
||||
from .const import Config
|
||||
from .common import myUtils
|
||||
|
||||
|
||||
class WarehouseApplication(Adw.Application):
|
||||
"""The main application singleton class."""
|
||||
@@ -104,14 +98,6 @@ class WarehouseApplication(Adw.Application):
|
||||
lang=lang,
|
||||
)
|
||||
|
||||
self.my_utils = myUtils(self)
|
||||
total = 0
|
||||
for rem in self.my_utils.get_host_remotes():
|
||||
if self.my_utils.get_install_type(rem[7]) != "disabled":
|
||||
total += 1
|
||||
if total < 1:
|
||||
self.lookup_action(f"open-search-install").set_enabled(False)
|
||||
|
||||
def open_search_install(self, widget, _):
|
||||
SearchInstallWindow(self.props.active_window)
|
||||
|
||||
|
||||
73
src/main_window/window.blp
Normal file
73
src/main_window/window.blp
Normal file
@@ -0,0 +1,73 @@
|
||||
using Gtk 4.0;
|
||||
using Adw 1;
|
||||
|
||||
template $WarehouseWindow: Adw.ApplicationWindow {
|
||||
title: "Warehouse";
|
||||
content:
|
||||
Adw.OverlaySplitView {
|
||||
sidebar:
|
||||
Adw.ToolbarView main_toolbar_view {
|
||||
[top]
|
||||
Adw.HeaderBar header_bar {
|
||||
[end]
|
||||
MenuButton main_menu {
|
||||
icon-name: "open-menu-symbolic";
|
||||
tooltip-text: _("Main Menu");
|
||||
menu-model: primary_menu;
|
||||
}
|
||||
}
|
||||
}
|
||||
;
|
||||
content:
|
||||
Adw.ToolbarView {
|
||||
[top]
|
||||
Adw.HeaderBar {}
|
||||
}
|
||||
;
|
||||
}
|
||||
;
|
||||
}
|
||||
|
||||
menu primary_menu {
|
||||
section {
|
||||
item {
|
||||
label: _("Manage Leftover Data…");
|
||||
action: "app.manage-data-folders";
|
||||
}
|
||||
|
||||
/*item {
|
||||
label: _("_Preferences");
|
||||
action: "app.preferences";
|
||||
}*/
|
||||
item {
|
||||
label: _("Manage Remotes…");
|
||||
action: "app.show-remotes-window";
|
||||
}
|
||||
}
|
||||
section {
|
||||
item {
|
||||
label: _("Install From File…");
|
||||
action: "app.install-from-file";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("Install From The Web…");
|
||||
action: "app.open-search-install";
|
||||
}
|
||||
}
|
||||
section {
|
||||
item {
|
||||
label: _("Refresh List");
|
||||
action: "app.refresh-list";
|
||||
}
|
||||
item {
|
||||
label: _("_Keyboard Shortcuts");
|
||||
action: "win.show-help-overlay";
|
||||
}
|
||||
|
||||
item {
|
||||
label: _("_About Warehouse");
|
||||
action: "app.about";
|
||||
}
|
||||
}
|
||||
}
|
||||
64
src/main_window/window.py
Normal file
64
src/main_window/window.py
Normal file
@@ -0,0 +1,64 @@
|
||||
# window.py
|
||||
#
|
||||
# Copyright 2023 Heliguy
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, version 3 of the License only.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import re
|
||||
import time
|
||||
|
||||
from gi.repository import Adw, Gdk, Gio, GLib, Gtk
|
||||
from .const import Config
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/main_window/window.ui")
|
||||
class WarehouseWindow(Adw.ApplicationWindow):
|
||||
__gtype_name__ = "WarehouseWindow"
|
||||
|
||||
def key_handler(self, controller, keyval, keycode, state):
|
||||
if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK:
|
||||
self.close()
|
||||
if keyval == Gdk.KEY_Escape:
|
||||
self.batch_mode_button.set_active(False)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.set_size_request(360, 360)
|
||||
self.settings = Gio.Settings.new("io.github.flattool.Warehouse")
|
||||
self.settings.bind(
|
||||
"window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT
|
||||
)
|
||||
self.settings.bind(
|
||||
"window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT
|
||||
)
|
||||
self.settings.bind(
|
||||
"is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT
|
||||
)
|
||||
self.settings.bind(
|
||||
"is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT
|
||||
)
|
||||
|
||||
event_controller = Gtk.EventControllerKey()
|
||||
event_controller.connect("key-pressed", self.key_handler)
|
||||
self.add_controller(event_controller)
|
||||
|
||||
file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY)
|
||||
# file_drop.connect("drop", self.drop_callback)
|
||||
# self.scrolled_window.add_controller(file_drop)
|
||||
|
||||
if Config.DEVEL:
|
||||
self.add_css_class("devel")
|
||||
@@ -1,18 +1,11 @@
|
||||
pkgdatadir = join_paths(get_option('prefix'), get_option('datadir'), meson.project_name())
|
||||
moduledir = join_paths(pkgdatadir, 'flattool_gui')
|
||||
moduledir = join_paths(pkgdatadir, 'Warehouse')
|
||||
gnome = import('gnome')
|
||||
|
||||
blueprints = custom_target('blueprints',
|
||||
input: files(
|
||||
'gtk/help-overlay.blp',
|
||||
'../data/ui/window.blp',
|
||||
'../data/ui/orphans.blp',
|
||||
'../data/ui/filter.blp',
|
||||
'../data/ui/remotes.blp',
|
||||
'../data/ui/downgrade.blp',
|
||||
'../data/ui/search_install.blp',
|
||||
'../data/ui/snapshots.blp',
|
||||
'../data/ui/properties.blp',
|
||||
'main_window/window.blp',
|
||||
),
|
||||
output: '.',
|
||||
command: [find_program('blueprint-compiler'), 'batch-compile', '@OUTPUT@', '@CURRENT_SOURCE_DIR@', '@INPUT@'],
|
||||
@@ -54,31 +47,8 @@ configure_file(
|
||||
warehouse_sources = [
|
||||
'__init__.py',
|
||||
'main.py',
|
||||
'common.py',
|
||||
'window.py',
|
||||
'app_row_widget.py',
|
||||
'main_window/window.py',
|
||||
'../data/style.css',
|
||||
|
||||
'properties_window.py',
|
||||
'../data/ui/properties.blp',
|
||||
|
||||
'orphans_window.py',
|
||||
'../data/ui/orphans.blp',
|
||||
|
||||
'remotes_window.py',
|
||||
'../data/ui/remotes.blp',
|
||||
|
||||
'filter_window.py',
|
||||
'../data/ui/filter.blp',
|
||||
|
||||
'downgrade_window.py',
|
||||
'../data/ui/downgrade.blp',
|
||||
|
||||
'search_install_window.py',
|
||||
'../data/ui/search_install.blp',
|
||||
|
||||
'snapshots_window.py',
|
||||
'../data/ui/snapshots.blp',
|
||||
]
|
||||
|
||||
configure_file(
|
||||
|
||||
@@ -1,353 +0,0 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/orphans.ui")
|
||||
class OrphansWindow(Adw.Dialog):
|
||||
__gtype_name__ = "OrphansWindow"
|
||||
|
||||
list_of_data = Gtk.Template.Child()
|
||||
install_button = Gtk.Template.Child()
|
||||
trash_button = Gtk.Template.Child()
|
||||
select_all_button = Gtk.Template.Child()
|
||||
main_overlay = Gtk.Template.Child()
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
main_stack = Gtk.Template.Child()
|
||||
no_data = Gtk.Template.Child()
|
||||
no_results = Gtk.Template.Child()
|
||||
action_bar = Gtk.Template.Child()
|
||||
search_button = Gtk.Template.Child()
|
||||
search_bar = Gtk.Template.Child()
|
||||
search_entry = Gtk.Template.Child()
|
||||
oepn_folder_button = Gtk.Template.Child()
|
||||
installing = Gtk.Template.Child()
|
||||
main_box = Gtk.Template.Child()
|
||||
installing_status = Gtk.Template.Child()
|
||||
|
||||
window_title = _("Manage Leftover Data")
|
||||
host_home = str(pathlib.Path.home())
|
||||
user_data_path = host_home + "/.var/app/"
|
||||
should_select_all = False
|
||||
selected_remote = ""
|
||||
selected_remote_install_type = ""
|
||||
is_result = False
|
||||
is_installing = False
|
||||
is_open = False
|
||||
|
||||
def key_handler(self, controller, keyval, keycode, state):
|
||||
if keyval == Gdk.KEY_Escape or (
|
||||
keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK
|
||||
):
|
||||
self.close()
|
||||
|
||||
def selection_handler(self, widget, dir_name):
|
||||
if widget.get_active():
|
||||
self.selected_dirs.append(dir_name)
|
||||
else:
|
||||
self.selected_dirs.remove(dir_name)
|
||||
|
||||
if len(self.selected_dirs) == 0:
|
||||
self.set_title(
|
||||
self.window_title
|
||||
) # Set the window title back to what it was when there are no selected dirs
|
||||
else:
|
||||
self.set_title(
|
||||
("{} selected").format(str(len(self.selected_dirs)))
|
||||
) # Set the window title to the amount of selected dirs
|
||||
|
||||
if len(self.selected_dirs) == 0:
|
||||
self.install_button.set_sensitive(False)
|
||||
self.trash_button.set_sensitive(False)
|
||||
else:
|
||||
self.install_button.set_sensitive(True)
|
||||
self.trash_button.set_sensitive(True)
|
||||
|
||||
def select_all_handler(self, button):
|
||||
for check in self.check_buttons:
|
||||
check.set_active(button.get_active())
|
||||
|
||||
def install_callback(self, *_args):
|
||||
self.is_installing = False
|
||||
self.generate_list()
|
||||
self.progress_bar.set_visible(False)
|
||||
self.app_window.refresh_list_of_flatpaks(self)
|
||||
self.set_can_close(True) # Make window able to close
|
||||
self.search_button.set_sensitive(True)
|
||||
if self.my_utils.install_success:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Installed successfully")))
|
||||
else:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not install some apps"))
|
||||
)
|
||||
|
||||
def install_handler(self):
|
||||
self.is_installing = True
|
||||
self.main_stack.set_visible_child(self.installing)
|
||||
self.search_button.set_sensitive(False)
|
||||
self.set_title(self.window_title)
|
||||
self.keep_checking = True
|
||||
task = Gio.Task.new(None, None, self.install_callback)
|
||||
task.run_in_thread(
|
||||
lambda _task, _obj, _data, _cancellable, id_list=self.selected_dirs, remote=self.selected_remote, app_type=self.selected_remote_type, progress_bar=self.progress_bar, status_label=self.installing_status: self.my_utils.install_flatpak(
|
||||
id_list, remote, app_type, progress_bar, status_label
|
||||
)
|
||||
)
|
||||
|
||||
def install_button_handler(self, button):
|
||||
def remote_select_handler(button, index):
|
||||
if not button.get_active():
|
||||
return
|
||||
self.selected_remote = self.host_remotes[index][0]
|
||||
self.selected_remote_type = self.my_utils.get_install_type(
|
||||
self.host_remotes[index][7]
|
||||
)
|
||||
|
||||
def on_response(dialog, response_id, _function):
|
||||
if response_id == "cancel":
|
||||
return
|
||||
self.install_handler()
|
||||
self.progress_bar.set_visible(True)
|
||||
self.action_bar.set_visible(False)
|
||||
self.set_can_close(False) # Make window unable to close
|
||||
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Attempt to Install?"),
|
||||
_("Warehouse will attempt to install apps matching the selected data."),
|
||||
)
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Install"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED)
|
||||
|
||||
height = 65 * len(self.host_remotes)
|
||||
max = 400
|
||||
if height > max:
|
||||
height = max
|
||||
remotes_scroll = Gtk.ScrolledWindow(vexpand=True, min_content_height=height)
|
||||
remote_list = Gtk.ListBox(selection_mode="none", valign="start")
|
||||
remotes_scroll.set_child(remote_list)
|
||||
remote_list.add_css_class("boxed-list")
|
||||
|
||||
total_added = 0
|
||||
remote_select_buttons = []
|
||||
for i in range(len(self.host_remotes)):
|
||||
title = self.host_remotes[i][1]
|
||||
name = self.host_remotes[i][0]
|
||||
type_arr = self.host_remotes[i][7]
|
||||
if "disabled" in type_arr:
|
||||
continue
|
||||
remote_row = Adw.ActionRow(title=title)
|
||||
remote_select = Gtk.CheckButton()
|
||||
remote_select_buttons.append(remote_select)
|
||||
remote_select.connect("toggled", remote_select_handler, i)
|
||||
remote_row.set_activatable_widget(remote_select)
|
||||
|
||||
type = self.my_utils.get_install_type(type_arr)
|
||||
if type == "user":
|
||||
remote_row.set_subtitle(_("User wide"))
|
||||
elif type == "system":
|
||||
remote_row.set_subtitle(_("System wide"))
|
||||
else:
|
||||
remote_row.set_subtitle(_("Unknown install type"))
|
||||
|
||||
if remote_row.get_title() == "-":
|
||||
remote_row.set_title(self.host_remotes[i][0])
|
||||
|
||||
if total_added > 0:
|
||||
remote_select.set_group(remote_select_buttons[0])
|
||||
|
||||
remote_row.add_prefix(remote_select)
|
||||
remote_list.append(remote_row)
|
||||
total_added += 1
|
||||
|
||||
remote_select_buttons[0].set_active(True)
|
||||
|
||||
if total_added > 1:
|
||||
dialog.set_extra_child(remotes_scroll)
|
||||
|
||||
dialog.connect("response", on_response, dialog.choose_finish)
|
||||
dialog.present(self)
|
||||
|
||||
def trash_handler(self, button):
|
||||
def on_response(dialog, response_id, _function):
|
||||
if response_id == "cancel":
|
||||
return
|
||||
for i in range(len(self.selected_dirs)):
|
||||
path = self.user_data_path + self.selected_dirs[i]
|
||||
self.my_utils.trash_folder(path)
|
||||
self.select_all_button.set_active(False)
|
||||
self.generate_list()
|
||||
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Trash folders?"), _("These folders will be sent to the trash.")
|
||||
)
|
||||
dialog.connect("response", on_response, dialog.choose_finish)
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Continue"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.present(self)
|
||||
|
||||
def open_button_handler(self, _widget, path=user_data_path):
|
||||
try:
|
||||
Gio.AppInfo.launch_default_for_uri(f"file://{path}", None)
|
||||
except GLib.GError:
|
||||
selt.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder")))
|
||||
|
||||
def size_callback(self, row_index):
|
||||
row = self.list_of_data.get_row_at_index(row_index)
|
||||
row.set_subtitle(f"~{self.data_rows[row_index][1]}")
|
||||
|
||||
def size_thread(self, index, path):
|
||||
size = self.my_utils.get_size_with_format(path)
|
||||
self.data_rows[index].append(size)
|
||||
|
||||
# Create the list of folders in the window
|
||||
def generate_list(self):
|
||||
self.data_rows = []
|
||||
self.check_buttons = []
|
||||
self.host_flatpaks = self.my_utils.get_host_flatpaks()
|
||||
|
||||
if self.host_flatpaks == [["", ""]]:
|
||||
self.app_window.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not manage data"))
|
||||
)
|
||||
self.this_just_crashes_the_window_so_it_doesnt_open()
|
||||
return
|
||||
|
||||
self.list_of_data.remove_all()
|
||||
self.selected_dirs = []
|
||||
self.set_title(self.window_title)
|
||||
dir_list = os.listdir(self.user_data_path)
|
||||
|
||||
# This is a list that only holds IDs of install flatpaks
|
||||
id_list = []
|
||||
for i in range(len(self.host_flatpaks)):
|
||||
try:
|
||||
id_list.append(self.host_flatpaks[i][2])
|
||||
except:
|
||||
print("Could not get data")
|
||||
|
||||
for i in range(len(dir_list)):
|
||||
dir_name = dir_list[i]
|
||||
|
||||
# Skip item if it has a matching flatpak
|
||||
if dir_name in id_list:
|
||||
continue
|
||||
|
||||
# Create row element
|
||||
dir_row = Adw.ActionRow(title=dir_name)
|
||||
self.data_rows.append([dir_row])
|
||||
path = self.user_data_path + dir_name
|
||||
index = len(self.data_rows) - 1
|
||||
task = Gio.Task.new(
|
||||
None, None, lambda *_, index=index: self.size_callback(index)
|
||||
)
|
||||
task.run_in_thread(
|
||||
lambda _task, _obj, _data, _cancellable, *_, index=index: self.size_thread(
|
||||
index, path
|
||||
)
|
||||
)
|
||||
|
||||
open_row_button = Gtk.Button(
|
||||
icon_name="document-open-symbolic",
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_("Open User Data Folder"),
|
||||
)
|
||||
open_row_button.add_css_class("flat")
|
||||
open_row_button.connect(
|
||||
"clicked", self.open_button_handler, (self.user_data_path + dir_name)
|
||||
)
|
||||
dir_row.add_suffix(open_row_button)
|
||||
|
||||
select_button = Gtk.CheckButton(tooltip_text=_("Select"))
|
||||
self.check_buttons.append(select_button)
|
||||
select_button.add_css_class("selection-mode")
|
||||
select_button.connect("toggled", self.selection_handler, dir_name)
|
||||
dir_row.add_suffix(select_button)
|
||||
dir_row.set_activatable_widget(select_button)
|
||||
|
||||
# Add row to list
|
||||
self.list_of_data.append(dir_row)
|
||||
|
||||
if self.list_of_data.get_row_at_index(0) == None:
|
||||
self.main_stack.set_visible_child(self.no_data)
|
||||
self.action_bar.set_visible(False)
|
||||
else:
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.action_bar.set_visible(True)
|
||||
|
||||
def filter_func(self, row):
|
||||
if self.search_entry.get_text().lower() in row.get_title().lower():
|
||||
self.is_result = True
|
||||
return True
|
||||
|
||||
def on_invalidate(self, row):
|
||||
if self.is_installing:
|
||||
return
|
||||
if self.list_of_data.get_row_at_index(0) == None:
|
||||
self.main_stack.set_visible_child(self.no_data)
|
||||
self.action_bar.set_visible(False)
|
||||
else:
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.action_bar.set_visible(True)
|
||||
|
||||
self.is_result = False
|
||||
self.list_of_data.invalidate_filter()
|
||||
if self.is_result == False:
|
||||
self.main_stack.set_visible_child(self.no_results)
|
||||
self.action_bar.set_visible(False)
|
||||
|
||||
def on_change(self, prop, prop2):
|
||||
if self.is_installing:
|
||||
return
|
||||
if self.search_bar.get_search_mode() == False:
|
||||
if self.list_of_data.get_row_at_index(0) == None:
|
||||
self.main_stack.set_visible_child(self.no_data)
|
||||
self.action_bar.set_visible(False)
|
||||
else:
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.action_bar.set_visible(True)
|
||||
|
||||
def __init__(self, main_window, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.my_utils = myUtils(
|
||||
self
|
||||
) # Access common utils and set the window to this window
|
||||
self.host_remotes = self.my_utils.get_host_remotes()
|
||||
self.host_flatpaks = self.my_utils.get_host_flatpaks()
|
||||
|
||||
self.progress_bar = Gtk.ProgressBar(visible=False)
|
||||
self.progress_bar.add_css_class("osd")
|
||||
self.app_window = main_window
|
||||
|
||||
self.generate_list()
|
||||
|
||||
event_controller = Gtk.EventControllerKey()
|
||||
event_controller.connect("key-pressed", self.key_handler)
|
||||
self.add_controller(event_controller)
|
||||
|
||||
self.install_button.connect("clicked", self.install_button_handler)
|
||||
if self.host_remotes[0][0] == "":
|
||||
self.install_button.set_visible(False)
|
||||
self.trash_button.connect("clicked", self.trash_handler)
|
||||
self.select_all_button.connect("toggled", self.select_all_handler)
|
||||
self.main_overlay.add_overlay(self.progress_bar)
|
||||
|
||||
self.list_of_data.set_filter_func(self.filter_func)
|
||||
self.search_entry.connect("search-changed", self.on_invalidate)
|
||||
self.search_bar.connect("notify", self.on_change)
|
||||
self.search_bar.connect_entry(self.search_entry)
|
||||
self.oepn_folder_button.connect("clicked", self.open_button_handler)
|
||||
|
||||
def set_is_open_false(*args):
|
||||
self.__class__.is_open = False
|
||||
self.connect("closed", set_is_open_false)
|
||||
if self.__class__.is_open:
|
||||
return
|
||||
else:
|
||||
self.present(main_window)
|
||||
self.__class__.is_open = True
|
||||
@@ -1,217 +0,0 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/properties.ui")
|
||||
class PropertiesWindow(Adw.Dialog):
|
||||
__gtype_name__ = "PropertiesWindow"
|
||||
|
||||
new_env = dict(os.environ)
|
||||
new_env["LC_ALL"] = "C"
|
||||
host_home = str(pathlib.Path.home())
|
||||
user_data_path = host_home + "/.var/app/"
|
||||
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
upper = Gtk.Template.Child()
|
||||
app_icon = Gtk.Template.Child()
|
||||
data_row = Gtk.Template.Child()
|
||||
open_data = Gtk.Template.Child()
|
||||
trash_data = Gtk.Template.Child()
|
||||
spinner = Gtk.Template.Child()
|
||||
details = Gtk.Template.Child()
|
||||
view_apps = Gtk.Template.Child()
|
||||
runtime = Gtk.Template.Child()
|
||||
runtime_properties = Gtk.Template.Child()
|
||||
runtime_copy = Gtk.Template.Child()
|
||||
lower = Gtk.Template.Child()
|
||||
eol_app_banner = Gtk.Template.Child()
|
||||
eol_runtime_banner = Gtk.Template.Child()
|
||||
mask_banner = Gtk.Template.Child()
|
||||
name = Gtk.Template.Child()
|
||||
description = Gtk.Template.Child()
|
||||
description_button = Gtk.Template.Child()
|
||||
|
||||
def copy_item(self, to_copy, to_toast=None):
|
||||
self.get_clipboard().set(to_copy)
|
||||
if to_toast:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Copied {}").format(to_toast)))
|
||||
|
||||
def open_button_handler(self, widget):
|
||||
try:
|
||||
Gio.AppInfo.launch_default_for_uri(f"file://{self.user_data_path}", None)
|
||||
except GLib.GError:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder")))
|
||||
|
||||
def show_details(self, widget):
|
||||
try:
|
||||
Gio.AppInfo.launch_default_for_uri(f"appstream://{self.app_id}", None)
|
||||
except GLib.GError:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not show details")))
|
||||
|
||||
def get_size_callback(self, *args):
|
||||
self.open_data.set_visible(True)
|
||||
self.open_data.connect("clicked", self.open_button_handler)
|
||||
self.trash_data.set_visible(True)
|
||||
self.data_row.set_title(_("User Data"))
|
||||
self.data_row.set_subtitle(f"~{self.size}")
|
||||
self.spinner.set_visible(False)
|
||||
|
||||
def get_size_thread(self, *args):
|
||||
self.size = self.my_utils.get_size_with_format(self.user_data_path)
|
||||
|
||||
def generate_upper(self):
|
||||
self.description_button.connect(
|
||||
"clicked",
|
||||
lambda *_a: self.copy_item(
|
||||
self.description.get_label(), _("Description")
|
||||
)
|
||||
)
|
||||
image = self.my_utils.find_app_icon(self.app_id)
|
||||
self.runtime.set_subtitle(self.current_flatpak[13])
|
||||
if image.get_paintable() == None:
|
||||
self.app_icon.set_from_icon_name(image.get_icon_name())
|
||||
else:
|
||||
self.app_icon.set_from_paintable(image.get_paintable())
|
||||
|
||||
if os.path.exists(self.user_data_path):
|
||||
task = Gio.Task.new(None, None, self.get_size_callback)
|
||||
task.run_in_thread(self.get_size_thread)
|
||||
else:
|
||||
self.data_row.set_title("")
|
||||
self.data_row.set_subtitle(_("No User Data"))
|
||||
self.spinner.set_visible(False)
|
||||
|
||||
if "runtime" in self.current_flatpak[12]:
|
||||
# Pak is a runtime
|
||||
self.runtime.set_visible(False)
|
||||
if self.app_ref in self.parent_window.dependent_runtimes:
|
||||
self.view_apps.set_visible(True)
|
||||
|
||||
def generate_lower(self):
|
||||
info = self.my_utils.get_flatpak_info(self.app_ref, self.install_type)
|
||||
name_desc = info["name"].split(" - ")
|
||||
self.name.set_label((name_desc[0]))
|
||||
try:
|
||||
self.description.set_label((name_desc[1]))
|
||||
except:
|
||||
pass
|
||||
for key in info.keys():
|
||||
if key == "name":
|
||||
continue
|
||||
row = Adw.ActionRow(
|
||||
title=GLib.markup_escape_text(key),
|
||||
subtitle=GLib.markup_escape_text(info[key]),
|
||||
activatable=True,
|
||||
)
|
||||
row.add_suffix(Gtk.Image.new_from_icon_name("edit-copy-symbolic"))
|
||||
row.add_css_class("property")
|
||||
row.connect(
|
||||
"activated",
|
||||
lambda *_a, row=row: self.copy_item(
|
||||
row.get_subtitle(), row.get_title()
|
||||
),
|
||||
)
|
||||
self.lower.add(row)
|
||||
|
||||
def view_apps_handler(self, widget):
|
||||
settings = Gio.Settings.new("io.github.flattool.Warehouse.filter")
|
||||
for key in settings.list_keys():
|
||||
settings.reset(key)
|
||||
settings.set_string("runtimes-list", f"{self.app_ref},")
|
||||
self.parent_window.apply_filter()
|
||||
self.close()
|
||||
|
||||
def show_properties_handler(self):
|
||||
runtime = self.current_flatpak[13]
|
||||
for i in range(len(self.host_flatpaks)):
|
||||
# open the properties when the flatpak matches the runtime *and* installation type
|
||||
if runtime in self.host_flatpaks[i][8] and self.install_type in self.host_flatpaks[i][12]:
|
||||
PropertiesWindow(i, self.host_flatpaks, self.parent_window)
|
||||
self.close()
|
||||
return
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not show properties")))
|
||||
|
||||
def trash_data_handler(self):
|
||||
def on_response(_none, response, widget):
|
||||
if response == "cancel":
|
||||
return
|
||||
|
||||
if self.my_utils.trash_folder(self.user_data_path) == 0:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data")))
|
||||
self.data_row.set_title(_("No User Data"))
|
||||
self.data_row.set_subtitle("")
|
||||
self.open_data.set_visible(False)
|
||||
self.trash_data.set_visible(False)
|
||||
else:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not trash user data"))
|
||||
)
|
||||
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Send {}'s User Data to the Trash?").format(self.app_name)
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("continue", _("Trash Data"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", on_response, dialog.choose_finish)
|
||||
dialog.present(self)
|
||||
|
||||
def __init__(self, flatpak_index, host_flatpaks, parent_window, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.my_utils = myUtils(self)
|
||||
self.current_flatpak = host_flatpaks[flatpak_index]
|
||||
self.parent_window = parent_window
|
||||
self.host_flatpaks = host_flatpaks
|
||||
self.flatpak_index = flatpak_index
|
||||
|
||||
self.app_name = self.current_flatpak[0]
|
||||
self.app_id = self.current_flatpak[2]
|
||||
self.origin_remote = self.current_flatpak[6]
|
||||
self.install_type = self.current_flatpak[7]
|
||||
self.app_ref = self.current_flatpak[8]
|
||||
self.user_data_path += self.app_id
|
||||
|
||||
self.details.connect("activated", self.show_details)
|
||||
self.runtime_copy.connect(
|
||||
"clicked",
|
||||
lambda *_: self.copy_item(
|
||||
self.runtime.get_subtitle(), self.runtime.get_title()
|
||||
),
|
||||
)
|
||||
self.runtime_properties.connect(
|
||||
"clicked", lambda *_: self.show_properties_handler()
|
||||
)
|
||||
self.view_apps.connect("activated", self.view_apps_handler)
|
||||
self.trash_data.connect("clicked", lambda *_: self.trash_data_handler())
|
||||
|
||||
if "eol" in self.current_flatpak[12]:
|
||||
self.eol_app_banner.set_revealed(True)
|
||||
self.eol_app_banner.set_title(
|
||||
_(
|
||||
"{} has reached its End of Life and will not receive any security updates"
|
||||
).format(self.app_name)
|
||||
)
|
||||
|
||||
if self.current_flatpak[13] in parent_window.eol_list:
|
||||
self.eol_runtime_banner.set_revealed(True)
|
||||
self.eol_runtime_banner.set_title(
|
||||
_(
|
||||
"{}'s runtime has reached its End of Life and will not receive any security updates"
|
||||
).format(self.app_name)
|
||||
)
|
||||
|
||||
if self.app_id in self.my_utils.get_host_masks(
|
||||
"system"
|
||||
) or self.app_id in self.my_utils.get_host_masks("user"):
|
||||
self.mask_banner.set_revealed(True)
|
||||
self.mask_banner.set_title(
|
||||
_("{} is masked and will not be updated").format(self.app_name)
|
||||
)
|
||||
|
||||
self.generate_upper()
|
||||
self.generate_lower()
|
||||
self.present(parent_window)
|
||||
@@ -1,710 +0,0 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import re
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/remotes.ui")
|
||||
class RemotesWindow(Adw.Dialog):
|
||||
__gtype_name__ = "RemotesWindow"
|
||||
|
||||
remotes_list = Gtk.Template.Child()
|
||||
stack = Gtk.Template.Child()
|
||||
main_group = Gtk.Template.Child()
|
||||
no_remotes = Gtk.Template.Child()
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
popular_remotes_list = Gtk.Template.Child()
|
||||
add_from_file = Gtk.Template.Child()
|
||||
custom_remote = Gtk.Template.Child()
|
||||
refresh = Gtk.Template.Child()
|
||||
adding = Gtk.Template.Child()
|
||||
show_disabled_button = Gtk.Template.Child()
|
||||
show_disabled_button_button_content = Gtk.Template.Child()
|
||||
show_disabled = False
|
||||
|
||||
is_open = False
|
||||
rows_in_list = []
|
||||
rows_in_popular_list = []
|
||||
|
||||
def make_toast(self, text):
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(text))
|
||||
|
||||
def get_host_flatpaks(self):
|
||||
output = subprocess.run(
|
||||
["flatpak-spawn", "--host", "flatpak", "list", "--columns=all"],
|
||||
capture_output=True,
|
||||
text=True,
|
||||
env=self.new_env,
|
||||
).stdout
|
||||
lines = output.strip().split("\n")
|
||||
columns = lines[0].split("\t")
|
||||
data = [columns]
|
||||
for line in lines[1:]:
|
||||
row = line.split("\t")
|
||||
data.append(row)
|
||||
return data
|
||||
|
||||
def remove_on_response(self, _dialog, response_id, _function, index):
|
||||
if response_id == "cancel":
|
||||
return
|
||||
|
||||
name = self.host_remotes[index][0]
|
||||
title = self.host_remotes[index][1]
|
||||
install_type = self.host_remotes[index][7]
|
||||
if "user" in install_type:
|
||||
install_type = "user"
|
||||
if "system" in install_type:
|
||||
install_type = "system"
|
||||
command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"remote-delete",
|
||||
"--force",
|
||||
name,
|
||||
f"--{install_type}",
|
||||
]
|
||||
try:
|
||||
subprocess.run(command, capture_output=True, check=True, env=self.new_env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.make_toast(_("Could not remove {}").format(title))
|
||||
print("error in remotes_window.remove_on_response: CalledProcessError:", e)
|
||||
self.generate_list()
|
||||
|
||||
def remove_handler(self, _widget, index, popoever):
|
||||
popoever.popdown()
|
||||
name = self.host_remotes[index][0]
|
||||
title = self.host_remotes[index][1]
|
||||
install_type = self.host_remotes[index][7]
|
||||
|
||||
body_text = _("Any installed apps from {} will stop receiving updates").format(
|
||||
name
|
||||
)
|
||||
dialog = Adw.AlertDialog.new(_("Remove {}?").format(title), body_text)
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Remove"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", self.remove_on_response, dialog.choose_finish, index)
|
||||
dialog.present(self)
|
||||
|
||||
def enable_handler(self, button, index):
|
||||
name = self.host_remotes[index][0]
|
||||
typeArr = self.host_remotes[index][7]
|
||||
type = ""
|
||||
if "system" in typeArr:
|
||||
type = "system"
|
||||
else:
|
||||
type = "user"
|
||||
|
||||
try:
|
||||
command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"remote-modify",
|
||||
name,
|
||||
f"--{type}",
|
||||
"--enable",
|
||||
]
|
||||
subprocess.run(command, capture_output=False, check=True, env=self.new_env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not enable {}").format(name))
|
||||
)
|
||||
print(
|
||||
f"error in remotes_window.enable_handler: could not enable remote {name}:",
|
||||
e,
|
||||
)
|
||||
|
||||
self.generate_list()
|
||||
|
||||
def disable_handler(self, button, index, popoever):
|
||||
def disable_response(_a, response, _b):
|
||||
if response == "cancel":
|
||||
return
|
||||
try:
|
||||
command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"remote-modify",
|
||||
name,
|
||||
f"--{type}",
|
||||
"--disable",
|
||||
]
|
||||
subprocess.run(
|
||||
command, capture_output=False, check=True, env=self.new_env
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not disable {}").format(name))
|
||||
)
|
||||
print(
|
||||
f"error in remotes_window.enable_handler: could not disable remote {name}:",
|
||||
e,
|
||||
)
|
||||
|
||||
self.generate_list()
|
||||
|
||||
name = self.host_remotes[index][0]
|
||||
title = self.host_remotes[index][1]
|
||||
typeArr = self.host_remotes[index][7]
|
||||
type = ""
|
||||
if "system" in typeArr:
|
||||
type = "system"
|
||||
else:
|
||||
type = "user"
|
||||
|
||||
popoever.popdown()
|
||||
|
||||
body_text = _("Any installed apps from {} will stop receiving updates").format(
|
||||
name
|
||||
)
|
||||
dialog = Adw.AlertDialog.new(_("Disable {}?").format(title), body_text)
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Disable"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", disable_response, dialog.choose_finish)
|
||||
dialog.present(self)
|
||||
|
||||
def view_paks(self, type, remote):
|
||||
if "user" in type:
|
||||
type = "user"
|
||||
elif "system" in type:
|
||||
type = "system"
|
||||
else:
|
||||
self.make_toast(_("Could not view apps").format(to_copy))
|
||||
print(
|
||||
"error in remotes_window.view_apps(): remote installation type is not either system or user. type is:",
|
||||
type,
|
||||
)
|
||||
return
|
||||
settings = Gio.Settings.new("io.github.flattool.Warehouse.filter")
|
||||
for key in settings.list_keys():
|
||||
settings.reset(key)
|
||||
settings.set_string("remotes-list", f"{remote}<>{type};")
|
||||
settings.set_boolean("show-runtimes", True)
|
||||
self.main_window.apply_filter()
|
||||
self.close()
|
||||
|
||||
def generate_list(self):
|
||||
if self.show_disabled_button.get_active():
|
||||
self.show_disabled_button_button_content.set_icon_name(
|
||||
"eye-open-negative-filled-symbolic"
|
||||
)
|
||||
else:
|
||||
self.show_disabled_button_button_content.set_icon_name(
|
||||
"eye-not-looking-symbolic"
|
||||
)
|
||||
|
||||
self.host_remotes = self.my_utils.get_host_remotes()
|
||||
self.host_flatpaks = self.get_host_flatpaks()
|
||||
for i in range(len(self.rows_in_list)):
|
||||
self.remotes_list.remove(self.rows_in_list[i])
|
||||
|
||||
self.rows_in_list = []
|
||||
|
||||
def rowCopyHandler(widget, to_copy):
|
||||
self.main_window.clipboard.set(to_copy)
|
||||
self.make_toast(_("Copied {}").format(to_copy))
|
||||
|
||||
self.no_remotes.set_visible(True)
|
||||
|
||||
has_disabled = False
|
||||
for i in range(len(self.host_remotes)):
|
||||
try:
|
||||
name = self.host_remotes[i][0]
|
||||
title = self.host_remotes[i][1]
|
||||
install_type = self.host_remotes[i][7]
|
||||
remote_row = Adw.ActionRow(title=title)
|
||||
|
||||
more = Gtk.MenuButton(
|
||||
icon_name="view-more-symbolic",
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_("View More"),
|
||||
)
|
||||
more.add_css_class("flat")
|
||||
options = Gtk.Popover()
|
||||
options_box = Gtk.Box(
|
||||
halign=Gtk.Align.CENTER,
|
||||
valign=Gtk.Align.CENTER,
|
||||
orientation=Gtk.Orientation.VERTICAL,
|
||||
)
|
||||
|
||||
filter_button = Gtk.Button()
|
||||
filter_button.set_child(
|
||||
Adw.ButtonContent(
|
||||
icon_name="funnel-symbolic", label=_("Set Filter")
|
||||
)
|
||||
)
|
||||
filter_button.add_css_class("flat")
|
||||
filter_button.connect(
|
||||
"clicked",
|
||||
lambda *_, i=i: self.view_paks(
|
||||
self.host_remotes[i][7], self.host_remotes[i][0]
|
||||
),
|
||||
)
|
||||
|
||||
enable_button = Gtk.Button(visible=False)
|
||||
enable_button.set_child(
|
||||
Adw.ButtonContent(
|
||||
icon_name="eye-open-negative-filled-symbolic", label=_("Enable")
|
||||
)
|
||||
)
|
||||
enable_button.add_css_class("flat")
|
||||
enable_button.connect("clicked", self.enable_handler, i)
|
||||
|
||||
disable_button = Gtk.Button()
|
||||
disable_button.set_child(
|
||||
Adw.ButtonContent(
|
||||
icon_name="eye-not-looking-symbolic", label=_("Disable")
|
||||
)
|
||||
)
|
||||
disable_button.add_css_class("flat")
|
||||
disable_button.connect("clicked", self.disable_handler, i, options)
|
||||
|
||||
remove_button = Gtk.Button()
|
||||
remove_button.set_child(
|
||||
Adw.ButtonContent(
|
||||
icon_name="user-trash-symbolic", label=_("Remove")
|
||||
)
|
||||
)
|
||||
remove_button.add_css_class("flat")
|
||||
remove_button.connect("clicked", self.remove_handler, i, options)
|
||||
|
||||
options_box.append(filter_button)
|
||||
options_box.append(enable_button)
|
||||
options_box.append(disable_button)
|
||||
options_box.append(remove_button)
|
||||
options.set_child(options_box)
|
||||
more.set_popover(options)
|
||||
|
||||
copy_button = Gtk.Button(
|
||||
icon_name="edit-copy-symbolic",
|
||||
valign=Gtk.Align.CENTER,
|
||||
tooltip_text=_("Copy remote name"),
|
||||
)
|
||||
copy_button.add_css_class("flat")
|
||||
copy_button.connect("clicked", rowCopyHandler, name)
|
||||
|
||||
remote_row.add_suffix(copy_button)
|
||||
remote_row.add_suffix(more)
|
||||
|
||||
install_type = self.my_utils.get_install_type(install_type)
|
||||
if install_type == "disabled":
|
||||
has_disabled = True
|
||||
if not self.show_disabled_button.get_active():
|
||||
continue
|
||||
|
||||
remote_row.set_subtitle(_("Disabled"))
|
||||
enable_button.set_visible(True)
|
||||
disable_button.set_visible(False)
|
||||
remote_row.add_css_class("warning")
|
||||
elif install_type == "user":
|
||||
remote_row.set_subtitle(_("User wide"))
|
||||
elif install_type == "system":
|
||||
remote_row.set_subtitle(_("System wide"))
|
||||
else:
|
||||
remote_row.set_subtitle(_("Unknown install type"))
|
||||
|
||||
url = self.host_remotes[i][2]
|
||||
if title == "-":
|
||||
remote_row.set_title(name)
|
||||
self.remotes_list.add(remote_row)
|
||||
# subprocess.run(['wget', f'{self.host_remotes[i][11]}']) Idea to display remote icons... Need internet connection. Not sure if that is worth it
|
||||
self.rows_in_list.append(remote_row)
|
||||
self.no_remotes.set_visible(False)
|
||||
except Exception as e:
|
||||
print(
|
||||
"error in remotes_window.generate_list: could not add remote. error:",
|
||||
e,
|
||||
)
|
||||
self.show_disabled_button.set_visible(has_disabled)
|
||||
|
||||
# Popular remotes
|
||||
for i in range(len(self.rows_in_popular_list)):
|
||||
self.popular_remotes_list.remove(self.rows_in_popular_list[i])
|
||||
|
||||
self.rows_in_popular_list = []
|
||||
|
||||
remotes = [
|
||||
# [Name to show in GUI, Name of remote for system, Link to repo to add, Description of remote]
|
||||
[
|
||||
"AppCenter",
|
||||
"appcenter",
|
||||
"https://flatpak.elementary.io/repo.flatpakrepo",
|
||||
_("The open source, pay-what-you-want app store from elementary"),
|
||||
],
|
||||
[
|
||||
"Flathub",
|
||||
"flathub",
|
||||
"https://dl.flathub.org/repo/flathub.flatpakrepo",
|
||||
_("Central repository of Flatpak applications"),
|
||||
],
|
||||
[
|
||||
"Flathub beta",
|
||||
"flathub-beta",
|
||||
"https://flathub.org/beta-repo/flathub-beta.flatpakrepo",
|
||||
_("Beta builds of Flatpak applications"),
|
||||
],
|
||||
[
|
||||
"Fedora",
|
||||
"fedora",
|
||||
"oci+https://registry.fedoraproject.org",
|
||||
_("Flatpaks packaged by Fedora Linux"),
|
||||
],
|
||||
[
|
||||
"GNOME Nightly",
|
||||
"gnome-nightly",
|
||||
"https://nightly.gnome.org/gnome-nightly.flatpakrepo",
|
||||
_("The latest beta GNOME Apps and Runtimes"),
|
||||
],
|
||||
[
|
||||
"KDE Testing Applications",
|
||||
"kdeapps",
|
||||
"https://distribute.kde.org/kdeapps.flatpakrepo",
|
||||
_("Beta KDE Apps and Runtimes"),
|
||||
],
|
||||
[
|
||||
"WebKit Developer SDK",
|
||||
"webkit-sdk",
|
||||
"https://software.igalia.com/flatpak-refs/webkit-sdk.flatpakrepo",
|
||||
_("Central repository of the WebKit Developer and Runtime SDK"),
|
||||
],
|
||||
]
|
||||
|
||||
host_remotes = self.my_utils.get_host_remotes()
|
||||
host_remotes_names = []
|
||||
|
||||
total_added = 0
|
||||
|
||||
for i in range(len(self.host_remotes)):
|
||||
host_remotes_names.append(self.host_remotes[i][0])
|
||||
|
||||
for i in range(len(remotes)):
|
||||
if remotes[i][1] in host_remotes_names:
|
||||
continue
|
||||
|
||||
total_added += 1
|
||||
row = Adw.ActionRow(
|
||||
title=remotes[i][0], subtitle=(remotes[i][2]), activatable=True
|
||||
)
|
||||
row.connect("activated", self.add_handler, remotes[i][1], remotes[i][2])
|
||||
row.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic"))
|
||||
self.rows_in_popular_list.append(row)
|
||||
self.popular_remotes_list.add(row)
|
||||
|
||||
self.popular_remotes_list.set_visible(total_added > 0)
|
||||
|
||||
def addRemoteCallback(self, _a, _b):
|
||||
self.generate_list()
|
||||
self.stack.set_visible_child(self.main_group)
|
||||
|
||||
def addRemoteThread(self, command):
|
||||
try:
|
||||
subprocess.run(command, capture_output=True, check=True, env=self.new_env)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not add {}").format(self.name_to_add))
|
||||
)
|
||||
print(
|
||||
"error in remotes_window.addRemoteThread: could not add remote. error:",
|
||||
e,
|
||||
)
|
||||
|
||||
def on_add_response(self, _dialog, response_id, _function, row):
|
||||
if response_id == "cancel":
|
||||
self.should_pulse = False
|
||||
return
|
||||
|
||||
self.stack.set_visible_child(self.adding)
|
||||
|
||||
install_type = "--user"
|
||||
if not self.add_as_user:
|
||||
install_type = "--system"
|
||||
|
||||
self.name_to_add = self.name_to_add.strip()
|
||||
self.url_to_add = self.url_to_add.strip()
|
||||
|
||||
command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"remote-add",
|
||||
"--if-not-exists",
|
||||
self.name_to_add,
|
||||
self.url_to_add,
|
||||
install_type,
|
||||
]
|
||||
task = Gio.Task.new(None, None, self.addRemoteCallback)
|
||||
task.run_in_thread(
|
||||
lambda _task, _obj, _data, _cancellable: self.addRemoteThread(command)
|
||||
)
|
||||
|
||||
def add_handler(self, row, name="", link=""):
|
||||
dialog = Adw.AlertDialog.new(_("Add Flatpak Remote"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Add"))
|
||||
dialog.set_response_enabled("continue", False)
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED)
|
||||
|
||||
def name_update(widget):
|
||||
is_enabled = True
|
||||
self.name_to_add = widget.get_text()
|
||||
name_pattern = re.compile(r"^[a-zA-Z0-9\-._]+$")
|
||||
if not name_pattern.match(self.name_to_add):
|
||||
is_enabled = False
|
||||
|
||||
if is_enabled:
|
||||
widget.remove_css_class("error")
|
||||
else:
|
||||
widget.add_css_class("error")
|
||||
|
||||
if len(self.name_to_add) == 0:
|
||||
is_enabled = False
|
||||
|
||||
confirm_enabler(is_enabled)
|
||||
|
||||
def url_update(widget):
|
||||
is_enabled = True
|
||||
self.url_to_add = widget.get_text()
|
||||
url_pattern = re.compile(r"^[a-zA-Z0-9\-._~:/?#[\]@!$&\'()*+,;=]+$")
|
||||
if not url_pattern.match(self.url_to_add):
|
||||
is_enabled = False
|
||||
|
||||
if is_enabled:
|
||||
widget.remove_css_class("error")
|
||||
else:
|
||||
widget.add_css_class("error")
|
||||
|
||||
if len(self.url_to_add) == 0:
|
||||
is_enabled = False
|
||||
|
||||
confirm_enabler(is_enabled)
|
||||
|
||||
def confirm_enabler(is_enabled):
|
||||
if len(self.name_to_add) == 0 or len(self.url_to_add) == 0:
|
||||
is_enabled = False
|
||||
dialog.set_response_enabled("continue", is_enabled)
|
||||
|
||||
def set_user(widget):
|
||||
self.add_as_user = widget.get_active()
|
||||
|
||||
self.name_to_add = ""
|
||||
self.url_to_add = ""
|
||||
self.add_as_user = True
|
||||
|
||||
info_box = Gtk.Box(orientation="vertical")
|
||||
entry_list = Gtk.ListBox(selection_mode="none", margin_bottom=12)
|
||||
entry_list.add_css_class("boxed-list")
|
||||
|
||||
name_entry = Adw.EntryRow(title=_("Name"))
|
||||
name_entry.set_text(name)
|
||||
name_entry.connect("changed", name_update)
|
||||
|
||||
url_entry = Adw.EntryRow(title=_("URL"))
|
||||
url_entry.set_text(link)
|
||||
url_entry.connect("changed", url_update)
|
||||
|
||||
entry_list.append(name_entry)
|
||||
entry_list.append(url_entry)
|
||||
info_box.append(entry_list)
|
||||
|
||||
install_type_list = Gtk.ListBox(selection_mode="none")
|
||||
install_type_list.add_css_class("boxed-list")
|
||||
|
||||
user_row = Adw.ActionRow(
|
||||
title=_("User"), subtitle=_("Remote will be available to only you")
|
||||
)
|
||||
user_check = Gtk.CheckButton(active=True)
|
||||
user_check.connect("toggled", set_user)
|
||||
user_row.add_prefix(user_check)
|
||||
user_row.set_activatable_widget(user_check)
|
||||
|
||||
system_row = Adw.ActionRow(
|
||||
title=_("System"),
|
||||
subtitle=_("Remote will be available to every user on the system"),
|
||||
)
|
||||
system_check = Gtk.CheckButton()
|
||||
system_row.add_prefix(system_check)
|
||||
system_check.set_group(user_check)
|
||||
system_row.set_activatable_widget(system_check)
|
||||
|
||||
install_type_list.append(user_row)
|
||||
install_type_list.append(system_row)
|
||||
|
||||
info_box.append(install_type_list)
|
||||
|
||||
dialog.set_extra_child(info_box)
|
||||
dialog.connect("response", self.on_add_response, dialog.choose_finish, row)
|
||||
dialog.present(self)
|
||||
|
||||
if name != "":
|
||||
name_update(name_entry)
|
||||
if link != "":
|
||||
url_update(url_entry)
|
||||
|
||||
def add_remote_file_thread(self, filepath, system_or_user, name):
|
||||
try:
|
||||
subprocess.run(
|
||||
[
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"remote-add",
|
||||
"--if-not-exists",
|
||||
name,
|
||||
filepath,
|
||||
f"--{system_or_user}",
|
||||
],
|
||||
capture_output=True,
|
||||
check=True,
|
||||
env=self.new_env,
|
||||
)
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("{} successfully added").format(name))
|
||||
)
|
||||
except subprocess.CalledProcessError as e:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not add {}").format(self.name_to_add))
|
||||
)
|
||||
print(
|
||||
"error in remotes_window.addRemoteFromFileThread: could not add remote. error:",
|
||||
e,
|
||||
)
|
||||
|
||||
def add_remote_file(self, filepath):
|
||||
def response(dialog, response, _a):
|
||||
if response == "cancel":
|
||||
self.should_pulse = False
|
||||
return
|
||||
|
||||
user_or_system = "user"
|
||||
if system_check.get_active():
|
||||
user_or_system = "system"
|
||||
|
||||
task = Gio.Task.new(None, None, self.addRemoteCallback)
|
||||
task.run_in_thread(
|
||||
lambda *_: self.add_remote_file_thread(
|
||||
filepath, user_or_system, name_row.get_text()
|
||||
)
|
||||
)
|
||||
|
||||
def name_update(widget):
|
||||
is_enabled = True
|
||||
self.name_to_add = widget.get_text()
|
||||
name_pattern = re.compile(r"^[a-zA-Z\-]+$")
|
||||
if not name_pattern.match(self.name_to_add):
|
||||
is_enabled = False
|
||||
|
||||
if is_enabled:
|
||||
widget.remove_css_class("error")
|
||||
else:
|
||||
widget.add_css_class("error")
|
||||
|
||||
if len(self.name_to_add) == 0:
|
||||
is_enabled = False
|
||||
|
||||
dialog.set_response_enabled("continue", is_enabled)
|
||||
|
||||
self.should_pulse = True
|
||||
|
||||
name = filepath.split("/")
|
||||
name = name[len(name) - 1]
|
||||
|
||||
dialog = Adw.AlertDialog.new(self, _("Add {}?").format(name))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Add"))
|
||||
dialog.set_response_enabled("continue", False)
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.connect("response", response, dialog.choose_finish)
|
||||
|
||||
# Create Widgets
|
||||
options_box = Gtk.Box(orientation="vertical")
|
||||
options_list = Gtk.ListBox(selection_mode="none", margin_top=15)
|
||||
name_row = Adw.EntryRow(title=_("Name"))
|
||||
name_row.connect("changed", name_update)
|
||||
user_row = Adw.ActionRow(
|
||||
title=_("User"), subtitle=_("Remote will be available to only you")
|
||||
)
|
||||
system_row = Adw.ActionRow(
|
||||
title=_("System"),
|
||||
subtitle=_("Remote will be available to every user on the system"),
|
||||
)
|
||||
user_check = Gtk.CheckButton()
|
||||
system_check = Gtk.CheckButton()
|
||||
|
||||
# Apply Widgets
|
||||
user_row.add_prefix(user_check)
|
||||
user_row.set_activatable_widget(user_check)
|
||||
system_row.add_prefix(system_check)
|
||||
system_row.set_activatable_widget(system_check)
|
||||
user_check.set_group(system_check)
|
||||
options_list.append(name_row)
|
||||
options_list.append(user_row)
|
||||
options_list.append(system_row)
|
||||
options_box.append(options_list)
|
||||
dialog.set_extra_child(options_box)
|
||||
|
||||
# Calls
|
||||
user_check.set_active(True)
|
||||
options_list.add_css_class("boxed-list")
|
||||
dialog.present(self)
|
||||
|
||||
def file_callback(self, object, result):
|
||||
try:
|
||||
file = object.open_finish(result)
|
||||
self.add_remote_file(file.get_path())
|
||||
except GLib.GError:
|
||||
pass
|
||||
|
||||
def add_file_handler(self):
|
||||
filter = Gtk.FileFilter(name=_("Flatpak Repos"))
|
||||
filter.add_suffix("flatpakrepo")
|
||||
filters = Gio.ListStore.new(Gtk.FileFilter)
|
||||
filters.append(filter)
|
||||
file_chooser = Gtk.FileDialog()
|
||||
file_chooser.set_filters(filters)
|
||||
file_chooser.set_default_filter(filter)
|
||||
file_chooser.open(self.main_window, None, self.file_callback)
|
||||
|
||||
def __init__(self, main_window, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Create Variables
|
||||
self.my_utils = myUtils(self)
|
||||
self.host_remotes = []
|
||||
self.host_flatpaks = []
|
||||
self.main_window = main_window
|
||||
self.new_env = dict(os.environ)
|
||||
self.new_env["LC_ALL"] = "C"
|
||||
self.should_pulse = False
|
||||
|
||||
self.refresh.connect("clicked", lambda *_: self.generate_list())
|
||||
|
||||
self.add_from_file.add_suffix(
|
||||
Gtk.Image.new_from_icon_name("right-large-symbolic")
|
||||
)
|
||||
self.add_from_file.connect("activated", lambda *_: self.add_file_handler())
|
||||
self.custom_remote.add_suffix(
|
||||
Gtk.Image.new_from_icon_name("right-large-symbolic")
|
||||
)
|
||||
self.custom_remote.connect("activated", self.add_handler)
|
||||
self.show_disabled_button.connect("clicked", lambda *_: self.generate_list())
|
||||
|
||||
# Calls
|
||||
self.generate_list()
|
||||
|
||||
def set_is_open_false(*args):
|
||||
self.__class__.is_open = False
|
||||
self.connect("closed", set_is_open_false)
|
||||
if self.__class__.is_open:
|
||||
return
|
||||
else:
|
||||
self.present(main_window)
|
||||
self.__class__.is_open = True
|
||||
@@ -1,266 +0,0 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import pathlib
|
||||
|
||||
|
||||
class RemoteRow(Adw.ActionRow):
|
||||
def __init__(self, remote, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
my_utils = myUtils(self)
|
||||
self.install_type = my_utils.get_install_type(remote[7])
|
||||
if self.install_type == "disabled":
|
||||
self.set_visible(False)
|
||||
return
|
||||
self.set_activatable(True)
|
||||
self.remote = remote
|
||||
if remote[1] == "-":
|
||||
self.set_title(remote[0])
|
||||
else:
|
||||
self.set_title(remote[1])
|
||||
self.set_subtitle(_("{} wide").format(self.install_type))
|
||||
self.add_suffix(Gtk.Image.new_from_icon_name("right-large-symbolic"))
|
||||
|
||||
|
||||
class ResultRow(Adw.ActionRow):
|
||||
def __init__(self, flatpak, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
my_utils = myUtils(self)
|
||||
name = flatpak[0]
|
||||
description = flatpak[1]
|
||||
app_id = flatpak[2]
|
||||
version = flatpak[3]
|
||||
self.flatpak = flatpak
|
||||
self.set_title(GLib.markup_escape_text(f"{name}"))
|
||||
self.set_subtitle(GLib.markup_escape_text(f"{app_id}\n{description}"))
|
||||
self.check = Gtk.CheckButton()
|
||||
self.check.add_css_class("selection-mode")
|
||||
self.add_suffix(
|
||||
Gtk.Label(
|
||||
label=GLib.markup_escape_text(version),
|
||||
wrap=True,
|
||||
hexpand=True,
|
||||
justify=Gtk.Justification.RIGHT,
|
||||
)
|
||||
)
|
||||
self.add_suffix(self.check)
|
||||
self.set_activatable_widget(self.check)
|
||||
|
||||
|
||||
@Gtk.Template(
|
||||
resource_path="/io/github/flattool/Warehouse/../data/ui/search_install.ui"
|
||||
)
|
||||
class SearchInstallWindow(Adw.Dialog):
|
||||
__gtype_name__ = "SearchInstallWindow"
|
||||
|
||||
nav_view = Gtk.Template.Child()
|
||||
search_page = Gtk.Template.Child()
|
||||
results_page = Gtk.Template.Child()
|
||||
remotes_list = Gtk.Template.Child()
|
||||
search_entry = Gtk.Template.Child()
|
||||
blank_page = Gtk.Template.Child()
|
||||
inner_stack = Gtk.Template.Child()
|
||||
outer_stack = Gtk.Template.Child()
|
||||
loading_page = Gtk.Template.Child()
|
||||
results_scroll = Gtk.Template.Child()
|
||||
results_list = Gtk.Template.Child()
|
||||
too_many = Gtk.Template.Child()
|
||||
action_bar = Gtk.Template.Child()
|
||||
search_button = Gtk.Template.Child()
|
||||
no_results = Gtk.Template.Child()
|
||||
install_button = Gtk.Template.Child()
|
||||
installing = Gtk.Template.Child()
|
||||
installing_status = Gtk.Template.Child()
|
||||
search_box = Gtk.Template.Child()
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
progress_bar = Gtk.Template.Child()
|
||||
|
||||
is_open = False
|
||||
|
||||
def reset(self):
|
||||
self.results = []
|
||||
self.results_list.remove_all()
|
||||
self.inner_stack.set_visible_child(self.blank_page)
|
||||
|
||||
def check_handler(self, button, row):
|
||||
if button.get_active():
|
||||
self.selected.append(row.flatpak)
|
||||
else:
|
||||
self.selected.remove(row.flatpak)
|
||||
if len(self.selected) == 0:
|
||||
self.set_title(self.title)
|
||||
self.action_bar.set_revealed(False)
|
||||
else:
|
||||
self.set_title(_("{} Selected").format(len(self.selected)))
|
||||
self.action_bar.set_revealed(True)
|
||||
|
||||
def generate_remotes_list(self):
|
||||
total = 0
|
||||
for rem in self.host_remotes:
|
||||
if self.my_utils.get_install_type(rem[7]) != "disabled":
|
||||
total += 1
|
||||
if total < 2:
|
||||
self.nav_view.push(self.results_page)
|
||||
self.results_page.set_can_pop(False)
|
||||
|
||||
for rem in self.host_remotes:
|
||||
if self.my_utils.get_install_type(rem[7]) != "disabled":
|
||||
self.search_remote = rem[0]
|
||||
self.install_type = self.my_utils.get_install_type(rem[7])
|
||||
break
|
||||
|
||||
if self.host_remotes[0][1] == "-":
|
||||
self.title = _("Search {}").format(self.host_remotes[0][0])
|
||||
else:
|
||||
self.title = _("Search {}").format(self.host_remotes[0][1])
|
||||
|
||||
self.set_title(self.title)
|
||||
self.search_entry.set_placeholder_text(
|
||||
_("Search {}").format(self.search_remote)
|
||||
)
|
||||
self.search_entry.grab_focus()
|
||||
return
|
||||
|
||||
self.nav_view.connect("popped", lambda *_: self.set_title(""))
|
||||
for remote in self.host_remotes:
|
||||
row = RemoteRow(remote)
|
||||
row.connect("activated", self.remote_choice)
|
||||
self.remotes_list.append(row)
|
||||
|
||||
def generate_results_list(self):
|
||||
for pak in self.results:
|
||||
row = ResultRow(pak)
|
||||
row.check.set_active(row.flatpak in self.selected)
|
||||
row.check.connect("toggled", self.check_handler, row)
|
||||
row.set_tooltip_text(row.flatpak[2])
|
||||
if self.search_remote in row.flatpak[5].split(","):
|
||||
self.results_list.append(row)
|
||||
if self.results_list.get_row_at_index(0):
|
||||
self.inner_stack.set_visible_child(self.results_scroll)
|
||||
else:
|
||||
self.inner_stack.set_visible_child(self.no_results)
|
||||
|
||||
def remote_choice(self, row):
|
||||
self.reset()
|
||||
self.selected = []
|
||||
self.install_type = row.install_type
|
||||
self.search_remote = row.remote[0]
|
||||
self.search_entry.set_placeholder_text(
|
||||
_("Search {}").format(self.search_remote)
|
||||
)
|
||||
self.title = _("Search {}").format(row.get_title())
|
||||
self.set_title(self.title)
|
||||
self.nav_view.push(self.results_page)
|
||||
self.search_entry.grab_focus()
|
||||
self.action_bar.set_revealed(len(self.selected) > 0)
|
||||
|
||||
def search_handler(self, *args):
|
||||
self.cancel_search.cancel()
|
||||
self.reset()
|
||||
self.inner_stack.set_visible_child(self.loading_page)
|
||||
query = self.search_entry.get_text().strip()
|
||||
if query == "":
|
||||
self.inner_stack.set_visible_child(self.blank_page)
|
||||
return
|
||||
|
||||
def search_thread(*args):
|
||||
command = [
|
||||
"flatpak-spawn",
|
||||
"--host",
|
||||
"flatpak",
|
||||
"search",
|
||||
"--columns=all",
|
||||
query,
|
||||
]
|
||||
output = (
|
||||
subprocess.run(
|
||||
command, capture_output=True, text=True, env=self.new_env
|
||||
)
|
||||
.stdout.strip()
|
||||
.split("\n")
|
||||
)
|
||||
for elm in output:
|
||||
self.results.append(elm.split("\t"))
|
||||
|
||||
def done(*args):
|
||||
if len(self.results) > 50:
|
||||
self.inner_stack.set_visible_child(self.too_many)
|
||||
return
|
||||
if ["No matches found"] in self.results:
|
||||
self.inner_stack.set_visible_child(self.no_results)
|
||||
return
|
||||
self.generate_results_list()
|
||||
|
||||
task = Gio.Task.new(None, self.cancel_search, done)
|
||||
task.run_in_thread(search_thread)
|
||||
|
||||
def install_handler(self, *args):
|
||||
paks = []
|
||||
for pak in self.selected:
|
||||
paks.append(pak[2])
|
||||
self.outer_stack.set_visible_child(self.installing)
|
||||
self.set_title(_("Install From The Web"))
|
||||
|
||||
def thread(*args):
|
||||
self.my_utils.install_flatpak(
|
||||
paks,
|
||||
self.search_remote,
|
||||
self.install_type,
|
||||
self.progress_bar,
|
||||
self.installing_status,
|
||||
)
|
||||
|
||||
def done(*args):
|
||||
self.main_window.refresh_list_of_flatpaks(None)
|
||||
# Make window able to close
|
||||
self.set_can_close(True)
|
||||
if self.my_utils.install_success:
|
||||
self.close()
|
||||
self.main_window.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Installed successfully"))
|
||||
)
|
||||
else:
|
||||
self.progress_bar.set_visible(False)
|
||||
self.nav_view.pop()
|
||||
self.outer_stack.set_visible_child(self.nav_view)
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Some apps didn't install"))
|
||||
)
|
||||
|
||||
# Make window unable to close
|
||||
self.set_can_close(False)
|
||||
task = Gio.Task.new(None, None, done)
|
||||
task.run_in_thread(thread)
|
||||
|
||||
def __init__(self, main_window, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Create Variables
|
||||
self.my_utils = myUtils(self)
|
||||
self.new_env = dict(os.environ)
|
||||
self.new_env["LC_ALL"] = "C"
|
||||
self.host_remotes = self.my_utils.get_host_remotes()
|
||||
self.main_window = main_window
|
||||
self.results = []
|
||||
self.selected = []
|
||||
self.search_remote = ""
|
||||
self.install_type = ""
|
||||
self.title = _("Install From The Web")
|
||||
|
||||
self.cancel_search = Gio.Cancellable()
|
||||
self.search_entry.connect("activate", self.search_handler)
|
||||
self.search_button.connect("clicked", self.search_handler)
|
||||
self.install_button.connect("clicked", self.install_handler)
|
||||
|
||||
# Apply Widgets
|
||||
self.generate_remotes_list()
|
||||
|
||||
def set_is_open_false(*args):
|
||||
self.__class__.is_open = False
|
||||
self.connect("closed", set_is_open_false)
|
||||
if self.__class__.is_open:
|
||||
return
|
||||
else:
|
||||
self.present(main_window)
|
||||
self.__class__.is_open = True
|
||||
@@ -1,281 +0,0 @@
|
||||
from gi.repository import Gtk, Adw, GLib, Gdk, Gio
|
||||
from .common import myUtils
|
||||
import subprocess
|
||||
import os
|
||||
import pathlib
|
||||
import time
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/snapshots.ui")
|
||||
class SnapshotsWindow(Adw.Dialog):
|
||||
__gtype_name__ = "SnapshotsWindow"
|
||||
|
||||
new_env = dict(os.environ)
|
||||
new_env["LC_ALL"] = "C"
|
||||
host_home = str(pathlib.Path.home())
|
||||
user_data_path = host_home + "/.var/app/"
|
||||
snapshots_path = (
|
||||
host_home + "/.var/app/io.github.flattool.Warehouse/data/Snapshots/"
|
||||
)
|
||||
|
||||
snapshots_group = Gtk.Template.Child()
|
||||
main_stack = Gtk.Template.Child()
|
||||
no_snapshots = Gtk.Template.Child()
|
||||
new_snapshot = Gtk.Template.Child()
|
||||
open_folder_button = Gtk.Template.Child()
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
outerbox = Gtk.Template.Child()
|
||||
loading = Gtk.Template.Child()
|
||||
loading_label = Gtk.Template.Child()
|
||||
action_bar = Gtk.Template.Child()
|
||||
|
||||
def show_list_or_empty(self):
|
||||
# Make window able to close
|
||||
self.set_can_close(True)
|
||||
|
||||
self.action_bar.set_revealed(True)
|
||||
if os.path.exists(self.snapshots_of_app_path):
|
||||
if len(os.listdir(self.snapshots_of_app_path)) > 0:
|
||||
self.main_stack.set_visible_child(self.outerbox)
|
||||
return "list"
|
||||
self.open_folder_button.set_sensitive(False)
|
||||
self.main_stack.set_visible_child(self.no_snapshots)
|
||||
return "empty"
|
||||
|
||||
def generate_list(self):
|
||||
if not os.path.exists(self.app_user_data):
|
||||
self.new_snapshot.set_sensitive(False)
|
||||
self.new_snapshot.set_tooltip_text(_("There is no User Data to Snapshot"))
|
||||
|
||||
if self.show_list_or_empty() == "empty":
|
||||
return
|
||||
|
||||
snapshot_files = os.listdir(self.snapshots_of_app_path)
|
||||
to_trash = []
|
||||
|
||||
for i in range(len(snapshot_files)):
|
||||
if not snapshot_files[i].endswith(".tar.zst"):
|
||||
# Find all files that aren't snapshots
|
||||
to_trash.append(snapshot_files[i])
|
||||
|
||||
for i in range(len(to_trash)):
|
||||
# Trash all files that aren't snapshots
|
||||
a = self.my_utils.trash_folder(f"{self.snapshots_of_app_path}{to_trash[i]}")
|
||||
if a == 0:
|
||||
snapshot_files.remove(to_trash[i])
|
||||
|
||||
if len(snapshot_files) == 0:
|
||||
self.main_stack.set_visible_child(self.no_snapshots)
|
||||
return
|
||||
|
||||
for i in range(len(snapshot_files)):
|
||||
self.create_row(snapshot_files[i])
|
||||
|
||||
def create_row(self, file):
|
||||
def size_thread(*args):
|
||||
size = self.my_utils.get_size_with_format(self.snapshots_of_app_path + file)
|
||||
GLib.idle_add(lambda *_a: row.set_subtitle(f"~{size}"))
|
||||
|
||||
split_file = file.removesuffix(".tar.zst").split("_")
|
||||
time = GLib.DateTime.new_from_unix_local(int(split_file[0])).format("%x %X")
|
||||
row = Adw.ActionRow(title=time)
|
||||
|
||||
task = Gio.Task()
|
||||
task.run_in_thread(size_thread)
|
||||
|
||||
label = Gtk.Label(
|
||||
label=_("Version {}").format(split_file[1]),
|
||||
hexpand=True,
|
||||
wrap=True,
|
||||
justify=Gtk.Justification.RIGHT,
|
||||
)
|
||||
row.add_suffix(label)
|
||||
|
||||
apply = Gtk.Button(icon_name="check-plain-symbolic", valign=Gtk.Align.CENTER)
|
||||
apply.set_tooltip_text(_("Apply Snapshot"))
|
||||
apply.connect("clicked", self.apply_snapshot, file, row)
|
||||
apply.add_css_class("flat")
|
||||
row.add_suffix(apply)
|
||||
|
||||
trash = Gtk.Button(icon_name="user-trash-symbolic", valign=Gtk.Align.CENTER)
|
||||
trash.set_tooltip_text(_("Trash Snapshot"))
|
||||
trash.connect("clicked", self.trash_snapshot, file, row)
|
||||
trash.add_css_class("flat")
|
||||
row.add_suffix(trash)
|
||||
self.snapshots_group.insert(row, 0)
|
||||
self.main_stack.set_visible_child(self.outerbox)
|
||||
self.open_folder_button.set_sensitive(True)
|
||||
|
||||
def trash_snapshot(self, button, file, row):
|
||||
def on_response(dialog, response, func):
|
||||
if response == "cancel":
|
||||
return
|
||||
a = self.my_utils.trash_folder(self.snapshots_of_app_path + file)
|
||||
if a == 0:
|
||||
self.snapshots_group.remove(row)
|
||||
if not self.snapshots_group.get_row_at_index(0):
|
||||
self.my_utils.trash_folder(self.snapshots_of_app_path)
|
||||
self.show_list_or_empty()
|
||||
else:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not trash snapshot"))
|
||||
)
|
||||
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Trash Snapshot?"),
|
||||
_("This snapshot and its contents will be sent to the trash."),
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("continue", _("Trash Snapshot"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", on_response, dialog.choose_finish)
|
||||
dialog.present(self)
|
||||
|
||||
def create_snapshot(self):
|
||||
epoch = int(time.time())
|
||||
|
||||
def thread():
|
||||
response = self.my_utils.snapshot_apps(
|
||||
epoch,
|
||||
[self.snapshots_of_app_path],
|
||||
[self.app_version],
|
||||
[self.app_user_data],
|
||||
)
|
||||
if response != 0:
|
||||
GLib.idle_add(
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not create snapshot"))
|
||||
)
|
||||
)
|
||||
return
|
||||
if self.show_list_or_empty() == "list":
|
||||
self.create_row(f"{epoch}_{self.app_version}.tar.zst")
|
||||
|
||||
# Make window unable to close
|
||||
self.set_can_close(False)
|
||||
self.loading_label.set_label(_("Creating Snapshot…"))
|
||||
self.action_bar.set_revealed(False)
|
||||
self.main_stack.set_visible_child(self.loading)
|
||||
|
||||
task = Gio.Task()
|
||||
task.run_in_thread(lambda *_: thread())
|
||||
|
||||
def apply_snapshot(self, button, file, row):
|
||||
self.applied = False
|
||||
|
||||
def thread():
|
||||
try:
|
||||
subprocess.run(
|
||||
[
|
||||
"tar",
|
||||
"--zstd",
|
||||
"-xvf",
|
||||
f"{self.snapshots_of_app_path}{file}",
|
||||
"-C",
|
||||
f"{self.app_user_data}",
|
||||
],
|
||||
check=True,
|
||||
env=self.new_env,
|
||||
)
|
||||
self.applied = True
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(
|
||||
"error in snapshots_window.apply_snapshot.thread: CalledProcessError:",
|
||||
e,
|
||||
)
|
||||
|
||||
def callback():
|
||||
if not self.applied:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not apply snapshot"))
|
||||
)
|
||||
else:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Snapshot applied")))
|
||||
|
||||
self.new_snapshot.set_tooltip_text("")
|
||||
self.show_list_or_empty()
|
||||
|
||||
def on_response(dialog, response, func):
|
||||
if response == "cancel":
|
||||
return
|
||||
to_apply = self.snapshots_of_app_path + file
|
||||
to_trash = self.app_user_data
|
||||
if os.path.exists(to_trash):
|
||||
a = self.my_utils.trash_folder(to_trash)
|
||||
if a != 0:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not apply snapshot"))
|
||||
)
|
||||
return
|
||||
data = Gio.File.new_for_path(self.app_user_data)
|
||||
data.make_directory()
|
||||
if not os.path.exists(data.get_path()):
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not apply snapshot"))
|
||||
)
|
||||
return
|
||||
|
||||
# Make window unable to close
|
||||
self.set_can_close(False)
|
||||
self.loading_label.set_label(_("Applying Snapshot…"))
|
||||
self.action_bar.set_revealed(False)
|
||||
self.main_stack.set_visible_child(self.loading)
|
||||
|
||||
task = Gio.Task.new(None, None, lambda *_: callback())
|
||||
task.run_in_thread(lambda *_: thread())
|
||||
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Apply Snapshot?"),
|
||||
_("Applying this snapshot will trash any current user data for {}.").format(
|
||||
self.app_name
|
||||
),
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("continue", _("Apply Snapshot"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", on_response, dialog.choose_finish)
|
||||
dialog.present(self)
|
||||
|
||||
def open_button_handler(self, widget, path):
|
||||
try:
|
||||
Gio.AppInfo.launch_default_for_uri(f"file://{path}", None)
|
||||
except GLib.GError:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder")))
|
||||
|
||||
def __init__(self, parent_window, flatpak_row, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
|
||||
# Variables
|
||||
self.my_utils = myUtils(self)
|
||||
self.app_name = flatpak_row[0]
|
||||
self.app_id = flatpak_row[2]
|
||||
self.app_version = flatpak_row[3]
|
||||
self.app_ref = flatpak_row[8]
|
||||
self.snapshots_of_app_path = self.snapshots_path + self.app_id + "/"
|
||||
self.app_user_data = self.user_data_path + self.app_id + "/"
|
||||
self.parent_window = parent_window
|
||||
|
||||
if (
|
||||
self.app_version == ""
|
||||
or self.app_version == "-"
|
||||
or self.app_version == None
|
||||
):
|
||||
self.app_version = 0.0
|
||||
|
||||
if not os.path.exists(self.snapshots_path):
|
||||
# Create snapshots folder if none exists
|
||||
file = Gio.File.new_for_path(self.snapshots_path)
|
||||
file.make_directory()
|
||||
|
||||
# Calls
|
||||
self.generate_list()
|
||||
self.open_folder_button.connect(
|
||||
"clicked", self.open_button_handler, self.snapshots_of_app_path
|
||||
)
|
||||
self.new_snapshot.connect("clicked", lambda *_: self.create_snapshot())
|
||||
|
||||
# Window stuffs
|
||||
self.set_title(_("{} Snapshots").format(self.app_name))
|
||||
self.present(parent_window)
|
||||
@@ -1,15 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gresources>
|
||||
<gresource prefix="/io/github/flattool/Warehouse">
|
||||
<file preprocess="xml-stripblanks">../data/ui/window.ui</file>
|
||||
<file preprocess="xml-stripblanks">../data/ui/orphans.ui</file>
|
||||
<file preprocess="xml-stripblanks">../data/ui/filter.ui</file>
|
||||
<file preprocess="xml-stripblanks">../data/ui/remotes.ui</file>
|
||||
<file preprocess="xml-stripblanks">../data/ui/downgrade.ui</file>
|
||||
<file preprocess="xml-stripblanks">../data/ui/search_install.ui</file>
|
||||
<file preprocess="xml-stripblanks">../data/ui/snapshots.ui</file>
|
||||
<file preprocess="xml-stripblanks">../data/ui/properties.ui</file>
|
||||
<file>../data/style.css</file>
|
||||
<file preprocess="xml-stripblanks">main_window/window.ui</file>
|
||||
<file preprocess="xml-stripblanks">gtk/help-overlay.ui</file>
|
||||
<!-- <file preprocess="xml-stripblanks">../data/io.github.flattool.Warehouse.metainfo.xml.in</file> -->
|
||||
</gresource>
|
||||
|
||||
@@ -42,5 +42,5 @@ if __name__ == '__main__':
|
||||
resource = Gio.Resource.load(os.path.join(pkgdatadir, 'warehouse.gresource'))
|
||||
resource._register()
|
||||
|
||||
from flattool_gui import main
|
||||
from Warehouse import main
|
||||
sys.exit(main.main(VERSION))
|
||||
|
||||
970
src/window.py
970
src/window.py
@@ -1,970 +0,0 @@
|
||||
# window.py
|
||||
#
|
||||
# Copyright 2023 Heliguy
|
||||
#
|
||||
# This program is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, version 3 of the License only.
|
||||
#
|
||||
# This program is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
#
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
#
|
||||
# SPDX-License-Identifier: GPL-3.0-only
|
||||
|
||||
import os
|
||||
import pathlib
|
||||
import subprocess
|
||||
import re
|
||||
import time
|
||||
|
||||
from gi.repository import Adw, Gdk, Gio, GLib, Gtk
|
||||
# from .properties_window import PropertiesWindow
|
||||
from .filter_window import FilterWindow
|
||||
from .common import myUtils
|
||||
from .remotes_window import RemotesWindow
|
||||
from .downgrade_window import DowngradeWindow
|
||||
from .snapshots_window import SnapshotsWindow
|
||||
from .const import Config
|
||||
|
||||
from .app_row_widget import AppRow
|
||||
|
||||
|
||||
@Gtk.Template(resource_path="/io/github/flattool/Warehouse/../data/ui/window.ui")
|
||||
class WarehouseWindow(Adw.ApplicationWindow):
|
||||
__gtype_name__ = "WarehouseWindow"
|
||||
main_window_title = "Warehouse"
|
||||
flatpaks_list_box = Gtk.Template.Child()
|
||||
search_entry = Gtk.Template.Child()
|
||||
search_button = Gtk.Template.Child()
|
||||
search_bar = Gtk.Template.Child()
|
||||
toast_overlay = Gtk.Template.Child()
|
||||
no_flatpaks = Gtk.Template.Child()
|
||||
no_results = Gtk.Template.Child()
|
||||
main_stack = Gtk.Template.Child()
|
||||
batch_mode_button = Gtk.Template.Child()
|
||||
batch_mode_bar = Gtk.Template.Child()
|
||||
batch_select_all_button = Gtk.Template.Child()
|
||||
batch_uninstall_button = Gtk.Template.Child()
|
||||
batch_clean_button = Gtk.Template.Child()
|
||||
batch_copy_button = Gtk.Template.Child()
|
||||
batch_snapshot_button = Gtk.Template.Child()
|
||||
main_box = Gtk.Template.Child()
|
||||
main_overlay = Gtk.Template.Child()
|
||||
main_toolbar_view = Gtk.Template.Child()
|
||||
filter_button = Gtk.Template.Child()
|
||||
scrolled_window = Gtk.Template.Child()
|
||||
main_menu = Gtk.Template.Child()
|
||||
installing = Gtk.Template.Child()
|
||||
uninstalling = Gtk.Template.Child()
|
||||
snapshotting = Gtk.Template.Child()
|
||||
loading_flatpaks = Gtk.Template.Child()
|
||||
no_matches = Gtk.Template.Child()
|
||||
reset_filters_button = Gtk.Template.Child()
|
||||
uninstalling_status = Gtk.Template.Child()
|
||||
refreshing = Gtk.Template.Child()
|
||||
|
||||
main_progress_bar = Gtk.ProgressBar(visible=False, can_target=False)
|
||||
main_progress_bar.add_css_class("osd")
|
||||
clipboard = Gdk.Display.get_default().get_clipboard()
|
||||
host_home = str(pathlib.Path.home())
|
||||
user_data_path = host_home + "/.var/app/"
|
||||
in_batch_mode = False
|
||||
should_select_all = False
|
||||
host_flatpaks = None
|
||||
install_success = True
|
||||
no_close = None
|
||||
re_get_flatpaks = False
|
||||
currently_uninstalling = False
|
||||
is_result = False
|
||||
is_empty = False
|
||||
total_selected = 0
|
||||
|
||||
def filter_func(self, row):
|
||||
if (self.search_entry.get_text().lower() in row.get_title().lower()) or (
|
||||
self.search_entry.get_text().lower() in row.get_subtitle().lower()
|
||||
):
|
||||
self.is_result = True
|
||||
return True
|
||||
|
||||
def uninstall_buttons_enable(self, should_enable):
|
||||
if self.currently_uninstalling:
|
||||
return
|
||||
if not should_enable:
|
||||
self.batch_uninstall_button.set_sensitive(False)
|
||||
|
||||
def uninstall_flatpak_callback(self, _a, _b):
|
||||
self.currently_uninstalling = False
|
||||
self.refresh_list_of_flatpaks(_a)
|
||||
self.main_toolbar_view.set_sensitive(True)
|
||||
self.disconnect(self.no_close)
|
||||
self.uninstall_buttons_enable(True)
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.search_button.set_sensitive(True)
|
||||
self.batch_actions_enable(False)
|
||||
if self.my_utils.uninstall_success:
|
||||
self.refresh_list_of_flatpaks(self)
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Uninstalled successfully")))
|
||||
else:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not uninstall some apps"))
|
||||
)
|
||||
|
||||
def uninstall_flatpak_thread(self, ref_arr, id_arr, type_arr, should_trash):
|
||||
self.my_utils.uninstall_flatpak(
|
||||
ref_arr,
|
||||
type_arr,
|
||||
should_trash,
|
||||
self.main_progress_bar,
|
||||
self.uninstalling_status,
|
||||
)
|
||||
|
||||
def uninstall_flatpak(self, should_trash):
|
||||
ref_arr = []
|
||||
id_arr = []
|
||||
type_arr = []
|
||||
self.currently_uninstalling = True
|
||||
i = 0
|
||||
while self.flatpaks_list_box.get_row_at_index(i) != None:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
if current.tickbox.get_active() == True:
|
||||
ref_arr.append(current.app_ref)
|
||||
id_arr.append(current.app_id)
|
||||
type_arr.append(current.install_type)
|
||||
i += 1
|
||||
self.set_title(self.main_window_title)
|
||||
task = Gio.Task.new(None, None, self.uninstall_flatpak_callback)
|
||||
task.run_in_thread(
|
||||
lambda _task, _obj, _data, _cancellable, ref_arr=ref_arr, id_arr=id_arr, type_arr=type_arr, should_trash=should_trash: self.uninstall_flatpak_thread(
|
||||
ref_arr, id_arr, type_arr, should_trash
|
||||
)
|
||||
)
|
||||
|
||||
def batch_uninstall_button_handler(self, _widget):
|
||||
has_user_data = False
|
||||
|
||||
def batch_uninstall_response(_idk, response_id, _widget):
|
||||
if response_id == "cancel":
|
||||
return 1
|
||||
|
||||
try:
|
||||
should_trash = trash_check.get_active()
|
||||
except:
|
||||
should_trash = False
|
||||
|
||||
self.uninstall_buttons_enable(False)
|
||||
|
||||
self.no_close = self.connect(
|
||||
"close-request", lambda event: True
|
||||
) # Make window unable to close
|
||||
self.main_stack.set_visible_child(self.uninstalling)
|
||||
self.search_button.set_sensitive(False)
|
||||
self.uninstall_flatpak(should_trash)
|
||||
|
||||
# Create Widgets
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Uninstall Selected Apps?"),
|
||||
_("It will not be possible to use these apps after removal."),
|
||||
)
|
||||
|
||||
# Check to see if at least one app in the list has user data
|
||||
i = 0
|
||||
while True:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
i += 1
|
||||
if current == None:
|
||||
break
|
||||
if current.tickbox.get_active() and os.path.exists(
|
||||
f"{self.user_data_path}{current.app_id}"
|
||||
):
|
||||
has_user_data = True
|
||||
break
|
||||
|
||||
if has_user_data:
|
||||
# Create Widgets
|
||||
options_box = Gtk.Box(orientation="vertical")
|
||||
header = Gtk.Label(
|
||||
label=_("App Settings & Data"), halign="start", margin_top=10
|
||||
)
|
||||
options_list = Gtk.ListBox(selection_mode="none", margin_top=15)
|
||||
keep_data = Adw.ActionRow(
|
||||
title=_("Keep"),
|
||||
subtitle=_("Allow restoring these apps' settings and content"),
|
||||
)
|
||||
trash_data = Adw.ActionRow(
|
||||
title=_("Trash"),
|
||||
subtitle=_("Send these apps' settings and content to the trash"),
|
||||
)
|
||||
keep_check = Gtk.CheckButton()
|
||||
trash_check = Gtk.CheckButton()
|
||||
|
||||
# Apply Widgets
|
||||
keep_data.add_prefix(keep_check)
|
||||
keep_data.set_activatable_widget(keep_check)
|
||||
trash_data.add_prefix(trash_check)
|
||||
trash_data.set_activatable_widget(trash_check)
|
||||
keep_check.set_group(trash_check)
|
||||
options_list.append(keep_data)
|
||||
options_list.append(trash_data)
|
||||
options_box.append(header)
|
||||
options_box.append(options_list)
|
||||
dialog.set_extra_child(options_box)
|
||||
|
||||
# Calls
|
||||
keep_check.set_active(True)
|
||||
options_list.add_css_class("boxed-list")
|
||||
header.add_css_class("heading")
|
||||
header.add_css_class("h4")
|
||||
|
||||
# Connections
|
||||
dialog.connect("response", batch_uninstall_response, dialog.choose_finish)
|
||||
|
||||
# Calls
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Uninstall"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.present(self)
|
||||
|
||||
def uninstall_button_handler(self, row, name, ref, id):
|
||||
if self.currently_uninstalling:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Cannot uninstall while already uninstalling"))
|
||||
)
|
||||
return
|
||||
|
||||
def uninstall_response(_idk, response_id, _widget):
|
||||
if response_id == "cancel":
|
||||
return 1
|
||||
|
||||
try:
|
||||
should_trash = trash_check.get_active()
|
||||
except:
|
||||
should_trash = False
|
||||
|
||||
if response_id == "purge":
|
||||
should_trash = True
|
||||
|
||||
self.uninstall_buttons_enable(False)
|
||||
|
||||
self.no_close = self.connect(
|
||||
"close-request", lambda event: True
|
||||
) # Make window unable to close
|
||||
self.main_stack.set_visible_child(self.uninstalling)
|
||||
self.search_button.set_sensitive(False)
|
||||
self.uninstall_flatpak(should_trash)
|
||||
|
||||
row.tickbox.set_active(True)
|
||||
|
||||
# Create Widgets
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Uninstall {}?").format(name),
|
||||
_("It will not be possible to use {} after removal.").format(name),
|
||||
)
|
||||
|
||||
if os.path.exists(f"{self.user_data_path}{id}"):
|
||||
# Create Widgets for Trash
|
||||
options_box = Gtk.Box(orientation="vertical")
|
||||
header = Gtk.Label(
|
||||
label=_("App Settings & Data"), halign="start", margin_top=10
|
||||
)
|
||||
options_list = Gtk.ListBox(selection_mode="none", margin_top=15)
|
||||
keep_data = Adw.ActionRow(
|
||||
title=_("Keep"),
|
||||
subtitle=_("Allow restoring this app's settings and content"),
|
||||
)
|
||||
trash_data = Adw.ActionRow(
|
||||
title=_("Trash"),
|
||||
subtitle=_("Send this app's settings and content to the trash"),
|
||||
)
|
||||
keep_check = Gtk.CheckButton(active=True)
|
||||
trash_check = Gtk.CheckButton()
|
||||
|
||||
# Apply Widgets for Trash
|
||||
keep_data.add_prefix(keep_check)
|
||||
keep_data.set_activatable_widget(keep_check)
|
||||
trash_data.add_prefix(trash_check)
|
||||
trash_data.set_activatable_widget(trash_check)
|
||||
keep_check.set_group(trash_check)
|
||||
dialog.set_extra_child(options_box)
|
||||
options_list.append(keep_data)
|
||||
options_list.append(trash_data)
|
||||
options_box.append(header)
|
||||
options_box.append(options_list)
|
||||
options_list.add_css_class("boxed-list")
|
||||
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Uninstall"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", uninstall_response, dialog.choose_finish)
|
||||
dialog.present(self)
|
||||
|
||||
def window_set_empty(self, is_empty):
|
||||
self.batch_mode_button.set_sensitive(not is_empty)
|
||||
self.search_button.set_sensitive(not is_empty)
|
||||
self.filter_button.set_sensitive(not is_empty)
|
||||
self.is_empty = is_empty
|
||||
|
||||
if is_empty:
|
||||
self.batch_mode_button.set_active(False)
|
||||
self.main_stack.set_visible_child(self.no_flatpaks)
|
||||
self.search_button.set_sensitive(False)
|
||||
else:
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.search_button.set_sensitive(True)
|
||||
|
||||
def create_row(self, index):
|
||||
row = AppRow(self, self.host_flatpaks, index)
|
||||
if row.app_id == "io.github.flattool.Warehouse":
|
||||
row.tickbox.set_sensitive(False)
|
||||
self.flatpaks_list_box.insert(row, index)
|
||||
|
||||
def generate_list_of_flatpaks(self):
|
||||
self.host_flatpaks = self.my_utils.get_host_flatpaks()
|
||||
self.dependent_runtimes = self.my_utils.get_dependent_runtimes()
|
||||
self.set_title(self.main_window_title)
|
||||
self.eol_list = []
|
||||
self.system_mask_list = self.my_utils.get_host_masks("system")
|
||||
self.user_mask_list = self.my_utils.get_host_masks("user")
|
||||
|
||||
for index in range(len(self.host_flatpaks)):
|
||||
try:
|
||||
if "eol" in self.host_flatpaks[index][12]:
|
||||
self.eol_list.append(self.host_flatpaks[index][8])
|
||||
except:
|
||||
print("Could not find EOL")
|
||||
|
||||
for index in range(len(self.host_flatpaks)):
|
||||
self.create_row(index)
|
||||
|
||||
# self.windowSetEmpty(not self.flatpaks_list_box.get_row_at_index(0))
|
||||
self.batch_actions_enable(False)
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.apply_filter()
|
||||
|
||||
# Stop list window from opening with the list at the bottom by focusing the first visible row item
|
||||
for index in range(len(self.host_flatpaks)):
|
||||
if self.flatpaks_list_box.get_row_at_index(index).is_visible():
|
||||
self.flatpaks_list_box.get_row_at_index(index).grab_focus()
|
||||
break
|
||||
|
||||
def refresh_list_of_flatpaks(self, widget):
|
||||
if self.currently_uninstalling:
|
||||
return
|
||||
|
||||
# I hate this so much...
|
||||
def callback(*args):
|
||||
self.flatpaks_list_box.remove_all()
|
||||
self.generate_list_of_flatpaks()
|
||||
self.batch_mode_button.set_active(False)
|
||||
self.total_selected = 0
|
||||
|
||||
def runner(*args):
|
||||
import time
|
||||
time.sleep(0.2)
|
||||
|
||||
self.main_stack.set_visible_child(self.refreshing)
|
||||
task = Gio.Task.new(None, None, callback)
|
||||
task.run_in_thread(runner)
|
||||
|
||||
def reset_filters(self):
|
||||
settings = Gio.Settings.new("io.github.flattool.Warehouse.filter")
|
||||
for key in settings.list_keys():
|
||||
settings.reset(key)
|
||||
self.apply_filter()
|
||||
|
||||
def apply_filter(self):
|
||||
self.batch_mode_button.set_active(False)
|
||||
settings = Gio.Settings.new("io.github.flattool.Warehouse.filter")
|
||||
show_apps = settings.get_boolean("show-apps")
|
||||
show_runtimes = settings.get_boolean("show-runtimes")
|
||||
remotes_list = settings.get_string("remotes-list").split(",")
|
||||
runtimes_list = settings.get_string("runtimes-list").split(",")
|
||||
total_visible = 0
|
||||
i = 0
|
||||
while self.flatpaks_list_box.get_row_at_index(i) != None:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
id = current.app_id
|
||||
dependant = current.dependent_runtime
|
||||
remote = f"{current.origin_remote}<>{current.install_type};"
|
||||
is_runtime = current.is_runtime
|
||||
|
||||
visible = True
|
||||
if (not show_apps) and (not is_runtime):
|
||||
visible = False
|
||||
|
||||
if (not show_runtimes) and is_runtime:
|
||||
visible = False
|
||||
|
||||
if (not "all" in remotes_list) and (not remote in remotes_list):
|
||||
visible = False
|
||||
|
||||
if (not "all" in runtimes_list) and (not dependant in runtimes_list):
|
||||
visible = False
|
||||
|
||||
current.set_is_visible(visible)
|
||||
total_visible += visible
|
||||
i += 1
|
||||
if (total_visible == 0) or (runtimes_list != ["all"] and show_runtimes):
|
||||
self.window_set_empty(True)
|
||||
self.main_stack.set_visible_child(self.no_matches)
|
||||
else:
|
||||
self.window_set_empty(False)
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
|
||||
def open_data_folder(self, path):
|
||||
try:
|
||||
Gio.AppInfo.launch_default_for_uri(f"file://{path}", None)
|
||||
except GLib.GError:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not open folder")))
|
||||
|
||||
def trash_data(self, name, id, index):
|
||||
def on_continue(dialog, response):
|
||||
if response == "cancel":
|
||||
return
|
||||
result = self.my_utils.trash_folder(f"{self.user_data_path}{id}")
|
||||
if result != 0:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not trash user data"))
|
||||
)
|
||||
return
|
||||
self.lookup_action(f"open-data{index}").set_enabled(False)
|
||||
self.lookup_action(f"trash{index}").set_enabled(False)
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Trashed user data")))
|
||||
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Send {}'s User Data to the Trash?").format(name)
|
||||
)
|
||||
dialog.set_body(
|
||||
_("Your files and data for this app will be sent to the trash.")
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("continue", _("Trash Data"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", on_continue)
|
||||
dialog.present(self)
|
||||
|
||||
def mask_flatpak(self, row):
|
||||
is_masked = (
|
||||
row.mask_label.get_visible()
|
||||
) # Check the visibility of the mask label to see if the flatpak is masked
|
||||
result = []
|
||||
|
||||
def callback():
|
||||
if result[0] == 1:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(
|
||||
_("Could not disable updates for {}").format(row.app_name)
|
||||
)
|
||||
)
|
||||
return
|
||||
row.set_masked(not is_masked)
|
||||
self.lookup_action(f"mask{row.index}").set_enabled(is_masked)
|
||||
self.lookup_action(f"unmask{row.index}").set_enabled(not is_masked)
|
||||
|
||||
def on_continue(dialog, response):
|
||||
if response == "cancel":
|
||||
return
|
||||
task = Gio.Task.new(None, None, lambda *_: callback())
|
||||
task.run_in_thread(
|
||||
lambda *_: result.append(
|
||||
self.my_utils.mask_flatpak(row.app_id, row.install_type, is_masked)
|
||||
)
|
||||
)
|
||||
|
||||
if is_masked:
|
||||
on_continue(self, None)
|
||||
else:
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Disable Updates for {}?").format(row.app_name)
|
||||
)
|
||||
dialog.set_body(
|
||||
_(
|
||||
"This will mask {} ensuring it will never recieve any feature or security updates."
|
||||
).format(row.app_name)
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("continue", _("Disable Updates"))
|
||||
dialog.connect("response", on_continue)
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.present(self)
|
||||
|
||||
def pin_flatpak(self, row):
|
||||
def thread(*args):
|
||||
command = f"flatpak-spawn --host flatpak pin --{row.install_type} runtime/{row.app_ref}"
|
||||
if row.is_pinned:
|
||||
command += " --remove"
|
||||
response = subprocess.run(
|
||||
command,
|
||||
capture_output=True,
|
||||
text=True,
|
||||
shell=True
|
||||
).stderr
|
||||
if response != "" and row.is_pinned:
|
||||
GLib.idle_add(self.toast_overlay.add_toast(Adw.Toast.new(_("Could not enable auto removal"))))
|
||||
return
|
||||
elif response != "":
|
||||
GLib.idle_add(self.toast_overlay.add_toast(Adw.Toast.new(_("Could not disable auto removal"))))
|
||||
return
|
||||
row.is_pinned = not row.is_pinned
|
||||
GLib.idle_add(lambda *_, row=row: self.lookup_action(f"pin{row.index}").set_enabled(not row.is_pinned))
|
||||
GLib.idle_add(lambda *_, row=row: self.lookup_action(f"unpin{row.index}").set_enabled(row.is_pinned))
|
||||
GLib.idle_add(lambda *_, row=row: row.pin_label.set_visible(row.is_pinned))
|
||||
GLib.idle_add(lambda *_, row=row: row.info_button_show_or_hide())
|
||||
|
||||
def callback(*args):
|
||||
print("done")
|
||||
|
||||
def on_continue(dialog, response):
|
||||
if response == "cancel":
|
||||
return
|
||||
|
||||
task = Gio.Task.new(None, None, None)
|
||||
task.run_in_thread(thread)
|
||||
|
||||
if row.is_pinned:
|
||||
on_continue(self, None)
|
||||
else:
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_(
|
||||
"Disable Automatic Removal for {}?"
|
||||
).format(row.app_name),
|
||||
_(
|
||||
"This will pin {} ensuring it well never be removed automatically, even if no app depends on it."
|
||||
).format(row.app_name),
|
||||
)
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.connect("response", on_continue)
|
||||
dialog.add_response("continue", _("Disable Auto Removal"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.present(self)
|
||||
|
||||
def copy_item(self, to_copy, to_toast=None):
|
||||
self.clipboard.set(to_copy)
|
||||
if to_toast:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(to_toast))
|
||||
|
||||
def run_callback(self, _a, _b):
|
||||
if not self.my_utils.run_app_error:
|
||||
return
|
||||
|
||||
error = self.my_utils.run_app_error_message
|
||||
dialog = Adw.AlertDialog.new(_("Could not Run App"), error)
|
||||
copy_button = Gtk.Button(
|
||||
label=_("Copy"), halign=Gtk.Align.CENTER, margin_top=12
|
||||
)
|
||||
copy_button.add_css_class("pill")
|
||||
copy_button.add_css_class("suggested-action")
|
||||
copy_button.connect("clicked", lambda *_: self.clipboard.set(error))
|
||||
dialog.set_extra_child(copy_button)
|
||||
dialog.add_response("ok", _("OK"))
|
||||
dialog.set_close_response("ok")
|
||||
dialog.present(self)
|
||||
|
||||
def run_app_thread(self, ref, to_toast=None):
|
||||
self.run_app_error = False
|
||||
task = Gio.Task.new(None, None, self.run_callback)
|
||||
task.run_in_thread(lambda *_: self.my_utils.run_app(ref))
|
||||
if to_toast:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(to_toast))
|
||||
|
||||
def batch_mode_handler(self, widget):
|
||||
batch_mode = widget.get_active()
|
||||
i = 0
|
||||
while self.flatpaks_list_box.get_row_at_index(i) != None:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
current.set_selectable(batch_mode)
|
||||
i += 1
|
||||
self.in_batch_mode = batch_mode
|
||||
self.batch_mode_bar.set_revealed(batch_mode)
|
||||
|
||||
if not widget.get_active():
|
||||
self.batch_select_all_button.set_active(False)
|
||||
|
||||
def key_handler(self, controller, keyval, keycode, state):
|
||||
if keyval == Gdk.KEY_w and state == Gdk.ModifierType.CONTROL_MASK:
|
||||
self.close()
|
||||
if keyval == Gdk.KEY_Escape:
|
||||
self.batch_mode_button.set_active(False)
|
||||
|
||||
def batch_actions_enable(self, should_enable):
|
||||
self.batch_copy_button.set_sensitive(should_enable)
|
||||
self.batch_clean_button.set_sensitive(should_enable)
|
||||
self.batch_snapshot_button.set_sensitive(should_enable)
|
||||
if not self.currently_uninstalling:
|
||||
self.batch_uninstall_button.set_sensitive(should_enable)
|
||||
|
||||
def on_batch_clean_response(self, dialog, response, _a):
|
||||
if response == "cancel":
|
||||
return
|
||||
i = 0
|
||||
trashReturnCodes = 0
|
||||
while True:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
i += 1
|
||||
if current == None:
|
||||
break
|
||||
if current.tickbox.get_active() == False:
|
||||
continue
|
||||
trash = self.my_utils.trash_folder(f"{self.user_data_path}{current.app_id}")
|
||||
if trash == 1:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("{} has no data to trash").format(current.app_name))
|
||||
)
|
||||
continue
|
||||
if trash == 2:
|
||||
self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(
|
||||
_("Could not trash {}'s data").format(current.app_name)
|
||||
)
|
||||
)
|
||||
continue
|
||||
self.lookup_action(f"open-data{current.index}").set_enabled(
|
||||
False
|
||||
) # Disable the Open User Data dropdown option when the data was deleted
|
||||
self.lookup_action(f"trash{current.index}").set_enabled(
|
||||
False
|
||||
) # Disable the Trash User Data dropdown option when the data was deleted
|
||||
self.batch_actions_enable(False)
|
||||
self.batch_mode_button.set_active(False)
|
||||
|
||||
def batch_clean_handler(self, widget):
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Trash Selected Apps' User Data?"),
|
||||
_("Your files and data for these apps will be sent to the trash."),
|
||||
)
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Trash Data"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.DESTRUCTIVE)
|
||||
dialog.connect("response", self.on_batch_clean_response, dialog.choose_finish)
|
||||
dialog.present(self)
|
||||
|
||||
def select_all_handler(self, widget):
|
||||
self.set_select_all(widget.get_active())
|
||||
|
||||
def batch_snapshot_handler(self, widget):
|
||||
def batch_snapshot_response(dialog, response, _a):
|
||||
if response == "cancel":
|
||||
return
|
||||
i = 0
|
||||
snapshots_path = (
|
||||
self.host_home
|
||||
+ "/.var/app/io.github.flattool.Warehouse/data/Snapshots/"
|
||||
)
|
||||
snapshot_arr = []
|
||||
app_ver_arr = []
|
||||
app_data_arr = []
|
||||
epoch = int(time.time())
|
||||
self.no_close = self.connect(
|
||||
"close-request", lambda event: True
|
||||
) # Make window unable to close
|
||||
while self.flatpaks_list_box.get_row_at_index(i) != None:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
i += 1
|
||||
if current.tickbox.get_active() == False:
|
||||
continue
|
||||
if not os.path.exists(f"{self.user_data_path}{current.app_id}"):
|
||||
continue
|
||||
snapshot_arr.append(snapshots_path + current.app_id + "/")
|
||||
app_ver_arr.append(current.app_version)
|
||||
app_data_arr.append(f"{self.user_data_path}{current.app_id}")
|
||||
|
||||
def thread():
|
||||
capture = self.my_utils.snapshot_apps(
|
||||
epoch,
|
||||
snapshot_arr,
|
||||
app_ver_arr,
|
||||
app_data_arr,
|
||||
self.main_progress_bar,
|
||||
)
|
||||
if capture != 0:
|
||||
GLib.idle_add(
|
||||
lambda *_: self.toast_overlay.add_toast(
|
||||
Adw.Toast.new(_("Could not snapshot some apps"))
|
||||
)
|
||||
)
|
||||
|
||||
def callback(*args):
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.disconnect(self.no_close)
|
||||
self.search_button.set_sensitive(True)
|
||||
|
||||
self.search_button.set_sensitive(False)
|
||||
self.batch_actions_enable(False)
|
||||
self.batch_mode_button.set_active(False)
|
||||
self.main_stack.set_visible_child(self.snapshotting)
|
||||
task = Gio.Task.new(None, None, callback)
|
||||
task.run_in_thread(lambda *_: thread())
|
||||
|
||||
dialog = Adw.AlertDialog.new(
|
||||
_("Create Snapshots?"),
|
||||
_(
|
||||
"Snapshots are backups of the app's user data. They can be reapplied at any time. This could take a while."
|
||||
),
|
||||
)
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Create Snapshots"))
|
||||
dialog.connect("response", batch_snapshot_response, dialog.choose_finish)
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.present(self)
|
||||
|
||||
def set_select_all(self, should_select_all):
|
||||
i = 0
|
||||
while self.flatpaks_list_box.get_row_at_index(i) != None:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
if (current.get_visible() == True) and (
|
||||
current.app_id != "io.github.flattool.Warehouse"
|
||||
):
|
||||
current.tickbox.set_active(should_select_all)
|
||||
i += 1
|
||||
|
||||
def row_select_handler(self, tickbox):
|
||||
if tickbox.get_active() == True:
|
||||
self.total_selected += 1
|
||||
else:
|
||||
self.total_selected -= 1
|
||||
|
||||
if self.total_selected == 0:
|
||||
buttons_enable = False
|
||||
self.set_title(self.main_window_title)
|
||||
self.batch_actions_enable(False)
|
||||
else:
|
||||
self.set_title(f"{self.total_selected} Selected")
|
||||
self.batch_actions_enable(True)
|
||||
|
||||
def create_action(self, name, callback, shortcuts=None):
|
||||
"""Add a window action.
|
||||
|
||||
Args:
|
||||
name: the name of the action
|
||||
callback: the function to be called when the action is
|
||||
activated
|
||||
shortcuts: an optional list of accelerators
|
||||
"""
|
||||
action = Gio.SimpleAction.new(name, None)
|
||||
action.connect("activate", callback)
|
||||
self.add_action(action)
|
||||
if shortcuts:
|
||||
self.set_accels_for_action(f"app.{name}", shortcuts)
|
||||
|
||||
def copy_names(self, widget, _a):
|
||||
to_copy = ""
|
||||
i = 0
|
||||
while True:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
i += 1
|
||||
if current == None:
|
||||
break
|
||||
if current.tickbox.get_active():
|
||||
to_copy += f"{current.app_name}\n"
|
||||
self.clipboard.set(to_copy)
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app names")))
|
||||
self.batch_actions_enable(False)
|
||||
self.batch_mode_button.set_active(False)
|
||||
|
||||
def copy_IDs(self, widget, _a):
|
||||
to_copy = ""
|
||||
i = 0
|
||||
while True:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
i += 1
|
||||
if current == None:
|
||||
break
|
||||
if current.tickbox.get_active():
|
||||
to_copy += f"{current.app_id}\n"
|
||||
self.clipboard.set(to_copy)
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app IDs")))
|
||||
self.batch_actions_enable(False)
|
||||
self.batch_mode_button.set_active(False)
|
||||
|
||||
def copy_refs(self, widget, _a):
|
||||
to_copy = ""
|
||||
i = 0
|
||||
while True:
|
||||
current = self.flatpaks_list_box.get_row_at_index(i)
|
||||
i += 1
|
||||
if current == None:
|
||||
break
|
||||
if current.tickbox.get_active():
|
||||
to_copy += f"{current.app_ref}\n"
|
||||
self.clipboard.set(to_copy)
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Copied selected app refs")))
|
||||
self.batch_actions_enable(False)
|
||||
self.batch_mode_button.set_active(False)
|
||||
|
||||
def install_callback(self, _a, _b):
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.search_button.set_sensitive(True)
|
||||
if self.my_utils.install_success:
|
||||
self.refresh_list_of_flatpaks(self)
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Installed successfully")))
|
||||
else:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("Could not install app")))
|
||||
|
||||
def install_thread(self, filepath, user_or_system):
|
||||
self.my_utils.install_flatpak(
|
||||
[filepath], None, user_or_system, self.main_progress_bar
|
||||
)
|
||||
|
||||
def install_file(self, filepath):
|
||||
def response(dialog, response, _a):
|
||||
if response == "cancel":
|
||||
return
|
||||
|
||||
self.main_stack.set_visible_child(self.installing)
|
||||
self.search_button.set_sensitive(False)
|
||||
user_or_system = "user"
|
||||
if system_check.get_active():
|
||||
user_or_system = "system"
|
||||
|
||||
task = Gio.Task.new(None, None, self.install_callback)
|
||||
task.run_in_thread(lambda *_: self.install_thread(filepath, user_or_system))
|
||||
|
||||
name = filepath.split("/")
|
||||
name = name[len(name) - 1]
|
||||
|
||||
dialog = Adw.AlertDialog.new(_("Install {}?").format(name))
|
||||
dialog.set_close_response("cancel")
|
||||
dialog.add_response("cancel", _("Cancel"))
|
||||
dialog.add_response("continue", _("Install"))
|
||||
dialog.set_response_appearance("continue", Adw.ResponseAppearance.SUGGESTED)
|
||||
dialog.connect("response", response, dialog.choose_finish)
|
||||
|
||||
# Create Widgets
|
||||
options_box = Gtk.Box(orientation="vertical")
|
||||
options_list = Gtk.ListBox(selection_mode="none", margin_top=15)
|
||||
user_row = Adw.ActionRow(
|
||||
title=_("User"), subtitle=_("The app will be available to only you")
|
||||
)
|
||||
system_row = Adw.ActionRow(
|
||||
title=_("System"),
|
||||
subtitle=_("The app will be available to every user on the system"),
|
||||
)
|
||||
user_check = Gtk.CheckButton()
|
||||
system_check = Gtk.CheckButton()
|
||||
|
||||
# Apply Widgets
|
||||
user_row.add_prefix(user_check)
|
||||
user_row.set_activatable_widget(user_check)
|
||||
system_row.add_prefix(system_check)
|
||||
system_row.set_activatable_widget(system_check)
|
||||
user_check.set_group(system_check)
|
||||
options_list.append(user_row)
|
||||
options_list.append(system_row)
|
||||
options_box.append(options_list)
|
||||
dialog.set_extra_child(options_box)
|
||||
|
||||
# Calls
|
||||
user_check.set_active(True)
|
||||
options_list.add_css_class("boxed-list")
|
||||
dialog.present(self)
|
||||
|
||||
def drop_callback(self, target, _x, _y, _data):
|
||||
filepath = target.get_value().get_path()
|
||||
if filepath.endswith(".flatpak") or filepath.endswith(".flatpakref"):
|
||||
self.install_file(filepath)
|
||||
elif filepath.endswith(".flatpakrepo"):
|
||||
remotes_window = RemotesWindow(self)
|
||||
remotes_window.present()
|
||||
remotes_window.add_remote_file(filepath)
|
||||
else:
|
||||
self.toast_overlay.add_toast(Adw.Toast.new(_("File type not supported")))
|
||||
|
||||
def on_invalidate(self, row):
|
||||
if self.is_empty:
|
||||
self.batch_mode_button.set_active(False)
|
||||
self.main_stack.set_visible_child(self.no_flatpaks)
|
||||
self.search_button.set_sensitive(False)
|
||||
else:
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.search_button.set_sensitive(True)
|
||||
|
||||
self.is_result = False
|
||||
self.flatpaks_list_box.invalidate_filter()
|
||||
if self.is_result == False:
|
||||
self.main_stack.set_visible_child(self.no_results)
|
||||
self.search_button.set_sensitive(False)
|
||||
|
||||
def on_change(self, prop, prop2):
|
||||
if self.search_bar.get_search_mode() == False:
|
||||
if self.is_empty:
|
||||
self.batch_mode_button.set_active(False)
|
||||
self.main_stack.set_visible_child(self.no_flatpaks)
|
||||
self.search_button.set_sensitive(False)
|
||||
else:
|
||||
self.main_stack.set_visible_child(self.main_box)
|
||||
self.search_button.set_sensitive(True)
|
||||
|
||||
def __init__(self, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
self.my_utils = myUtils(self)
|
||||
self.set_size_request(360, 360)
|
||||
self.settings = Gio.Settings.new("io.github.flattool.Warehouse")
|
||||
self.settings.bind(
|
||||
"window-width", self, "default-width", Gio.SettingsBindFlags.DEFAULT
|
||||
)
|
||||
self.settings.bind(
|
||||
"window-height", self, "default-height", Gio.SettingsBindFlags.DEFAULT
|
||||
)
|
||||
self.settings.bind(
|
||||
"is-maximized", self, "maximized", Gio.SettingsBindFlags.DEFAULT
|
||||
)
|
||||
self.settings.bind(
|
||||
"is-fullscreen", self, "fullscreened", Gio.SettingsBindFlags.DEFAULT
|
||||
)
|
||||
|
||||
self.system_pins = self.my_utils.get_host_system_pins()
|
||||
self.user_pins = self.my_utils.get_host_user_pins()
|
||||
|
||||
self.new_env = dict(os.environ)
|
||||
self.new_env["LC_ALL"] = "C"
|
||||
|
||||
if self.host_flatpaks == [["", ""]]:
|
||||
self.window_set_empty(True)
|
||||
return
|
||||
|
||||
self.flatpaks_list_box.set_filter_func(self.filter_func)
|
||||
|
||||
task = Gio.Task()
|
||||
task.run_in_thread(
|
||||
lambda *_: GLib.idle_add(lambda *_: self.generate_list_of_flatpaks())
|
||||
)
|
||||
|
||||
self.search_entry.connect("search-changed", self.on_invalidate)
|
||||
self.search_bar.connect_entry(self.search_entry)
|
||||
self.search_bar.connect("notify", self.on_change)
|
||||
self.filter_button.connect("clicked", lambda *_: FilterWindow(self))
|
||||
self.batch_mode_button.connect("toggled", self.batch_mode_handler)
|
||||
self.batch_clean_button.connect("clicked", self.batch_clean_handler)
|
||||
self.batch_uninstall_button.connect(
|
||||
"clicked", self.batch_uninstall_button_handler
|
||||
)
|
||||
self.batch_select_all_button.connect("clicked", self.select_all_handler)
|
||||
self.batch_snapshot_button.connect("clicked", self.batch_snapshot_handler)
|
||||
self.reset_filters_button.connect("clicked", lambda *_: self.reset_filters())
|
||||
self.batch_actions_enable(False)
|
||||
event_controller = Gtk.EventControllerKey()
|
||||
event_controller.connect("key-pressed", self.key_handler)
|
||||
self.add_controller(event_controller)
|
||||
self.main_overlay.add_overlay(self.main_progress_bar)
|
||||
|
||||
self.create_action("copy-names", self.copy_names)
|
||||
self.create_action("copy-ids", self.copy_IDs)
|
||||
self.create_action("copy-refs", self.copy_refs)
|
||||
|
||||
file_drop = Gtk.DropTarget.new(Gio.File, Gdk.DragAction.COPY)
|
||||
file_drop.connect("drop", self.drop_callback)
|
||||
self.scrolled_window.add_controller(file_drop)
|
||||
|
||||
if Config.DEVEL:
|
||||
self.add_css_class("devel")
|
||||
Reference in New Issue
Block a user