Add type annotations and docstrings across modules

- Introduced type annotations for functions, methods, and class variables
- Added docstrings to improve code clarity and maintainability
- Improved consistency in function signatures and replaced ambiguous types with specific ones
- Updated `setup.py` and key modules with better descriptions and format adjustments
This commit is contained in:
ericbsd
2025-09-12 19:12:44 -03:00
parent 391904b744
commit e8a179495c
14 changed files with 521 additions and 159 deletions
+15 -3
View File
@@ -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
+14
View File
@@ -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"
+33 -8
View File
@@ -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(
+10 -10
View File
@@ -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.
+49 -7
View File
@@ -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)
+67 -27
View File
@@ -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:
+25 -20
View File
@@ -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.
+14 -14
View File
@@ -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.
+24 -24
View File
@@ -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.
+11 -11
View File
@@ -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
+165 -12
View File
@@ -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" \
+9 -9
View File
@@ -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.
+65 -11
View File
@@ -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)
+20 -3
View File
@@ -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/',