Fresh start

Remove pretty much everything in preparation of complete overhaul for 2.X
This commit is contained in:
heliguy
2024-07-03 16:35:39 -04:00
parent 69d6a7952d
commit 342c2d4194
27 changed files with 171 additions and 6272 deletions

View File

@@ -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.");
}
}
}
};
}
}

View File

@@ -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"]
}
}
}
}
}
}
};
};
}

View File

@@ -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;
}
}
}
}

View File

@@ -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;
}
}
}
}
}
};
}
}

View File

@@ -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 {}
}
}
}
}
};
}
}

View File

@@ -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"]
}
}
}
};
}
}

View File

@@ -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"
]
}
}
};
}
};
}
}

View File

@@ -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";
}
}
};
}
}

View File

@@ -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";
}
}
}

View File

@@ -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

View File

@@ -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 ""

View File

@@ -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()

View File

@@ -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

View File

@@ -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)

View File

@@ -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

View File

@@ -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)

View 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
View 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")

View File

@@ -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(

View 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

View File

@@ -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)

View File

@@ -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

View File

@@ -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

View File

@@ -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)

View File

@@ -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>

View File

@@ -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))

View File

@@ -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")