From ecda8b258a3a00d1fec50b08b7e1ae1873c8a7c7 Mon Sep 17 00:00:00 2001 From: GloriousEggroll Date: Thu, 27 Mar 2025 05:52:26 -0600 Subject: [PATCH] add basic functionality for user/system toggle --- libflatpak_query.py | 35 ++++++------- main.py | 125 ++++++++++++++++++++++++++++++++++---------- 2 files changed, 113 insertions(+), 47 deletions(-) diff --git a/libflatpak_query.py b/libflatpak_query.py index dc57c16..0fdf671 100755 --- a/libflatpak_query.py +++ b/libflatpak_query.py @@ -246,8 +246,8 @@ class AppstreamSearcher: packages = [] metadata = AppStream.Metadata.new() metadata.set_format_style(AppStream.FormatStyle.CATALOG) - if self.refresh: - inst.update_appstream_full_sync(remote.get_name(), None, None, True) + #if self.refresh: + # inst.update_appstream_full_sync(remote.get_name(), None, None, True) appstream_file = Path(remote.get_appstream_dir().get_path() + "/appstream.xml.gz") if appstream_file.exists(): metadata.parse_file(Gio.File.new_for_path(appstream_file.as_posix()), AppStream.FormatKind.XML) @@ -333,9 +333,8 @@ class AppstreamSearcher: installation = get_installation(system) def process_installed_refs(inst: Flatpak.Installation, system=False): - for ref in inst.list_installed_refs_by_kind(Flatpak.RefKind.APP): - app_id = ref.format_ref() - # Get remote name from the installation + for ref in inst.list_installed_refs(): + app_id = ref.get_name() remote_name = ref.get_origin() if system is False: installed_refs.append((app_id, remote_name, "user")) @@ -461,8 +460,6 @@ class AppstreamSearcher: if "installed" in category: installed_apps = searcher.get_installed_apps() for app_id, repo_name, repo_type in installed_apps: - parts = app_id.split('/') - app_id = parts[parts.index('app') + 1] if repo_name: search_result = searcher.search_flatpak(app_id, repo_name) self.installed_results.extend(search_result) @@ -485,10 +482,13 @@ class AppstreamSearcher: if not check_internet(): return self._handle_offline_mode() + if system: + refresh = False + searcher = get_reposearcher(system, refresh) self.all_apps = searcher.get_all_apps() - return self._process_categories(searcher) + return self._process_categories(searcher, system) def _initialize_metadata(self): """Initialize empty lists for metadata storage.""" @@ -520,7 +520,7 @@ class AppstreamSearcher: self.collection_results.extend(search_result) return self._get_current_results() - def _process_categories(self, searcher): + def _process_categories(self, searcher, system=False): """Process categories and retrieve metadata.""" total_categories = sum(len(categories) for categories in self.category_groups.values()) current_category = 0 @@ -530,7 +530,7 @@ class AppstreamSearcher: if category not in self.category_groups['system']: self._process_category(searcher, category, current_category, total_categories) else: - self._process_system_category(searcher, category) + self._process_system_category(searcher, category, system) current_category += 1 return self._get_current_results() @@ -585,18 +585,16 @@ class AppstreamSearcher: except requests.RequestException as e: logger.error(f"Error refreshing category {category}: {str(e)}") - def _process_system_category(self, searcher, category): + def _process_system_category(self, searcher, category, system=False): """Process system-related categories.""" if "installed" in category: - installed_apps = searcher.get_installed_apps() + installed_apps = searcher.get_installed_apps(system) for app_id, repo_name, repo_type in installed_apps: - parts = app_id.split('/') - app_id = parts[parts.index('app') + 1] if repo_name: search_result = searcher.search_flatpak(app_id, repo_name) self.installed_results.extend(search_result) elif "updates" in category: - updates = searcher.check_updates() + updates = searcher.check_updates(system) for app_id, repo_name, repo_type in updates: if repo_name: search_result = searcher.search_flatpak(app_id, repo_name) @@ -617,6 +615,7 @@ def install_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tupl Args: app (AppStreamPackage): The package to install. repo_name (str): Optional repository name to use for installation + system (Optional[bool]): Whether to operate on user or system installation Returns: tuple[bool, str]: (success, message) @@ -644,7 +643,7 @@ def remove_flatpak(app: AppStreamPackage, repo_name=None, system=False) -> tuple Args: app (AppStreamPackage): The package to install. - user (Optional[bool]): Whether to operate on user or system installation + system (Optional[bool]): Whether to operate on user or system installation Returns: Tuple[bool, str]: (success, message) @@ -872,6 +871,8 @@ def main(): parser.add_argument('--remove', type=str, metavar='APP_ID', help='Remove a Flatpak package') parser.add_argument('--system', action='store_true', help='Install as system instead of user') + parser.add_argument('--refresh', action='store_true', help='Install as system instead of user') + parser.add_argument('--refresh-local', action='store_true', help='Install as system instead of user') args = parser.parse_args() @@ -978,8 +979,6 @@ def handle_list_installed(args, searcher): installed_apps = searcher.get_installed_apps(args.system) print(f"\nInstalled Flatpak Applications ({len(installed_apps)}):") for app_id, repo_name, repo_type in installed_apps: - parts = app_id.split('/') - app_id = parts[parts.index('app') + 1] print(f"{app_id} (Repository: {repo_name}, Installation: {repo_type})") def handle_check_updates(args, searcher): diff --git a/main.py b/main.py index e0ad1ff..1a022bd 100755 --- a/main.py +++ b/main.py @@ -8,6 +8,7 @@ from gi.repository import Gtk, Gio, Gdk, GLib import libflatpak_query import json import threading +import subprocess class MainWindow(Gtk.Window): def __init__(self): @@ -159,10 +160,13 @@ class MainWindow(Gtk.Window): ) # Create main layout - self.main_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL) self.add(self.main_box) + # Create_header_bar + self.create_header_bar() + # Create panels self.create_panels() @@ -171,6 +175,85 @@ class MainWindow(Gtk.Window): # Select Trending by default self.select_default_category() + def create_header_bar(self): + # Create horizontal bar + self.top_bar = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.top_bar.set_hexpand(True) + self.top_bar.set_spacing(6) + self.top_bar.set_border_width(6) + + # Add search bar + self.searchbar = Gtk.SearchBar() # Use self.searchbar instead of searchbar + self.searchbar.set_hexpand(True) + self.searchbar.set_margin_bottom(6) + + # Create search entry with icon + searchentry = Gtk.SearchEntry() + searchentry.set_placeholder_text("Search applications...") + searchentry.set_icon_from_gicon(Gtk.EntryIconPosition.PRIMARY, + Gio.Icon.new_for_string('search')) + + # Connect search entry signals + searchentry.connect("search-changed", self.on_search_changed) + searchentry.connect("activate", self.on_search_activate) + + # Connect search entry to search bar + self.searchbar.connect_entry(searchentry) + self.searchbar.add(searchentry) + + self.searchbar.set_search_mode(True) + + self.top_bar.pack_start(self.searchbar, False, False, 0) + + # Create system mode switch box + system_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=6) + + # Create system mode switch + self.system_switch = Gtk.Switch() + self.system_switch.connect("notify::active", self.on_system_mode_toggled) + self.system_switch.set_vexpand(False) + + # Create system mode label + system_label = Gtk.Label(label="System") + + # Pack switch and label + system_box.pack_end(system_label, False, False, 0) + system_box.pack_end(self.system_switch, False, False, 0) + + # Add system controls to header + self.top_bar.pack_end(system_box, False, False, 0) + + # Add the top bar to the main box + self.main_box.pack_start(self.top_bar, False, True, 0) + + def on_system_mode_toggled(self, switch, gparam): + """Handle system mode toggle switch state changes""" + desired_state = switch.get_active() + + if desired_state: + # Request superuser validation + try: + subprocess.run(['pkexec', 'true'], check=True) + self.system_mode = True + self.refresh_data() + self.refresh_current_page() + except subprocess.CalledProcessError: + switch.set_active(False) + dialog = Gtk.MessageDialog( + transient_for=self, + message_type=Gtk.MessageType.ERROR, + buttons=Gtk.ButtonsType.OK, + text="Authentication failed", + secondary_text="Could not enable system mode" + ) + dialog.connect("response", lambda d, r: d.destroy()) + dialog.show() + else: + if self.system_mode == True: + self.system_mode = False + self.refresh_data() + self.refresh_current_page() + def populate_repo_dropdown(self): # Get list of repositories libflatpak_query.repolist(self.system_mode) @@ -282,15 +365,22 @@ class MainWindow(Gtk.Window): if hasattr(self, 'right_panel') and self.right_panel.get_parent(): self.main_box.remove(self.right_panel) - # Create right panel - self.right_panel = self.create_applications_panel("Applications") - # Create left panel with grouped categories self.left_panel = self.create_grouped_category_panel("Categories", self.category_groups) + # Create right panel + self.right_panel = self.create_applications_panel("Applications") + + # Create panels container + self.panels_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL) + self.panels_box.set_hexpand(True) + # Pack the panels with proper expansion - self.main_box.pack_end(self.right_panel, True, True, 0) # Right panel expands both ways - self.main_box.pack_start(self.left_panel, False, False, 0) # Left panel doesn't expand + self.panels_box.pack_start(self.left_panel, False, False, 0) # Left panel doesn't expand + self.panels_box.pack_end(self.right_panel, True, True, 0) # Right panel expands both ways + + # Add panels container to main box + self.main_box.pack_start(self.panels_box, True, True, 0) def create_grouped_category_panel(self, title, groups): @@ -304,25 +394,6 @@ class MainWindow(Gtk.Window): panel_container.set_halign(Gtk.Align.FILL) # Fill horizontally panel_container.set_valign(Gtk.Align.FILL) # Align to top - # Add search bar - self.searchbar = Gtk.SearchBar() # Use self.searchbar instead of searchbar - self.searchbar.set_hexpand(True) - self.searchbar.set_margin_bottom(6) - - # Create search entry with icon - searchentry = Gtk.SearchEntry() - searchentry.set_placeholder_text("Search applications...") - searchentry.set_icon_from_gicon(Gtk.EntryIconPosition.PRIMARY, - Gio.Icon.new_for_string('search')) - - # Connect search entry signals - searchentry.connect("search-changed", self.on_search_changed) - searchentry.connect("activate", self.on_search_activate) - - # Connect search entry to search bar - self.searchbar.connect_entry(searchentry) - self.searchbar.add(searchentry) - # Create scrollable area scrolled_window = Gtk.ScrolledWindow() scrolled_window.set_hexpand(True) @@ -393,13 +464,9 @@ class MainWindow(Gtk.Window): scrolled_window.add(container) # Pack the scrolled window directly into main box - panel_container.pack_start(self.searchbar, False, False, 0) panel_container.pack_start(scrolled_window, True, True, 0) - - self.searchbar.set_search_mode(True) return panel_container - #self.searchbar.show_all() def on_search_changed(self, searchentry): """Handle search text changes"""