diff --git a/app/main.py b/app/main.py index 1dc7847..4308f02 100755 --- a/app/main.py +++ b/app/main.py @@ -1,9 +1,11 @@ #!/usr/bin/env python3 -import gi import argparse import sys + +import gi from network_service import NetworkService from ui.main_window import NetworkManagerWindow + gi.require_version("Gtk", "4.0") from gi.repository import Gtk from ui.styles import StyleManager @@ -11,42 +13,41 @@ # Application version __version__ = "1.0.0" + class NetworkManagerApp(Gtk.Application): """Main application class""" - + def __init__(self): super().__init__(application_id="com.network.manager") - + def do_activate(self): """Application activation""" StyleManager.apply_styles() win = NetworkManagerWindow(self) win.present() + def parse_arguments(): """Parse command line arguments""" parser = argparse.ArgumentParser( description="Network Manager - A GTK4 network management application", - prog="network-manager" - ) - - parser.add_argument( - "-v", "--version", - action="version", - version=f"%(prog)s {__version__}" + prog="network-manager", ) - + + parser.add_argument("-v", "--version", action="version", version=f"%(prog)s {__version__}") + return parser.parse_args() + if __name__ == "__main__": try: # Parse command line arguments first args = parse_arguments() - + # Check if NetworkManager is available before starting the app if not NetworkService.check_networkmanager(): sys.exit(1) - + # If we get here, NetworkManager is available, so start the app app = NetworkManagerApp() app.run() diff --git a/app/ui/main_window.py b/app/ui/main_window.py index c3d8ead..0e255d3 100644 --- a/app/ui/main_window.py +++ b/app/ui/main_window.py @@ -1,24 +1,27 @@ import gi - from models import NetworkInfo, WiFiState + from ui.network_list import NetworkListWidget + gi.require_version("Gtk", "4.0") -from gi.repository import Gtk, Gdk, GLib from typing import Optional -from network_service import NetworkService +from gi.repository import Gdk, GLib, Gtk +from network_service import NetworkService + +from ui.dialogs import PasswordDialog from ui.network_details import NetworkDetailsWidget from ui.wifi_off import WiFiOffWidget -from ui.dialogs import PasswordDialog + class NetworkManagerWindow(Gtk.ApplicationWindow): """Main application window""" - + def __init__(self, app): super().__init__(application=app) self.set_title("Network Manager") self.set_default_size(450, 350) - + self.current_state = WiFiState.OFF self.current_view = "list" # "list" or "details" self._setup_ui() @@ -28,7 +31,7 @@ def __init__(self, app): key_controller = Gtk.EventControllerKey.new() key_controller.connect("key-pressed", self._on_esc_pressed) self.add_controller(key_controller) - + def _setup_ui(self): """Setup the main UI""" main_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=10) @@ -36,46 +39,46 @@ def _setup_ui(self): main_box.set_margin_bottom(20) main_box.set_margin_start(20) main_box.set_margin_end(20) - + main_box.append(self._create_wifi_toggle()) main_box.append(Gtk.Separator(orientation=Gtk.Orientation.HORIZONTAL)) - + self.content_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, vexpand=True) main_box.append(self.content_box) - + self.set_child(main_box) - + def _create_wifi_toggle(self) -> Gtk.Box: """Create the WiFi toggle section""" self.wifi_switch = Gtk.Switch(valign=Gtk.Align.CENTER) self.wifi_switch.connect("state-set", self._on_wifi_toggled) - + wifi_label = Gtk.Label(label="Wi-Fi", xalign=0, name="wifi-label") - + toggle_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, spacing=10) toggle_box.append(wifi_label) toggle_box.append(Gtk.Box(hexpand=True)) # Spacer toggle_box.append(self.wifi_switch) - + return toggle_box - + def _on_wifi_toggled(self, switch, state): """Handle WiFi toggle""" current_state = NetworkService.get_wifi_status() - + if current_state == state: return - + if NetworkService.toggle_wifi(state): self._update_wifi_state(scan_immediately=state) else: switch.set_active(current_state) - + def _update_wifi_state(self, scan_immediately=False, initial_load=False): """Update the UI based on WiFi state""" wifi_status = NetworkService.get_wifi_status() self.wifi_switch.set_active(wifi_status) - + if wifi_status: self.current_state = WiFiState.ON if self.current_view == "list": @@ -84,36 +87,36 @@ def _update_wifi_state(self, scan_immediately=False, initial_load=False): self.current_state = WiFiState.OFF self.current_view = "list" self._show_wifi_off() - + def _show_network_list(self, scan_immediately=False): """Show the network list widget""" self.current_view = "list" self._clear_content() - + self.network_list = NetworkListWidget(self._on_network_selected, self._on_network_details) self.content_box.append(self.network_list) - + if scan_immediately: GLib.timeout_add(500, lambda: self.network_list.start_scan()) - + def _show_network_details(self, network: NetworkInfo): """Show the network details widget""" self.current_view = "details" self._clear_content() - + self.network_details = NetworkDetailsWidget(network, self._on_back_to_list) self.content_box.append(self.network_details) - + def _on_back_to_list(self): """Handle back button click from details view""" self._show_network_list(scan_immediately=True) - + def _show_wifi_off(self): """Show the WiFi off widget""" self._clear_content() wifi_off_widget = WiFiOffWidget() self.content_box.append(wifi_off_widget) - + def _clear_content(self): """Clear the content area""" child = self.content_box.get_first_child() @@ -121,39 +124,43 @@ def _clear_content(self): next_child = child.get_next_sibling() self.content_box.remove(child) child = next_child - + def _on_network_selected(self, network: NetworkInfo): """Handle network selection for connection""" if NetworkService.is_wifi_known(network.ssid): self._connect_to_network(network.ssid) elif network.requires_password: - dialog = PasswordDialog(self, network.ssid, - lambda password: self._connect_to_network(network.ssid, password)) + dialog = PasswordDialog( + self, + network.ssid, + lambda password: self._connect_to_network(network.ssid, password), + ) dialog.show() else: self._connect_to_network(network.ssid) - + def _on_network_details(self, network: NetworkInfo): """Handle network details button click""" self._show_network_details(network) - + def _connect_to_network(self, ssid: str, password: Optional[str] = None): """Connect to a network""" self.current_state = WiFiState.CONNECTING - + def connect_thread(): success, message = NetworkService.connect_to_network(ssid, password) GLib.idle_add(lambda: self._connection_complete(ssid, success, message)) - + import threading + thread = threading.Thread(target=connect_thread) thread.daemon = True thread.start() - + def _connection_complete(self, ssid: str, success: bool, message: str): """Handle connection completion""" self.current_state = WiFiState.ON - + dialog = Gtk.AlertDialog() if success: dialog.set_message(f"Connected to {ssid}") @@ -161,13 +168,13 @@ def _connection_complete(self, ssid: str, success: bool, message: str): else: dialog.set_message(f"Failed to connect to {ssid}") dialog.set_detail(f"Error: {message}") - + dialog.set_modal(True) dialog.show(self) - - if self.current_view == "list" and hasattr(self, 'network_list'): + + if self.current_view == "list" and hasattr(self, "network_list"): self.network_list.start_scan() - + return False def _on_esc_pressed(self, controller, keyval, keycode, state): diff --git a/app/ui/styles.py b/app/ui/styles.py index 0a726e8..c9fd91b 100644 --- a/app/ui/styles.py +++ b/app/ui/styles.py @@ -1,17 +1,21 @@ """CSS styling for the application""" import gi + gi.require_version("Gtk", "4.0") -from gi.repository import Gtk, Gdk +import os + +from gi.repository import Gdk, Gtk + class StyleManager: """Handles CSS styling for the application""" - + CSS_STYLES = b""" #wifi-label { font-weight: bold; font-size: 16px; - } + } #wifi-networks-label { font-weight: bold; @@ -25,18 +29,18 @@ class StyleManager: #wifi-scan-label:hover { color: #4a90d9; } - + #scanning-label { font-style: italic; color: #888888; margin: 10px; } - + #error-label { color: #cc0000; margin: 10px; } - + #no-networks-label { font-style: italic; color: #888888; @@ -50,21 +54,21 @@ class StyleManager: .connected-network { background-color: rgba(74, 144, 217, 0.1); } - + #wifi-off-box { margin: 50px; } - + #wifi-off-label { color: #666666; font-size: 14px; } - + .rescan-in-progress { color: #4a90d9; animation: pulse 1.5s infinite; } - + @keyframes pulse { 0% { opacity: 1; } 50% { opacity: 0.5; } @@ -75,30 +79,35 @@ class StyleManager: font-size: 1.5rem; font-weight: 500; } - + .title-4 { font-size: 1.1rem; font-weight: 500; margin-bottom: 8px; } - + .dim-label { opacity: 0.7; font-size: 0.9rem; } - + scrollbar slider { min-width: 6px; min-height: 6px; } """ - + @classmethod def apply_styles(cls): """Apply CSS styles to the application""" css_provider = Gtk.CssProvider() - css_provider.load_from_data(cls.CSS_STYLES) - + # Test if the ~/.config/nmgui/style.css exists + home_path = os.path.expanduser("~") + if os.path.exists(f"{home_path}/.config/nmgui/style.css"): + css_provider.load_from_path(f"{home_path}/.config/nmgui/style.css") + else: + css_provider.load_from_data(cls.CSS_STYLES) + display = Gdk.Display.get_default() Gtk.StyleContext.add_provider_for_display( display, css_provider, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION