diff --git a/install-station b/install-station index 0fa003b..5ff8acf 100755 --- a/install-station +++ b/install-station @@ -1,10 +1,16 @@ #!/usr/local/bin/python """ Install Station executable module. + +This is the main entry point for the Install Station GTK+ application. +It initializes all page components and sets up the main window interface. """ +from __future__ import annotations + import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk + from install_station.language import Language from install_station.keyboard import Keyboard from install_station.network_setup import NetworkSetup @@ -21,16 +27,22 @@ from install_station.interface_controller import Interface, Button class MainWindow: """ Install Station main window class. + + This class initializes the main GTK window and sets up all page components + for the installation wizard interface. """ - def __init__(self): + def __init__(self) -> None: """ - Install Station main window class initiation. + Initialize the Install Station main window. + + Sets up page assignments to Interface class, configures the main window + properties, and creates the main interface layout. """ Interface.welcome = Language Interface.keyboard = Keyboard Interface.network_setup = NetworkSetup - Interface.try_isntall = TryOrInstall + Interface.try_install = TryOrInstall Interface.installation_type = InstallTypes Interface.custom_partition = PartitionManager Interface.full_zfs = ZFS diff --git a/install_station/__init__.py b/install_station/__init__.py index e69de29..1927838 100644 --- a/install_station/__init__.py +++ b/install_station/__init__.py @@ -0,0 +1,14 @@ +""" +Install Station Package. + +A streamlined installer for GhostBSD providing a GTK+ interface +for disk partitioning and OS installation. + +This package contains all the modules needed for the installation wizard +including language selection, keyboard configuration, network setup, +and filesystem management. +""" + +__version__ = "0.1" +__author__ = "Eric Turgeon" +__license__ = "BSD" \ No newline at end of file diff --git a/install_station/common.py b/install_station/common.py index f62d40d..50f2471 100644 --- a/install_station/common.py +++ b/install_station/common.py @@ -1,3 +1,9 @@ +""" +Common utility functions for Install Station. + +This module provides various utility functions including password strength +checking, text validation, and deprecation decorators. +""" import re import warnings from install_station.data import get_text @@ -62,13 +68,15 @@ def lower_upper(text: str) -> bool: return not bool(search(text)) -# Find if password contain only lower and upper case and -def lower_upper_number(text) -> bool: +def lower_upper_number(text: str) -> bool: """ - Find if password contain only lower and upper case and - :param text: password - - :return: True if password contain only lower and upper case and + Find if password contains only lowercase, uppercase letters and numbers. + + Args: + text: password to check + + Returns: + True if password contains only lowercase, uppercase letters and numbers """ search = re.compile(r'[^a-zA-Z0-9]').search return not bool(search(text)) @@ -76,7 +84,7 @@ def lower_upper_number(text) -> bool: # Find if password contain only lowercase, uppercase numbers # and some special character. -def all_character(text): +def all_character(text: str) -> bool: """ Find if password contain only lowercase, uppercase numbers and some special character. @@ -89,7 +97,14 @@ def all_character(text): return not bool(search(text)) -def password_strength(password, label3): +def password_strength(password: str, label3) -> None: + """ + Evaluate and display password strength. + + Args: + password: The password to evaluate + label3: GTK Label widget to display strength result + """ same_character_type = any( [ lower_case(password), @@ -155,6 +170,16 @@ def password_strength(password, label3): def deprecated(*, version: str, reason: str): + """ + Decorator to mark functions as deprecated. + + Args: + version: Version when function was deprecated + reason: Reason for deprecation + + Returns: + Decorator function that adds deprecation warnings + """ def decorator(func): def wrapper(*args, **kwargs): warnings.warn( diff --git a/install_station/data.py b/install_station/data.py index b3385e8..6e03150 100644 --- a/install_station/data.py +++ b/install_station/data.py @@ -4,14 +4,14 @@ Contains the data class and some commonly use variables import os import gettext -be_name = "default" -logo = "/usr/local/lib/install-station/image/logo.png" -gif_logo = "/usr/local/lib/install-station/image/G_logo.gif" -pc_sysinstall = "/usr/local/sbin/pc-sysinstall" -query = "sh /usr/local/lib/install-station/backend-query" -tmp = "/tmp" -installation_config = f'{tmp}/ghostbsd_installation.cfg' -zfs_datasets = "/," \ +be_name: str = "default" +logo: str = "/usr/local/lib/install-station/image/logo.png" +gif_logo: str = "/usr/local/lib/install-station/image/G_logo.gif" +pc_sysinstall: str = "/usr/local/sbin/pc-sysinstall" +query: str = "sh /usr/local/lib/install-station/backend-query" +tmp: str = "/tmp" +installation_config: str = f'{tmp}/ghostbsd_installation.cfg' +zfs_datasets: str = "/," \ "/home(mountpoint=/home)," \ "/tmp(mountpoint=/tmp|exec=on|setuid=off)," \ "/usr(mountpoint=/usr|canmount=off)," \ @@ -67,7 +67,7 @@ class InstallationData: network_config: dict = {} @classmethod - def reset(cls): + def reset(cls) -> None: """Reset all installation data""" cls.destroy = {} cls.delete = [] @@ -92,7 +92,7 @@ class InstallationData: cls.network_config = {} -def get_text(text): +def get_text(text: str) -> str: """ Global translation function that always returns current language translation. diff --git a/install_station/install_type.py b/install_station/install_type.py index 7952fe0..410f2d2 100644 --- a/install_station/install_type.py +++ b/install_station/install_type.py @@ -18,30 +18,72 @@ styleContext.add_provider_for_screen( class InstallTypes: + """Utility class for filesystem type selection following the utility class pattern. + + This class provides a GTK+ interface for installation type selection including: + - Filesystem type selection between ZFS disk configuration and multi-boot + - Radio button interface for user selection + - Integration with InstallationData for persistent configuration + + The class follows a utility pattern with class methods and variables for state management, + designed to integrate with the Interface controller for navigation flow. + """ # Class variables instead of instance variables - ne = 'zfs' - vbox1 = None + ne: str = 'zfs' + vbox1: Gtk.Box | None = None + full_zfs_button: Gtk.RadioButton | None = None + custom_button: Gtk.RadioButton | None = None @classmethod - def filesystem_type(cls, widget, val): + def filesystem_type(cls, widget: Gtk.RadioButton, val: str) -> None: + """Handle filesystem type selection from radio buttons. + + Only responds to activation, not deactivation. Updates both + class variables and InstallationData with the selected filesystem type. + + Args: + widget: RadioButton widget that triggered the action + val: Filesystem type value ('zfs' or 'custom') + """ # Only respond to activation, not deactivation if widget.get_active(): cls.ne = val InstallationData.filesystem_type = val - return + print(f"Filesystem type selected: {val}") @classmethod - def get_type(cls): + def get_type(cls) -> str: + """Get the current filesystem type selection. + + Returns: + str: Current filesystem type ('zfs' or 'custom') + """ return InstallationData.filesystem_type or cls.ne @classmethod - def get_model(cls): + def get_model(cls) -> Gtk.Box: + """Return the GTK widget model for the installation type interface. + + Returns the main container widget that was created during initialization. + + Returns: + Gtk.Box: The main container widget for the installation type interface + """ if cls.vbox1 is None: cls.initialize() return cls.vbox1 @classmethod - def initialize(cls): + def initialize(cls) -> None: + """Initialize the installation type selection UI following the utility class pattern. + + Creates the main interface including: + - Radio buttons for ZFS disk configuration and multi-boot setup + - Descriptive text for each option + - Centered layout with proper spacing + + This method is called automatically by get_model() when the interface is first accessed. + """ cls.vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0) cls.vbox1.show() vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0) diff --git a/install_station/interface_controller.py b/install_station/interface_controller.py index 37463fc..17c01ef 100644 --- a/install_station/interface_controller.py +++ b/install_station/interface_controller.py @@ -1,28 +1,43 @@ +""" +Interface Controller Module. + +This module provides the main navigation interface and button controls +for the Install Station GTK application wizard. +""" +import gi +gi.require_version('Gtk', '3.0') from gi.repository import Gtk from install_station.install import InstallProgress, InstallWindow from install_station.partition import DiskPartition from install_station.window import Window from install_station.data import InstallationData, get_text +from install_station.system_calls import localize_system, set_keyboard class Button: - back_button = Gtk.Button(label=get_text('Back')) + """ + Button management class for navigation controls. + + Manages the Back, Cancel, and Next buttons used throughout + the installation wizard interface. + """ + back_button: Gtk.Button = Gtk.Button(label=get_text('Back')) """This button is used to go back to the previous page.""" - cancel_button = Gtk.Button(label=get_text('Cancel')) + cancel_button: Gtk.Button = Gtk.Button(label=get_text('Cancel')) """This button is used to quit and clean up.""" - next_button = Gtk.Button(label=get_text('Next')) + next_button: Gtk.Button = Gtk.Button(label=get_text('Next')) """This button is used to go to the next page.""" - _box = None + _box: Gtk.Box | None = None @classmethod - def update_button_labels(cls): + def update_button_labels(cls) -> None: """Update button labels with current language translations.""" cls.back_button.set_label(get_text('Back')) cls.cancel_button.set_label(get_text('Cancel')) cls.next_button.set_label(get_text('Next')) @classmethod - def hide_all(cls): + def hide_all(cls) -> None: """ This method hides all buttons. """ @@ -31,7 +46,7 @@ class Button: cls.next_button.hide() @classmethod - def show_initial(cls): + def show_initial(cls) -> None: """ This method shows the initial buttons. Cancel and Next. """ @@ -40,21 +55,21 @@ class Button: cls.next_button.show() @classmethod - def show_back(cls): + def show_back(cls) -> None: """ This method shows the back button. """ cls.back_button.show() @classmethod - def hide_back(cls): + def hide_back(cls) -> None: """ This method hides the back button. """ cls.back_button.hide() @classmethod - def box(cls): + def box(cls) -> Gtk.Box: """ This method creates a box container of buttons aligned to the right. @@ -80,18 +95,26 @@ class Button: class Interface: + """ + Main interface controller for the installation wizard. + + Manages the GTK Notebook pages and navigation between different + screens in the installation process including language, keyboard, + network setup, installation type, and configuration screens. + """ welcome = None keyboard = None network_setup = None - try_isntall = None + try_install = None installation_type = None custom_partition = None full_zfs = None boot_manager = None - page = Gtk.Notebook() + page: Gtk.Notebook = Gtk.Notebook() + nbButton: Gtk.Notebook | None = None @classmethod - def get_interface(cls): + def get_interface(cls) -> Gtk.Box: interface_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0) interface_box.show() interface_box.pack_start(cls.page, True, True, 0) @@ -118,13 +141,13 @@ class Interface: return interface_box @classmethod - def delete(cls, _widget, _event=None): + def delete(cls, _widget: Gtk.Widget, _event=None) -> None: """Close the main window.""" InstallationData.reset() Gtk.main_quit() @classmethod - def next_page(cls, _widget): + def next_page(cls, _widget: Gtk.Button) -> None: """Go to the next window.""" page = cls.page.get_current_page() if page == 0: @@ -155,22 +178,39 @@ class Interface: if cls.page.get_n_pages() <= 3: try_install_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0) try_install_box.show() - get_try_install = cls.try_isntall.get_model() + get_try_install = cls.try_install.get_model() try_install_box.pack_start(get_try_install, True, True, 0) label = Gtk.Label(label=get_text("Try Or Install GhostBSD")) cls.page.insert_page(try_install_box, label, 3) cls.page.next_page() cls.page.show_all() elif page == 3: - if cls.page.get_n_pages() <= 4: - type_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0) - type_box.show() - get_types = cls.installation_type.get_model() - type_box.pack_start(get_types, True, True, 0) - label = Gtk.Label(label=get_text("Installation Types")) - cls.page.insert_page(type_box, label, 4) - cls.page.next_page() - cls.page.show_all() + if cls.try_install.get_what() == 'install': + if cls.page.get_n_pages() <= 4: + type_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0) + type_box.show() + get_types = cls.installation_type.get_model() + type_box.pack_start(get_types, True, True, 0) + label = Gtk.Label(label=get_text("Installation Types")) + cls.page.insert_page(type_box, label, 4) + cls.page.next_page() + cls.page.show_all() + else: + # Apply localization and keyboard layout for live session + # Apply system localization if language was selected + if InstallationData.language_code: + localize_system(InstallationData.language_code) + + # Apply keyboard layout if selected + if InstallationData.keyboard_layout_code: + set_keyboard( + InstallationData.keyboard_layout_code, + InstallationData.keyboard_variant, + InstallationData.keyboard_model_code + ) + + # Continue to network setup for live session + cls.next_setup_page() elif page == 4: Button.show_back() if InstallationData.filesystem_type == "custom": @@ -226,7 +266,7 @@ class Interface: Window.set_title(title_text) @classmethod - def next_setup_page(cls): + def next_setup_page(cls) -> None: page = cls.page.get_current_page() if page == 0: Button.next_button.show() @@ -248,7 +288,7 @@ class Interface: Gtk.main_quit() @classmethod - def back_page(cls, _widget): + def back_page(cls, _widget: Gtk.Button) -> None: """Go back to the previous window.""" current_page = cls.page.get_current_page() if current_page == 1: diff --git a/install_station/keyboard.py b/install_station/keyboard.py index e70bb8b..e865b70 100644 --- a/install_station/keyboard.py +++ b/install_station/keyboard.py @@ -32,10 +32,15 @@ styleContext.add_provider_for_screen( ) -# This class is for placeholder for entry. class PlaceHolderEntry(Gtk.Entry): + """ + GTK Entry widget with placeholder text functionality. + + This class extends Gtk.Entry to provide placeholder text that disappears + when the widget gains focus and returns when focus is lost if empty. + """ - def __init__(self, *args, **kwds): + def __init__(self, *args, **kwds) -> None: Gtk.Entry.__init__(self, *args, **kwds) self.placeholder = get_text('Type here to test your keyboard') self.set_text(self.placeholder) @@ -43,18 +48,18 @@ class PlaceHolderEntry(Gtk.Entry): self.connect('focus-in-event', self._focus_in_event) self.connect('focus-out-event', self._focus_out_event) - def _focus_in_event(self, _widget, _event): + def _focus_in_event(self, _widget: Gtk.Widget, _event) -> None: if self._default: self.set_text('') - def _focus_out_event(self, _widget, _event): + def _focus_out_event(self, _widget: Gtk.Widget, _event) -> None: if Gtk.Entry.get_text(self) == '': self.set_text(self.placeholder) self._default = True else: self._default = False - def get_text(self): + def get_text(self) -> str: if self._default: return '' return Gtk.Entry.get_text(self) @@ -74,15 +79,15 @@ class Keyboard: designed to integrate with the Interface controller for navigation flow. """ # Class variables instead of instance variables - kb_layout = None - kb_variant = None - kb_model = None - vbox1 = None - treeView = None - test_entry = None + kb_layout: str | None = None + kb_variant: str | None = None + kb_model: str | None = None + vbox1: Gtk.Box | None = None + treeView: Gtk.TreeView | None = None + test_entry: PlaceHolderEntry | None = None @classmethod - def layout_columns(cls, treeview): + def layout_columns(cls, treeview: Gtk.TreeView) -> None: """ Configure the keyboard layout treeview with appropriate columns. @@ -102,7 +107,7 @@ class Keyboard: treeview.append_column(column) @classmethod - def variant_columns(cls, treeview): + def variant_columns(cls, treeview: Gtk.TreeView) -> None: """ Configure the keyboard model treeview with appropriate columns. @@ -122,7 +127,7 @@ class Keyboard: treeview.append_column(column) @classmethod - def layout_selection(cls, tree_selection): + def layout_selection(cls, tree_selection: Gtk.TreeSelection) -> None: """ Handle keyboard layout selection from the treeview. @@ -147,7 +152,7 @@ class Keyboard: print(f"Keyboard layout selected: {value} ({cls.kb_layout}/{cls.kb_variant})") @classmethod - def model_selection(cls, tree_selection): + def model_selection(cls, tree_selection: Gtk.TreeSelection) -> None: """ Handle keyboard model selection from the treeview. @@ -170,7 +175,7 @@ class Keyboard: print(f"Keyboard model selected: {value} ({cls.kb_model})") @classmethod - def save_selection(cls): + def save_selection(cls) -> None: """ Save the current keyboard selection. @@ -186,7 +191,7 @@ class Keyboard: file.write(f"{cls.kb_model}\\n") @classmethod - def save_keyboard(cls): + def save_keyboard(cls) -> None: """ Apply the keyboard configuration to the system. @@ -197,7 +202,7 @@ class Keyboard: set_keyboard(cls.kb_layout, cls.kb_variant, cls.kb_model) @classmethod - def initialize(cls): + def initialize(cls) -> None: """ Initialize the keyboard configuration UI following the utility class pattern. @@ -278,7 +283,7 @@ class Keyboard: cls.treeView.set_cursor(0) @classmethod - def get_model(cls): + def get_model(cls) -> Gtk.Box: """ Return the GTK widget model for the keyboard configuration interface. @@ -292,7 +297,7 @@ class Keyboard: return cls.vbox1 @classmethod - def get_keyboard_info(cls): + def get_keyboard_info(cls) -> dict[str, str | None]: """ Get the current keyboard configuration information. diff --git a/install_station/language.py b/install_station/language.py index 2ff2e3f..c834b6a 100644 --- a/install_station/language.py +++ b/install_station/language.py @@ -40,14 +40,14 @@ class Language: designed to integrate with the Interface controller for navigation flow. """ # Class variables instead of instance variables - vbox1 = None - language = None - treeview = None - welcome_text = None - language_column_header = None + vbox1: Gtk.Box | None = None + language: str | None = None + treeview: Gtk.TreeView | None = None + welcome_text: Gtk.Label | None = None + language_column_header: Gtk.Label | None = None @classmethod - def language_selection(cls, tree_selection): + def language_selection(cls, tree_selection: Gtk.TreeSelection) -> None: """ Handle language selection from the treeview. @@ -77,7 +77,7 @@ class Language: cls.update_ui_text() @classmethod - def update_ui_text(cls): + def update_ui_text(cls) -> None: """ Update all UI text elements with new translations after language change. """ @@ -101,7 +101,7 @@ class Language: Window.set_title(get_text("Welcome to GhostBSD")) @classmethod - def setup_language_columns(cls, treeview): + def setup_language_columns(cls, treeview: Gtk.TreeView) -> None: """ Configure the language selection treeview with appropriate columns. @@ -123,7 +123,7 @@ class Language: treeview.append_column(column) @classmethod - def save_selection(cls): + def save_selection(cls) -> None: """ Save the current language selection. @@ -134,7 +134,7 @@ class Language: pass @classmethod - def save_language(cls): + def save_language(cls) -> None: """ Apply the language configuration to the system. @@ -146,7 +146,7 @@ class Language: localize_system(language_code) @classmethod - def initialize(cls): + def initialize(cls) -> None: """ Initialize the language selection UI following the utility class pattern. @@ -225,7 +225,7 @@ class Language: main_grid.show() @classmethod - def get_model(cls): + def get_model(cls) -> Gtk.Box: """ Return the GTK widget model for the language selection interface. @@ -239,7 +239,7 @@ class Language: return cls.vbox1 @classmethod - def get_language(cls): + def get_language(cls) -> str | None: """ Get the selected language code. @@ -249,7 +249,7 @@ class Language: return InstallationData.language_code or cls.language @classmethod - def get_language_info(cls): + def get_language_info(cls) -> dict[str, str]: """ Get the current language configuration information. diff --git a/install_station/network_setup.py b/install_station/network_setup.py index 6f61d46..ded8d84 100644 --- a/install_station/network_setup.py +++ b/install_station/network_setup.py @@ -39,19 +39,19 @@ class NetworkSetup: designed to integrate with the Interface controller for navigation flow. """ # Class variables instead of instance variables - vbox1 = None - network_info = None - wire_connection_label = None - wire_connection_image = None - wifi_connection_label = None - wifi_connection_image = None - connection_box = None - store = None - window = None - password = None + vbox1: Gtk.Box | None = None + network_info: dict | None = None + wire_connection_label: Gtk.Label | None = None + wire_connection_image: Gtk.Image | None = None + wifi_connection_label: Gtk.Label | None = None + wifi_connection_image: Gtk.Image | None = None + connection_box: Gtk.Box | None = None + store: Gtk.ListStore | None = None + window: Gtk.Window | None = None + password: Gtk.Entry | None = None @classmethod - def get_model(cls): + def get_model(cls) -> Gtk.Box: """ Return the GTK widget model for the network setup interface. @@ -65,7 +65,7 @@ class NetworkSetup: return cls.vbox1 @staticmethod - def wifi_stat(bar): + def wifi_stat(bar: int) -> str: """ Get WiFi signal strength icon name based on signal bar percentage. @@ -87,7 +87,7 @@ class NetworkSetup: return 'nm-signal-00' @classmethod - def update_network_detection(cls): + def update_network_detection(cls) -> None: """ Update network detection status and UI elements. @@ -135,7 +135,7 @@ class NetworkSetup: cls.wifi_connection_label.set_label(wifi_text) @classmethod - def update_ui_text(cls): + def update_ui_text(cls) -> None: """ Update all UI text elements with new translations after language change. """ @@ -147,7 +147,7 @@ class NetworkSetup: cls.update_network_detection() @classmethod - def initialize(cls): + def initialize(cls) -> None: """ Initialize the network setup UI following the utility class pattern. @@ -257,7 +257,7 @@ class NetworkSetup: main_grid.attach(cls.connection_box, 1, 4, 10, 5) @classmethod - def wifi_setup(cls, tree_selection, wifi_card): + def wifi_setup(cls, tree_selection: Gtk.TreeSelection, wifi_card: str) -> None: """ Handle WiFi access point selection and connection setup. @@ -285,7 +285,7 @@ class NetworkSetup: cls.authentication(ssid_info, wifi_card, False) @classmethod - def add_to_wpa_supplicant(cls, _widget, ssid_info, card): + def add_to_wpa_supplicant(cls, _widget: Gtk.Button, ssid_info: list, card: str) -> None: """ Add WiFi credentials to wpa_supplicant configuration and connect. @@ -303,7 +303,7 @@ class NetworkSetup: cls.window.hide() @classmethod - def try_to_connect_to_ssid(cls, ssid, ssid_info, card): + def try_to_connect_to_ssid(cls, ssid: str, ssid_info: list, card: str) -> None: """ Attempt to connect to the specified WiFi network. @@ -329,7 +329,7 @@ class NetworkSetup: return @classmethod - def restart_authentication(cls, ssid_info, card): + def restart_authentication(cls, ssid_info: list, card: str) -> None: """ Restart WiFi authentication after a failed connection attempt. @@ -340,7 +340,7 @@ class NetworkSetup: cls.authentication(ssid_info, card, True) @classmethod - def on_check(cls, widget): + def on_check(cls, widget: Gtk.CheckButton) -> None: """ Toggle password visibility in authentication dialog. @@ -353,7 +353,7 @@ class NetworkSetup: cls.password.set_visibility(False) @classmethod - def authentication(cls, ssid_info, card, failed): + def authentication(cls, ssid_info: list, card: str, failed: bool) -> str: """ Show WiFi authentication dialog. @@ -411,7 +411,7 @@ class NetworkSetup: return 'Done' @classmethod - def close(cls, _widget): + def close(cls, _widget: Gtk.Button) -> None: """ Close the authentication dialog. @@ -421,7 +421,7 @@ class NetworkSetup: cls.window.hide() @staticmethod - def setup_wpa_supplicant(ssid, ssid_info, pwd): + def setup_wpa_supplicant(ssid: str, ssid_info: list, pwd: str) -> None: """ Setup wpa_supplicant configuration for WiFi network. @@ -456,7 +456,7 @@ class NetworkSetup: wsf.close() @staticmethod - def open_wpa_supplicant(ssid): + def open_wpa_supplicant(ssid: str) -> None: """ Add open network entry to wpa_supplicant configuration. diff --git a/install_station/partition.py b/install_station/partition.py index a03591a..0ac9cc8 100644 --- a/install_station/partition.py +++ b/install_station/partition.py @@ -11,7 +11,7 @@ from install_station.data import query, zfs_datasets, InstallationData # Define required file paths -def get_disk_from_partition(part): +def get_disk_from_partition(part: str) -> str: """Extract the disk name from a partition identifier. Args: @@ -26,7 +26,7 @@ def get_disk_from_partition(part): return part.partition('s')[0] -def slice_number(part): +def slice_number(part: str) -> int: """Extract the slice/partition number from a partition identifier. Args: @@ -41,7 +41,7 @@ def slice_number(part): return int(part.partition('s')[2]) -def find_next_partition(partition_name, partition_list): +def find_next_partition(partition_name: str, partition_list: list[str]) -> str: """Find the next available partition name with sequential numbering. Args: @@ -56,7 +56,7 @@ def find_next_partition(partition_name, partition_list): return f'{partition_name}{num}' -def disk_list(): +def disk_list() -> list[str]: """Get a list of available disk devices on the system. Queries the FreeBSD kernel for available disks and filters out @@ -78,7 +78,7 @@ def disk_list(): return sorted(cleaned_disk.split()) -def device_model(disk): +def device_model(disk: str) -> str: """Get the model description of a disk device. Args: @@ -98,7 +98,7 @@ def device_model(disk): return device_popen.stdout.read().strip() -def disk_size(disk): +def disk_size(disk: str) -> str: """Get the size of a disk device. Args: @@ -119,7 +119,7 @@ def disk_size(disk): return disk_size_output.stdout.readlines()[0].rstrip() -def get_scheme(disk): +def get_scheme(disk: str) -> str: """Detect the partition scheme of a disk device. Args: @@ -1206,7 +1206,7 @@ class CreatePartition(): InstallationData.new_partition = new_partitions -def delete_partition(): +def delete_partition() -> None: """Execute physical deletion of partitions marked for deletion. Iterates through partitions marked for deletion in InstallationData @@ -1227,7 +1227,7 @@ def delete_partition(): raise RuntimeError('No partitions to delete') -def destroy_partition(): +def destroy_partition() -> None: """Destroy and recreate partition tables on disks. Completely destroys existing partition tables and creates new ones @@ -1251,7 +1251,7 @@ def destroy_partition(): raise RuntimeError('No disks to destroy') -def bios_or_uefi(): +def bios_or_uefi() -> str: """Detect the system boot method (BIOS or UEFI). Returns: @@ -1263,7 +1263,7 @@ def bios_or_uefi(): return output1.stdout.readlines()[0].rstrip() -def add_partition(): +def add_partition() -> None: """Execute physical creation of partitions marked for creation. Creates actual partitions on disk using FreeBSD gpart commands diff --git a/install_station/system_calls.py b/install_station/system_calls.py index db3a009..93343a6 100644 --- a/install_station/system_calls.py +++ b/install_station/system_calls.py @@ -6,7 +6,14 @@ from subprocess import Popen, run, PIPE from install_station.data import pc_sysinstall -def replace_pattern(current, new, file): +def replace_pattern(current: str, new: str, file: str) -> None: + """Replace text patterns in a file using regex substitution. + + Args: + current: Regular expression pattern to search for + new: Replacement text + file: Path to file to modify + """ parser_file = open(file, 'r').read() parser_patched = re.sub(current, new, parser_file) save_parser_file = open(file, 'w') @@ -14,7 +21,12 @@ def replace_pattern(current, new, file): save_parser_file.close() -def language_dictionary(): +def language_dictionary() -> dict[str, str]: + """Get available system languages from pc-sysinstall. + + Returns: + Dictionary mapping language names to language codes + """ langs = Popen(f'{pc_sysinstall} query-langs', shell=True, stdin=PIPE, stdout=PIPE, universal_newlines=True, close_fds=True).stdout.readlines() @@ -27,7 +39,15 @@ def language_dictionary(): return dictionary -def localize_system(locale): +def localize_system(locale: str) -> None: + """Apply localization settings to the system. + + Updates login.conf, profile files, and greeter configurations + with the specified locale. + + Args: + locale: Language code (e.g. 'en_US', 'fr_FR') + """ slick_greeter = "/usr/local/share/xgreeters/slick-greeter.desktop" gtk_greeter = "/usr/local/share/xgreeters/lightdm-gtk-greeter.desktop" replace_pattern('lang=C', f'lang={locale}', '/etc/login.conf') @@ -48,7 +68,12 @@ def localize_system(locale): ) -def keyboard_dictionary(): +def keyboard_dictionary() -> dict[str, dict[str, str | None]]: + """Get available keyboard layouts and variants from pc-sysinstall. + + Returns: + Dictionary mapping keyboard layout names to layout/variant dictionaries + """ xkeyboard_layouts = Popen(f'{pc_sysinstall} xkeyboard-layouts', shell=True, stdout=PIPE, universal_newlines=True).stdout.readlines() @@ -76,7 +101,12 @@ def keyboard_dictionary(): return dictionary -def keyboard_models(): +def keyboard_models() -> dict[str, str]: + """Get available keyboard models from pc-sysinstall. + + Returns: + Dictionary mapping keyboard model names to model codes + """ xkeyboard_models = Popen(f'{pc_sysinstall} xkeyboard-models', shell=True, stdout=PIPE, universal_newlines=True).stdout.readlines() @@ -88,7 +118,14 @@ def keyboard_models(): return dictionary -def change_keyboard(kb_layout, kb_variant=None, kb_model=None): +def change_keyboard(kb_layout: str, kb_variant: str | None = None, kb_model: str | None = None) -> None: + """Apply keyboard layout change immediately using setxkbmap. + + Args: + kb_layout: Keyboard layout code + kb_variant: Optional keyboard variant code + kb_model: Optional keyboard model code + """ if kb_variant is None and kb_model is not None: run(f"setxkbmap -layout {kb_layout} -model {kb_model}", shell=True) elif kb_variant is not None and kb_model is None: @@ -101,11 +138,104 @@ def change_keyboard(kb_layout, kb_variant=None, kb_model=None): run(f"setxkbmap -layout {kb_layout}", shell=True) -def set_keyboard(kb_layout, kb_variant=None, kb_model=None): - pass +def set_keyboard(kb_layout: str, kb_variant: str | None = None, kb_model: str | None = None) -> None: + """ + Permanently configure keyboard layout for the live system. + Based on pc-sysinstall's localize_x_keyboard function. + """ + setxkbmap_cmd = "" + + # Build setxkbmap command + if kb_model and kb_model != "NONE": + setxkbmap_cmd = f"-model {kb_model}" + kx_model = kb_model + else: + kx_model = "pc104" + + if kb_layout and kb_layout != "NONE": + setxkbmap_cmd = f"{setxkbmap_cmd} -layout {kb_layout}".strip() + kx_layout = kb_layout + else: + kx_layout = "us" + + if kb_variant and kb_variant != "NONE": + setxkbmap_cmd = f"{setxkbmap_cmd} -variant {kb_variant}" + + # Apply the keyboard layout immediately + if setxkbmap_cmd: + run(f"setxkbmap {setxkbmap_cmd}", shell=True) + + # Create .xprofile for persistent keyboard layout + xprofile_path = "/home/ghostbsd/.xprofile" + try: + # Read existing .xprofile or create new one + if os.path.exists(xprofile_path): + with open(xprofile_path, 'r') as f: + content = f.read() + # Remove existing setxkbmap lines + lines = [line for line in content.splitlines() if not line.strip().startswith('setxkbmap')] + else: + lines = ["#!/bin/sh"] + + # Add new setxkbmap command + lines.append(f"setxkbmap {setxkbmap_cmd}") + + # Write back to .xprofile + with open(xprofile_path, 'w') as f: + f.write('\n'.join(lines) + '\n') + + # Make executable + os.chmod(xprofile_path, 0o755) + + except (OSError, IOError) as e: + print(f"Warning: Could not update .xprofile: {e}") + + # Set console keymap in rc.conf for live system persistence + try: + _set_console_keymap(kx_layout) + except (OSError, IOError) as e: + print(f"Warning: Could not update console keymap: {e}") -def timezone_dictionary(): +def _set_console_keymap(key_layout: str) -> None: + """Helper function to set console keymap in rc.conf""" + # Map X11 layouts to console keymaps (from pc-sysinstall) + keymap_mapping = { + 'ca': 'ca-fr.kbd', + 'et': 'ee.kbd', + 'es': 'es.acc.kbd', + 'gb': 'uk.kbd' + } + + console_keymap = keymap_mapping.get(key_layout, f"{key_layout}.kbd") + + rc_conf_path = "/etc/rc.conf" + keymap_line = f'keymap="{console_keymap}"\n' + + # Check if keymap already exists in rc.conf + if os.path.exists(rc_conf_path): + with open(rc_conf_path, 'r') as f: + lines = f.readlines() + + # Remove existing keymap lines + lines = [line for line in lines if not line.strip().startswith('keymap=')] + + # Add new keymap + lines.append(keymap_line) + + with open(rc_conf_path, 'w') as f: + f.writelines(lines) + else: + with open(rc_conf_path, 'w') as f: + f.write(keymap_line) + + +def timezone_dictionary() -> dict[str, list[str]]: + """Get available timezones from pc-sysinstall. + + Returns: + Dictionary mapping continents to lists of cities/regions + """ tz_list = Popen(f'{pc_sysinstall} list-tzones', shell=True, stdout=PIPE, universal_newlines=True).stdout.readlines() city_list = [] @@ -128,7 +258,12 @@ def timezone_dictionary(): return dictionary -def zfs_disk_query(): +def zfs_disk_query() -> list[str]: + """Query available disks for ZFS installation. + + Returns: + List of available disk device names + """ disk_output = Popen( f"{pc_sysinstall} disk-list", shell=True, @@ -140,7 +275,15 @@ def zfs_disk_query(): return disk_output.stdout.readlines() -def zfs_disk_size_query(disk): +def zfs_disk_size_query(disk: str) -> str: + """Query disk size information. + + Args: + disk: Disk device name + + Returns: + Disk size information string + """ disk_info_output = Popen( f"{pc_sysinstall} disk-info {disk}", shell=True, @@ -152,7 +295,17 @@ def zfs_disk_size_query(disk): return disk_info_output.stdout.readlines()[3].partition('=')[2] -def set_admin_user(username, name, password, shell, homedir, hostname): +def set_admin_user(username: str, name: str, password: str, shell: str, homedir: str, hostname: str) -> None: + """Set up administrator user and system hostname. + + Args: + username: Username for the admin user + name: Full name for the admin user + password: Password for the admin user + shell: Default shell for the admin user + homedir: Home directory path for the admin user + hostname: System hostname to set + """ # Set Root user run(f"echo '{password}' | pw usermod -n root -h 0", shell=True) cmd = f"echo '{password}' | pw useradd {username} -c {name} -h 0" \ diff --git a/install_station/try_install.py b/install_station/try_install.py index 8b08c71..48aaa9e 100644 --- a/install_station/try_install.py +++ b/install_station/try_install.py @@ -27,14 +27,14 @@ class TryOrInstall: designed to integrate with the Interface controller for navigation flow. """ # Class variables instead of instance variables - what = None - install_button = None - try_button = None - instruction_label = None - vbox1 = None + what: str | None = None + install_button: Gtk.RadioButton | None = None + try_button: Gtk.RadioButton | None = None + instruction_label: Gtk.Label | None = None + vbox1: Gtk.Box | None = None @classmethod - def mode_selection(cls, widget, val): + def mode_selection(cls, widget: Gtk.RadioButton, val: str) -> None: """ Handle mode selection from radio buttons. @@ -52,7 +52,7 @@ class TryOrInstall: print(f"Mode selected: {val}") @classmethod - def get_what(cls): + def get_what(cls) -> str | None: """ Get the current installation mode. @@ -65,7 +65,7 @@ class TryOrInstall: return InstallationData.install_mode or cls.what @classmethod - def initialize(cls): + def initialize(cls) -> None: """ Initialize the welcome screen UI following the utility class pattern. @@ -147,7 +147,7 @@ class TryOrInstall: cls.install_button.set_active(True) @classmethod - def get_model(cls): + def get_model(cls) -> Gtk.Box: """ Return the GTK widget model for the welcome screen interface. diff --git a/install_station/window.py b/install_station/window.py index 3c6c097..bd3b841 100644 --- a/install_station/window.py +++ b/install_station/window.py @@ -1,49 +1,103 @@ +""" +Window Module. + +This module provides a singleton wrapper around GTK Window to provide +a consistent interface for the main application window. +""" import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk class Window: - window = Gtk.Window() + """ + Singleton wrapper for GTK Window. + + Provides a class-based interface to a single GTK Window instance + that can be accessed throughout the application. + """ + window: Gtk.Window = Gtk.Window() @classmethod - def connect(cls, signal, callback): + def connect(cls, signal: str, callback) -> int: + """Connect a signal handler to the window. + + Args: + signal: Signal name to connect to + callback: Callback function to invoke + + Returns: + Connection ID + """ return cls.window.connect(signal, callback) @classmethod - def set_border_width(cls, width): + def set_border_width(cls, width: int) -> None: + """Set the border width of the window. + + Args: + width: Border width in pixels + """ return cls.window.set_border_width(width) @classmethod - def set_default_size(cls, width, height): + def set_default_size(cls, width: int, height: int) -> None: + """Set the default size of the window. + + Args: + width: Default width in pixels + height: Default height in pixels + """ return cls.window.set_default_size(width, height) @classmethod - def set_size_request(cls, width, height): + def set_size_request(cls, width: int, height: int) -> None: + """Set the size request of the window. + + Args: + width: Requested width in pixels + height: Requested height in pixels + """ return cls.window.set_size_request(width, height) @classmethod - def set_title(cls, title): + def set_title(cls, title: str) -> None: + """Set the window title. + + Args: + title: Window title text + """ return cls.window.set_title(title) @classmethod - def set_icon_from_file(cls, filename): + def set_icon_from_file(cls, filename: str) -> None: + """Set the window icon from a file. + + Args: + filename: Path to icon file + """ return cls.window.set_icon_from_file(filename) @classmethod - def add(cls, widget): + def add(cls, widget: Gtk.Widget) -> None: + """Add a widget to the window. + + Args: + widget: Widget to add to the window + """ return cls.window.add(widget) @classmethod - def show_all(cls): + def show_all(cls) -> None: + """Show the window and all its children.""" return cls.window.show_all() @classmethod - def hide(cls): + def hide(cls) -> None: """Hide the window.""" return cls.window.hide() @classmethod - def __getattr__(cls, name): + def __getattr__(cls, name: str): """Fallback for any methods not explicitly defined.""" return getattr(cls.window, name) diff --git a/setup.py b/setup.py index 89cb861..d5340c2 100755 --- a/setup.py +++ b/setup.py @@ -1,7 +1,14 @@ #!/usr/bin/env python +""" +Setup script for Install Station. + +Install Station is a streamlined installer for GhostBSD, providing +a GTK+ interface for disk partitioning and OS installation. +""" import os import sys -from setuptools import setup, Command, glob +from setuptools import setup, Command +import glob from DistUtilsExtra.command.build_extra import build_extra from DistUtilsExtra.command.build_i18n import build_i18n from DistUtilsExtra.command.clean_i18n import clean_i18n @@ -12,6 +19,16 @@ PROGRAM_VERSION = __VERSION__ def data_file_list(install_base, source_base): + """ + Generate list of data files for installation. + + Args: + install_base: Base installation path + source_base: Source directory to scan + + Returns: + List of (install_path, files) tuples for setuptools + """ data = [] for root, subFolders, files in os.walk(source_base): file_list = [] @@ -114,7 +131,7 @@ lib_install_station_image = [ lib_install_station_backend_query = [ 'src/backend-query/detect-laptop.sh', 'src/backend-query/detect-nics.sh', - 'src/backend-query/detect-sheme.sh', + 'src/backend-query/detect-scheme.sh', 'src/backend-query/detect-vmware.sh', 'src/backend-query/detect-wifi.sh', 'src/backend-query/disk-info.sh', @@ -148,7 +165,7 @@ data_files.extend(data_file_list(f'{prefix}/share/locale', 'build/mo')) setup( name="install-station", version=PROGRAM_VERSION, - description="Install Station is a strip down version of gbi", + description="Install Station - Streamlined GhostBSD installer", license='BSD', author='Eric Turgeon', url='https://github/GhostBSD/install-station/',