Add initial install_station Python package structure

- Add install_station package with __init__.py and core modules
  - Include boot_manager.py for boot loader configuration (BootManager class with singleton pattern)
  - Add common.py with password strength validation utilities and ZFS dataset definitions
  - Add custom.py with Partitions class for disk partitioning management (1010 lines)
  - Establish foundation for GTK+ based GhostBSD installer application

  Modules added:
  - boot_manager: UEFI/BIOS boot manager selection with rEFInd and FreeBSD options
  - common: Password validation functions and deprecated decorator utility
  - custom: Comprehensive partition management with GTK+ interface
This commit is contained in:
ericbsd
2025-07-09 21:27:46 -03:00
parent 8a02068d10
commit 9db6c513c9
95 changed files with 19345 additions and 268 deletions
View File
+218
View File
@@ -0,0 +1,218 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
from install_station.partition import bios_or_uefi
from install_station.data import InstallationData
import gettext
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('/usr/local/lib/install-station/ghostbsd-style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
screen,
cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
class BootManager:
"""
Utility class for managing boot manager selection in GhostBSD installation following the utility class pattern.
This class provides a GTK+ interface for selecting and configuring boot managers
including rEFInd, FreeBSD boot manager, and native UEFI/BIOS loaders. The class
automatically determines available options based on the partition scheme (GPT/MBR)
and firmware type (UEFI/BIOS).
Available boot manager options:
- rEFInd: Available only for GPT + UEFI configurations
- FreeBSD boot manager: Available only for MBR partition schemes
- Native loader: Default option, always available (UEFI or BIOS loader)
The class follows a utility pattern with class methods and variables for state management,
designed to integrate with the InstallationData system for configuration persistence.
"""
# Class variables for state management
boot = None
vbox1 = None
# UI elements as class variables
refind = None
bsd = None
none = None
box3 = None
@classmethod
def get_model(cls):
"""
Return the GTK widget model for the boot manager interface.
Creates and initializes the UI if it doesn't exist yet.
Returns:
Gtk.Box: The main container widget for the boot manager interface
"""
if cls.vbox1 is None:
cls.initialize()
return cls.vbox1
@classmethod
def boot_manager_selection(cls, _radiobutton, val: str):
"""
Handle boot manager selection from radio buttons.
Called when a radio button is toggled to update the selected boot manager
option and store it in both the class variable and InstallationData.
Args:
_radiobutton: The radio button widget that was toggled (unused)
val: The boot manager value ('refind', 'bsd', or 'none')
"""
cls.boot = val
InstallationData.boot = cls.boot
@classmethod
def get_boot_manager_option(cls):
"""
Get the currently selected boot manager option.
Returns the boot manager value from InstallationData if available,
otherwise falls back to the class variable.
Returns:
str: The selected boot manager ('refind', 'bsd', or 'none')
"""
return InstallationData.boot or cls.boot
@classmethod
def initialize(cls):
"""
Initialize the boot manager user interface following the utility class pattern.
Creates the GTK+ interface for boot manager selection including:
- Title header ("Boot Option")
- Radio button group for boot manager options
- Automatic option availability based on partition scheme and firmware type
- Default selection of native loader option
The interface adapts based on:
- Firmware type (UEFI/BIOS) detected from system
- Partition scheme (GPT/MBR) from installation configuration
- rEFInd: Only available for GPT + UEFI
- FreeBSD boot manager: Only available for MBR
- Native loader: Always available as default
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()
# Determine firmware type
if bios_or_uefi() == "UEFI":
loader = "UEFI"
else:
loader = "BIOS"
# Get partition scheme from InstallationData
scheme = cls._get_partition_scheme()
# Create title header
title = Gtk.Label(label=_('Boot Option'), name="Header")
title.set_property("height-request", 50)
cls.vbox1.pack_start(title, False, False, 0)
# Create main horizontal container
hbox1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=0)
hbox1.show()
cls.vbox1.pack_start(hbox1, True, True, 10)
# Create vertical box for radio buttons
bbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
bbox1.show()
# rEFInd boot manager option
cls.refind = Gtk.RadioButton(label=_("Setup rEFInd boot manager"))
bbox1.pack_start(cls.refind, False, True, 10)
cls.refind.connect("toggled", cls.boot_manager_selection, "refind")
cls.refind.show()
# Enable rEFInd only for GPT + UEFI
if scheme == 'GPT' and loader == "UEFI":
cls.refind.set_sensitive(True)
else:
cls.refind.set_sensitive(False)
# FreeBSD boot manager option
cls.bsd = Gtk.RadioButton.new_with_label_from_widget(
cls.refind,
_("Setup FreeBSD boot manager")
)
bbox1.pack_start(cls.bsd, False, True, 10)
cls.bsd.connect("toggled", cls.boot_manager_selection, "bsd")
cls.bsd.show()
# Enable FreeBSD boot manager only for MBR
if scheme == 'MBR':
cls.bsd.set_sensitive(True)
else:
cls.bsd.set_sensitive(False)
# Native loader option (always available)
cls.none = Gtk.RadioButton.new_with_label_from_widget(
cls.bsd,
_("FreeBSD {loader} loader only").format(loader=loader)
)
bbox1.pack_start(cls.none, False, True, 10)
cls.none.connect("toggled", cls.boot_manager_selection, "none")
cls.none.show()
# Add radio button container to main layout
hbox1.pack_start(bbox1, False, False, 50)
# Set default selection
cls.none.set_active(True)
cls.boot = "none"
InstallationData.boot = cls.boot
# Create additional container for future expansion
cls.box3 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
cls.box3.set_border_width(0)
cls.vbox1.pack_start(cls.box3, True, True, 0)
@classmethod
def _get_partition_scheme(cls):
"""
Determine the partition scheme from installation configuration data.
Checks ZFS and UFS configuration data, then falls back to InstallationData.scheme
or defaults to GPT if no scheme is found.
Returns:
str: The partition scheme ('GPT' or 'MBR')
"""
# Check ZFS config data for scheme
if InstallationData.zfs_config_data:
scheme_line = next((line for line in InstallationData.zfs_config_data if 'partscheme=' in line), '')
if scheme_line:
return scheme_line.split('=')[1].strip()
# Check UFS config data for scheme
if InstallationData.ufs_config_data:
scheme_line = next((line for line in InstallationData.ufs_config_data if 'partscheme=' in line), '')
if scheme_line:
return scheme_line.split('=')[1].strip()
# Use scheme from InstallationData or default to GPT
if InstallationData.scheme:
# Handle both 'partscheme=GPT' and 'GPT' formats
if 'partscheme=' in InstallationData.scheme:
return InstallationData.scheme.split('=')[1].strip()
else:
return InstallationData.scheme.strip()
return 'GPT' # Default fallback
+168
View File
@@ -0,0 +1,168 @@
import re
import warnings
def lower_case(text: str) -> bool:
"""
Find if password contain only lower case.
:param text: password
:return: True if password contain only lower case
"""
search = re.compile(r'[^a-z]').search
return not bool(search(text))
# Find if password contain only upper case
def upper_case(text: str) -> bool:
"""
Find if password contain only upper case.
:param text: password
:return: True if password contain only upper case
"""
search = re.compile(r'[^A-Z]').search
return not bool(search(text))
# Find if password contain only lower case and number
def lower_and_number(text: str) -> bool:
"""
Find if password contain only lower case and number.
:param text: password
:return: True if password contain only lower case and number
"""
search = re.compile(r'[^a-z0-9]').search
return not bool(search(text))
# Find if password contain only upper case and number
def upper_and_number(text: str) -> bool:
"""
Find if password contain only upper case and number.
:param text: password
:return: True if password contain only upper case and number
"""
search = re.compile(r'[^A-Z0-9]').search
return not bool(search(text))
# Find if password contain only lower and upper case and
def lower_upper(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
"""
search = re.compile(r'[^a-zA-Z]').search
return not bool(search(text))
# Find if password contain only lower and upper case and
def lower_upper_number(text) -> 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
"""
search = re.compile(r'[^a-zA-Z0-9]').search
return not bool(search(text))
# Find if password contain only lowercase, uppercase numbers
# and some special character.
def all_character(text):
"""
Find if password contain only lowercase, uppercase numbers
and some special character.
:param text: password
:return: True if password contain only lowercase, uppercase numbers
and some special character.
"""
search = re.compile(r'[^a-zA-Z0-9~!@#$%^&*_+":;\'-]').search
return not bool(search(text))
def password_strength(password, label3):
same_character_type = any(
[
lower_case(password),
upper_case(password),
password.isdigit()
]
)
mix_character = any(
[
lower_and_number(password),
upper_and_number(password),
lower_upper(password)
]
)
if ' ' in password or '\t' in password:
label3.set_text("Space not allowed")
elif len(password) <= 4:
label3.set_text("Super Weak")
elif len(password) <= 8 and same_character_type:
label3.set_text("Super Weak")
elif len(password) <= 8 and mix_character:
label3.set_text("Very Weak")
elif len(password) <= 8 and lower_upper_number(password):
label3.set_text("Fairly Weak")
elif len(password) <= 8 and all_character(password):
label3.set_text("Weak")
elif len(password) <= 12 and same_character_type:
label3.set_text("Very Weak")
elif len(password) <= 12 and mix_character:
label3.set_text("Fairly Weak")
elif len(password) <= 12 and lower_upper_number(password):
label3.set_text("Weak")
elif len(password) <= 12 and all_character(password):
label3.set_text("Strong")
elif len(password) <= 16 and same_character_type:
label3.set_text("Fairly Weak")
elif len(password) <= 16 and mix_character:
label3.set_text("Weak")
elif len(password) <= 16 and lower_upper_number(password):
label3.set_text("Strong")
elif len(password) <= 16 and all_character(password):
label3.set_text("Fairly Strong")
elif len(password) <= 20 and same_character_type:
label3.set_text("Weak")
elif len(password) <= 20 and mix_character:
label3.set_text("Strong")
elif len(password) <= 20 and lower_upper_number(password):
label3.set_text("Fairly Strong")
elif len(password) <= 20 and all_character(password):
label3.set_text("Very Strong")
elif len(password) <= 24 and same_character_type:
label3.set_text("Strong")
elif len(password) <= 24 and mix_character:
label3.set_text("Fairly Strong")
elif len(password) <= 24 and lower_upper_number(password):
label3.set_text("Very Strong")
elif len(password) <= 24 and all_character(password):
label3.set_text("Super Strong")
elif same_character_type:
label3.set_text("Fairly Strong")
else:
label3.set_text("Super Strong")
def deprecated(*, version: str, reason: str):
def decorator(func):
def wrapper(*args, **kwargs):
warnings.warn(
f"{func.__name__} is deprecated (version {version}): {reason}",
category=DeprecationWarning,
stacklevel=2
)
return func(*args, **kwargs)
wrapper.__name__ = func.__name__
wrapper.__doc__ = func.__doc__
return wrapper
return decorator
+167
View File
@@ -0,0 +1,167 @@
from install_station.data import InstallationData, installation_config
class Configuration:
"""
Utility class for creating and validating GhostBSD installation configuration files.
This class provides methods to generate the ghostbsd_installation.cfg file
used by the GhostBSD installer and validate all required installation data
before configuration file creation.
The class follows a utility pattern with class methods for stateless operations,
designed to integrate with the InstallationData system for configuration management.
"""
@classmethod
def sanity_check(cls):
"""
Perform sanity checks on all installation data used by create_cfg.
Validates that all required installation parameters are present and valid
before attempting to create the configuration file.
Returns:
tuple: (bool, list) - (is_valid, list_of_errors)
is_valid: True if all checks pass, False otherwise
list_of_errors: List of error messages describing validation failures
"""
errors = []
# Check basic installation data
if not hasattr(InstallationData, 'boot') or not InstallationData.boot:
errors.append("Boot manager not specified")
elif InstallationData.boot not in ['refind', 'grub', 'none']:
errors.append(f"Invalid boot manager: {InstallationData.boot}")
# Check ZFS configuration path
if InstallationData.zfs_config_data:
if not isinstance(InstallationData.zfs_config_data, list):
errors.append("ZFS config data is not a list")
else:
# Check for required ZFS configuration elements
has_partscheme = any('partscheme' in str(line) for line in InstallationData.zfs_config_data)
if not has_partscheme:
errors.append("ZFS config missing partition scheme")
has_disk = any('disk0=' in str(line) for line in InstallationData.zfs_config_data)
if not has_disk:
errors.append("ZFS config missing disk specification")
else:
# Check custom partition configuration path
if not hasattr(InstallationData, 'disk') or not InstallationData.disk:
errors.append("Disk not specified for custom partitioning")
if not hasattr(InstallationData, 'slice') or not InstallationData.slice:
errors.append("Partition slice not specified")
if not hasattr(InstallationData, 'scheme') or not InstallationData.scheme:
errors.append("Partition scheme not specified")
elif InstallationData.scheme not in ['partscheme=GPT', 'partscheme=MBR']:
errors.append(f"Invalid partition scheme: {InstallationData.scheme}")
if not hasattr(InstallationData, 'new_partition') or not InstallationData.new_partition:
errors.append("No partitions defined for custom partitioning")
elif not isinstance(InstallationData.new_partition, list):
errors.append("Partition data is not a list")
# Check installation config file path
if not installation_config:
errors.append("Installation config file path not defined")
return len(errors) == 0, errors
@classmethod
def create_cfg(cls):
"""
Create the ghostbsd_installation.cfg file to install GhostBSD.
Generates the configuration file used by the GhostBSD installer based on
data stored in InstallationData. Supports both ZFS and custom partitioning
configurations.
The configuration includes:
- Installation mode and type settings
- Disk and partition configuration
- Boot manager setup
- Network configuration
- First boot preparation commands
Raises:
ValueError: If sanity check fails, indicating invalid configuration data
IOError: If unable to write to the configuration file
"""
# Perform sanity check before creating configuration
is_valid, errors = cls.sanity_check()
if not is_valid:
error_msg = "Configuration validation failed:\n" + "\n".join(f"- {error}" for error in errors)
raise ValueError(error_msg)
try:
with open(installation_config, 'w') as f:
# Installation Mode
f.write('# Installation Mode\n')
f.write('installMode=fresh\n')
f.write('installInteractive=no\n')
f.write('installType=GhostBSD\n')
f.write('installMedium=livezfs\n')
f.write('packageType=livezfs\n')
if InstallationData.zfs_config_data:
# ZFS Configuration Path
for line in InstallationData.zfs_config_data:
if 'partscheme' in line:
f.write(line)
boot = InstallationData.boot
if boot == 'refind':
f.write('bootManager=none\n')
f.write(f'efiLoader={boot}\n')
else:
f.write(f'bootManager={boot}\n')
f.write('efiLoader=none\n')
else:
f.write(line)
else:
# Custom Partition Configuration Path
d_output = InstallationData.disk
f.write('\n# Disk Setup\n')
f.write('ashift=12\n')
f.write(f'disk0={d_output}\n')
# Partition Slice.
f.write(f'partition={InstallationData.slice}\n')
# Boot Menu
boot = InstallationData.boot
if boot == 'refind':
f.write('bootManager=none\n')
f.write(f'efiLoader={boot}\n')
else:
f.write(f'bootManager={boot}\n')
f.write('efiLoader=none\n')
f.write(f'{InstallationData.scheme}\n')
f.write('commitDiskPart\n')
# Partition Setup
f.write('\n# Partition Setup\n')
for line in InstallationData.new_partition:
if 'BOOT' in line or 'BIOS' in line or 'UEFI' in line:
pass
else:
f.write(f'disk0-part={line.strip()}\n')
f.write('commitDiskLabel\n')
# Network Configuration
f.write('\n# Network Configuration\n')
f.write('hostname=installed\n')
# First Boot Preparation Commands
f.write('\n# command to prepare first boot\n')
f.write("runCommand=sysrc hostname='installed'\n")
f.write("runCommand=pw userdel -n ghostbsd -r\n")
f.write("runCommand=sed -i '' 's/ghostbsd/root/g' /etc/gettytab\n")
f.write("runCommand=sed -i '' 's/ghostbsd/root/g' /etc/ttys\n")
f.write("runCommand=mv /usr/local/etc/devd/automount_devd"
".conf.skip /usr/local/etc/devd/automount_devd.conf\n")
f.write("runCommand=mv /usr/local/etc/devd/automount_devd"
"_localdisks.conf.skip /usr/local/etc/devd/"
"automount_devd_localdisks.conf\n")
except IOError as e:
raise IOError(f"Failed to write configuration file: {e}")
+887
View File
@@ -0,0 +1,887 @@
import gi
gi.require_version('Gtk', '3.0')
gi.require_version('Gdk', '3.0')
from gi.repository import Gtk, Gdk
from install_station.partition import (
DiskPartition,
DeletePartition,
bios_or_uefi,
CreateSlice,
AutoFreeSpace,
CreatePartition,
CreateLabel
)
from install_station.data import InstallationData, logo
from install_station.interface_controller import Button
import gettext
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
bios_type = bios_or_uefi()
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('/usr/local/lib/install-station/ghostbsd-style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
screen,
cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
class PartitionManager:
"""
Utility class for partition management operations following the pattern of InstallTypes and ZFS.
This class provides a GTK+ interface for managing disk partitions including creating,
deleting, and configuring partitions with support for both GPT and MBR schemes.
Supports ZFS, UFS, SWAP, BOOT, and UEFI filesystem types.
The class follows a utility pattern with class methods and variables for state management,
designed to integrate with the InstallationData system for configuration persistence.
"""
# Class variables for state management
fs = None
mount_point = None
window = None
efi_exist = True
fs_behind = None
disk_index = None
path = None
disk = None
vbox1 = None
scheme = 'GPT'
size = None
slice = None
label = None
mount_point_behind = None
change_schemes = False
iter = None
store = None
treeview = None
tree_selection = None
# UI elements as class variables
create_bt = None
delete_bt = None
revert_bt = None
auto_bt = None
fs_type = None
entry = None
mount_point_box = None
@classmethod
def set_fs(cls, widget):
"""
Set the filesystem type from a ComboBoxText widget selection.
Args:
widget: GTK ComboBoxText widget containing filesystem type options
"""
cls.fs = widget.get_active_text()
@classmethod
def get_mount_point(cls, widget):
"""
Get the mount point from a ComboBoxText widget selection.
Args:
widget: GTK ComboBoxText widget containing mount point options
"""
cls.mount_point = widget.get_active_text()
@classmethod
def get_model(cls):
"""
Return the GTK widget model for the partition manager.
Creates and initializes the UI if it doesn't exist yet.
Returns:
Gtk.Box: The main container widget for the partition manager interface
"""
if cls.vbox1 is None:
cls.initialize()
return cls.vbox1
@classmethod
def initialize(cls):
"""
Initialize the partition manager UI following the utility class pattern.
Creates the main interface including the partition tree view, control buttons,
and sets up the partition database. This method is called automatically
by get_model() when the interface is first accessed.
"""
DiskPartition.create_partition_database()
cls.vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
cls.vbox1.show()
# Scrolled window for partition tree
sw = Gtk.ScrolledWindow(hexpand=True, vexpand=True)
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
cls.store = Gtk.TreeStore(str, str, str, str, bool)
cls.tree_store()
cls.treeview = Gtk.TreeView()
cls.treeview.set_model(cls.store)
cls.treeview.set_rules_hint(True)
# Setup columns
cls._setup_columns()
cls.treeview.set_reorderable(True)
cls.treeview.expand_all()
cls.tree_selection = cls.treeview.get_selection()
cls.tree_selection.set_mode(Gtk.SelectionMode.SINGLE)
cls.tree_selection.connect("changed", cls.partition_selection)
sw.add(cls.treeview)
sw.show()
cls.vbox1.pack_start(sw, True, True, 0)
# Button box
hbox1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=0)
hbox1.set_border_width(10)
cls.vbox1.pack_start(hbox1, False, False, 0)
hbox1.show()
cls.scheme = 'GPT'
hbox1.pack_start(cls.delete_create_button(), False, False, 10)
@classmethod
def _setup_columns(cls):
"""
Setup treeview columns for the partition display.
Creates columns for Partition, Size(MB), Mount Point, and System/Type
with appropriate widths and header labels.
"""
columns_config = [
('Partition', 0, 150),
('Size(MB)', 1, 150),
('Mount Point', 2, 150),
('System/Type', 3, 150)
]
for i, (title, text_col, width) in enumerate(columns_config):
cell = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(None, cell, text=text_col)
column_header = Gtk.Label(label=title)
column_header.set_use_markup(True)
column_header.show()
column.set_widget(column_header)
column.set_resizable(True)
column.set_fixed_width(width)
if i == 0:
column.set_sort_column_id(0)
cls.treeview.append_column(column)
@classmethod
def tree_store(cls):
"""
Populate the tree store with disk and partition information from the disk database.
Creates a hierarchical tree structure showing:
- Disks (top level)
- Partitions/slices (second level)
- Labels/partitions (third level)
Each level displays relevant information like size, mount points, and filesystem types.
Returns:
Gtk.TreeStore: The populated tree store model
"""
cls.store.clear()
disk_db = DiskPartition.get_disk_database()
cls.disk_index = list(disk_db.keys())
for disk in disk_db:
disk_info = disk_db[disk]
disk_scheme = disk_info['scheme']
mount_point = ''
disk_size = str(disk_info['size'])
disk_partitions = disk_info['partitions']
partition_list = disk_info['partition-list']
pinter1 = cls.store.append(None, [disk, disk_size, mount_point,
disk_scheme, True])
for partition in partition_list:
partition_info = disk_partitions[partition]
file_system = partition_info['file-system']
mount_point = partition_info['mount-point']
partition_size = str(partition_info['size'])
partition_partitions = partition_info['partitions']
label_list = partition_info['partition-list']
pinter2 = cls.store.append(pinter1, [partition, partition_size, mount_point, file_system, True])
for label in label_list:
label_info = partition_partitions[label]
file_system = label_info['file-system']
label_mount_point = label_info['mount-point']
label_size = str(label_info['size'])
cls.store.append(pinter2, [label, label_size, label_mount_point, file_system, True])
return cls.store
@classmethod
def update(cls):
"""
Update the treeview after partition operations.
Refreshes the partition tree display, expands all rows, and attempts
to restore the previously selected row if it still exists.
"""
old_path = cls.path
cls.tree_store()
cls.treeview.expand_all()
if old_path:
cls.treeview.row_activated(old_path, cls.treeview.get_columns()[0])
cls.treeview.set_cursor(old_path)
@classmethod
def delete_create_button(cls):
"""
Create the button toolbar for partition operations.
Creates a horizontal box containing Create, Delete, Revert, and Auto buttons
for partition management operations.
Returns:
Gtk.Box: Container with partition operation buttons
"""
bbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=True, spacing=10)
bbox.set_border_width(5)
bbox.set_spacing(10)
cls.create_bt = Gtk.Button(label="Create")
cls.create_bt.connect("clicked", cls.create_partition)
cls.create_bt.set_sensitive(False)
bbox.pack_start(cls.create_bt, True, True, 0)
cls.delete_bt = Gtk.Button(label="Delete")
cls.delete_bt.connect("clicked", cls.delete_partition)
cls.delete_bt.set_sensitive(False)
bbox.pack_start(cls.delete_bt, True, True, 0)
cls.revert_bt = Gtk.Button(label="Revert")
cls.revert_bt.connect("clicked", cls.revert_change)
cls.revert_bt.set_sensitive(False)
bbox.pack_start(cls.revert_bt, True, True, 0)
cls.auto_bt = Gtk.Button(label="Auto")
cls.auto_bt.connect("clicked", cls.auto_partition)
cls.auto_bt.set_sensitive(False)
bbox.pack_start(cls.auto_bt, True, True, 0)
return bbox
@classmethod
def on_add_label(cls, _widget, entry, free_space, path):
"""
Handle adding a new label/partition in MBR scheme.
Args:
_widget: The button widget (unused)
entry: SpinButton containing the partition size
free_space: Available space in MB
path: TreePath indicating the parent location
"""
create_size = entry.get_value_as_int()
left_size = free_space - create_size
CreateLabel(path, cls.disk, cls.slice, left_size, create_size,
cls.mount_point, cls.fs)
cls.window.hide()
cls.update()
@classmethod
def on_add_partition(cls, _widget, entry, free_space, path):
"""
Handle adding a new partition in GPT scheme.
Args:
_widget: The button widget (unused)
entry: SpinButton containing the partition size
free_space: Available space in MB
path: TreePath indicating the parent location
"""
create_size = entry.get_value_as_int()
left_size = int(free_space - create_size)
CreatePartition(path, cls.disk, left_size, create_size,
cls.mount_point, cls.fs)
cls.window.hide()
cls.update()
@classmethod
def cancel(cls, _widget):
"""
Cancel the current partition operation and close the dialog.
Args:
_widget: The cancel button widget (unused)
"""
cls.window.hide()
cls.update()
@classmethod
def label_editor(cls, path, size, scheme):
"""
Open the partition/label editor dialog.
Creates a dialog window for configuring a new partition with options for
filesystem type, size, and mount point based on the partitioning scheme.
Args:
path: TreePath indicating where to create the partition
size: Available free space in MB
scheme: Partitioning scheme ('GPT' or 'MBR')
"""
free_space = int(size)
cls.window = Gtk.Window()
cls.window.set_title(title="Add Partition")
cls.window.set_border_width(0)
cls.window.set_size_request(480, 200)
cls.window.set_icon_from_file(logo)
box1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
cls.window.add(box1)
box1.show()
box2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=10)
box2.set_border_width(10)
box1.pack_start(box2, True, True, 0)
box2.show()
# Create partition configuration table
table = Gtk.Table(1, 2, True)
label1 = Gtk.Label(label="Type:")
label2 = Gtk.Label(label="Size(MB):")
label3 = Gtk.Label(label="Mount point:")
cls.fs_type = Gtk.ComboBoxText()
cls.fs_type.append_text('ZFS')
cls.fs_type.append_text('SWAP')
cls.fs_type.append_text('UFS') # Add UFS option
# Set filesystem options based on scheme and existing partitions
if scheme == 'GPT':
if bios_type == "UEFI":
cls.fs_type.append_text("UEFI")
if cls.efi_exist is False:
cls.fs_type.set_active(3) # UEFI
cls.fs = "UEFI"
elif cls.mount_point_behind == "/" or cls.fs_behind == "ZFS":
cls.fs_type.set_active(1) # SWAP
cls.fs = "SWAP"
else:
cls.fs_type.set_active(0) # ZFS
cls.fs = "ZFS"
else:
cls.fs_type.append_text("BOOT")
if not InstallationData.new_partition:
cls.fs_type.set_active(4) # BOOT
cls.fs = "BOOT"
elif len(InstallationData.new_partition) == 0:
cls.fs_type.set_active(4) # BOOT
cls.fs = "BOOT"
elif cls.mount_point_behind == "/" or cls.fs_behind == "ZFS":
cls.fs_type.set_active(1) # SWAP
cls.fs = "SWAP"
else:
cls.fs_type.set_active(0) # ZFS
cls.fs = "ZFS"
elif cls.mount_point_behind == "/" or cls.fs_behind == "ZFS":
cls.fs_type.set_active(1) # SWAP
cls.fs = "SWAP"
else:
cls.fs_type.set_active(0) # ZFS
cls.fs = "ZFS"
cls.fs_type.connect("changed", cls.set_fs)
# Size spinner
adj = Gtk.Adjustment(free_space, 0, free_space, 1, 100, 0)
cls.entry = Gtk.SpinButton(adjustment=adj, numeric=True)
cls.entry.set_editable(True)
# Mount point selection
cls.mount_point_box = Gtk.ComboBoxText()
cls.mount_point = "none"
cls.mount_point_box.append_text('none')
cls.mount_point_box.append_text('/')
if InstallationData.new_partition:
if scheme == 'GPT' and len(InstallationData.new_partition) == 1:
cls.mount_point_box.append_text('/boot')
elif scheme == 'MBR' and len(InstallationData.new_partition) == 0:
cls.mount_point_box.append_text('/boot')
elif scheme == 'MBR' and not InstallationData.new_partition:
cls.mount_point_box.append_text('/boot')
cls.mount_point_box.append_text('/etc')
cls.mount_point_box.append_text('/root')
cls.mount_point_box.append_text('/tmp')
cls.mount_point_box.append_text('/usr')
cls.mount_point_box.append_text('/home')
cls.mount_point_box.append_text('/var')
cls.mount_point_box.set_active(0)
# Enable mount point selection for UFS
if 'UFS' in cls.fs:
cls.mount_point_box.set_sensitive(True)
else:
cls.mount_point_box.set_sensitive(False)
cls.mount_point_box.connect("changed", cls.get_mount_point)
# Add to table
table.attach(label1, 0, 1, 1, 2)
table.attach(cls.fs_type, 1, 2, 1, 2)
table.attach(label2, 0, 1, 2, 3)
table.attach(cls.entry, 1, 2, 2, 3)
table.attach(label3, 0, 1, 3, 4)
table.attach(cls.mount_point_box, 1, 2, 3, 4)
box2.pack_start(table, False, False, 0)
# Buttons
box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=10)
box2.set_border_width(5)
box1.pack_start(box2, False, True, 0)
box2.show()
bbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=True, spacing=10)
bbox.set_border_width(5)
bbox.set_spacing(10)
button = Gtk.Button(stock=Gtk.STOCK_CANCEL)
button.connect("clicked", cls.cancel)
bbox.pack_start(button, True, True, 0)
button = Gtk.Button(stock=Gtk.STOCK_ADD)
if scheme == 'MBR':
button.connect(
"clicked", cls.on_add_label, cls.entry, free_space, path
)
elif scheme == 'GPT' and cls.fs == 'BOOT':
button.connect(
"clicked", cls.on_add_partition, cls.entry, free_space, path
)
elif scheme == 'GPT' and cls.fs == 'UEFI' and cls.efi_exist is False:
button.connect(
"clicked", cls.on_add_partition, cls.entry, free_space, path
)
else:
button.connect(
"clicked", cls.on_add_partition, cls.entry, free_space, path
)
bbox.pack_start(button, True, True, 0)
box2.pack_end(bbox, True, True, 5)
cls.window.show_all()
@classmethod
def scheme_selection(cls, combobox):
"""
Handle partition scheme selection from combo box.
Args:
combobox: ComboBox widget containing scheme options
"""
model = combobox.get_model()
index = combobox.get_active()
data = model[index][0]
value = data.partition(':')[0]
cls.scheme = value
@classmethod
def add_gpt_mbr(cls, _widget):
"""
Apply the selected partition scheme to the disk.
Args:
_widget: The add button widget (unused)
"""
DiskPartition.set_disk_scheme(cls.scheme, cls.disk, cls.size)
cls.update()
cls.window.hide()
@classmethod
def scheme_editor(cls):
"""
Create a partition scheme editor window.
Opens a dialog allowing the user to select between GPT and MBR
partition schemes for the selected disk.
"""
cls.window = Gtk.Window()
cls.window.set_title("Partition Scheme")
cls.window.set_border_width(0)
cls.window.set_size_request(400, 150)
cls.window.set_icon_from_file(logo)
box1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
cls.window.add(box1)
box1.show()
box2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=10)
box2.set_border_width(10)
box1.pack_start(box2, True, True, 0)
box2.show()
# Creating MBR or GPT drive
label = Gtk.Label(label='<b>Select a partition scheme for this drive:</b>')
label.set_use_markup(True)
# Adding a combo box to selecting MBR or GPT scheme.
cls.scheme = 'GPT'
scheme_box = Gtk.ComboBoxText()
scheme_box.append_text("GPT: GUID Partition Table")
scheme_box.append_text("MBR: DOS Partition")
scheme_box.connect('changed', cls.scheme_selection)
scheme_box.set_active(0)
table = Gtk.Table(1, 2, True)
table.attach(label, 0, 2, 0, 1)
table.attach(scheme_box, 0, 2, 1, 2)
box2.pack_start(table, False, False, 0)
box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=10)
box2.set_border_width(5)
box1.pack_start(box2, False, True, 0)
box2.show()
# Add create_scheme button
bbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=True, spacing=10)
bbox.set_border_width(5)
bbox.set_spacing(10)
button = Gtk.Button(stock=Gtk.STOCK_ADD)
button.connect("clicked", cls.add_gpt_mbr)
bbox.pack_start(button, True, True, 0)
box2.pack_end(bbox, True, True, 5)
cls.window.show_all()
@classmethod
def get_value(cls, _widget, entry):
"""
Handle slice creation from the slice editor dialog.
Gets the partition size from the entry widget and creates a new slice
in the MBR partition table.
Args:
_widget: The add button widget (unused)
entry: SpinButton containing the partition size
"""
partition_size = int(entry.get_value_as_int())
rs = int(cls.size) - partition_size
CreateSlice(partition_size, rs, cls.path, cls.disk)
cls.update()
cls.window.hide()
@classmethod
def slice_editor(cls):
"""
Create a window for editing partition slices in MBR scheme.
Opens a dialog for creating a new slice partition with size configuration.
Used specifically for MBR partitioning scheme.
"""
free_space = int(cls.size)
cls.window = Gtk.Window()
cls.window.set_title("Add Partition")
cls.window.set_border_width(0)
cls.window.set_size_request(400, 150)
cls.window.set_icon_from_file(logo)
box1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
cls.window.add(box1)
box1.show()
box2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=10)
box2.set_border_width(10)
box1.pack_start(box2, True, True, 0)
box2.show()
# Create Partition slice
table = Gtk.Table(1, 2, True)
label1 = Gtk.Label(label="Size(MB):")
adj = Gtk.Adjustment(free_space, 0, free_space, 1, 100, 0)
cls.entry = Gtk.SpinButton(adjustment=adj, numeric=True)
cls.entry.set_numeric(True)
table.attach(label1, 0, 1, 1, 2)
table.attach(cls.entry, 1, 2, 1, 2)
box2.pack_start(table, False, False, 0)
box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=10)
box2.set_border_width(5)
box1.pack_start(box2, False, True, 0)
box2.show()
# Add button
bbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=True, spacing=10)
bbox.set_border_width(5)
bbox.set_spacing(10)
button = Gtk.Button(stock=Gtk.STOCK_CANCEL)
button.connect("clicked", cls.cancel)
bbox.pack_start(button, True, True, 0)
button = Gtk.Button(stock=Gtk.STOCK_ADD)
button.connect("clicked", cls.get_value, cls.entry)
bbox.pack_start(button, True, True, 0)
box2.pack_end(bbox, True, True, 5)
cls.window.show_all()
@classmethod
def delete_partition(cls, _widget):
"""
Delete the currently selected partition.
Removes the selected partition or slice from the disk and updates
the partition display.
Args:
_widget: The delete button widget (unused)
"""
part = cls.slice if cls.label == "Not selected" else cls.label
DeletePartition(part, cls.path)
cls.update()
@classmethod
def auto_partition(cls, _widget):
"""
Automatically partition the disk with default ZFS configuration.
Creates automatic partitions suitable for ZFS installation including
boot partitions (if needed) and ZFS root partition.
Args:
_widget: The auto button widget (unused)
"""
cls.create_bt.set_sensitive(False)
cls.delete_bt.set_sensitive(False)
cls.auto_bt.set_sensitive(False)
cls.revert_bt.set_sensitive(False)
if 'freespace' in cls.slice:
AutoFreeSpace(cls.path, cls.size, 'ZFS', cls.efi_exist,
cls.disk, cls.scheme)
cls.update()
else:
print('wrong utilization')
@classmethod
def revert_change(cls, _widget):
"""
Revert all partition changes and restore original state.
Clears all partition configuration data from InstallationData and
recreates the original partition database, effectively undoing
all partition modifications.
Args:
_widget: The revert button widget (unused)
"""
# Reset all partition configuration data in InstallationData
InstallationData.create = []
InstallationData.scheme = ""
InstallationData.disk = ""
InstallationData.slice = ""
InstallationData.delete = []
InstallationData.destroy = {}
InstallationData.new_partition = []
DiskPartition.create_partition_database()
cls.tree_store()
cls.treeview.expand_all()
@classmethod
def create_partition(cls, _widget):
"""
Create a new partition based on the current selection.
Opens the appropriate editor dialog based on the selected item:
- Scheme editor for un-partitioned disks
- Label editor for free space in MBR or GPT
- Slice editor for MBR primary partition creation
Args:
_widget: The create button widget (unused)
"""
cls.create_bt.set_sensitive(False)
cls.delete_bt.set_sensitive(False)
cls.auto_bt.set_sensitive(False)
cls.revert_bt.set_sensitive(False)
if cls.change_schemes is True:
cls.scheme_editor()
elif 'freespace' in cls.label:
cls.label_editor(cls.path, cls.size, 'MBR')
elif 'freespace' in cls.slice:
if cls.scheme == "MBR" and cls.path[1] < 4:
cls.slice_editor()
elif cls.scheme == "GPT":
cls.label_editor(cls.path, cls.size, 'GPT')
else:
print('This method of creating partition is not implemented')
@classmethod
def partition_selection(cls, widget):
"""
Handle partition selection events and update UI button states.
This method is called when a user selects a different item in the partition
tree view. It analyzes the selection and enables/disables appropriate buttons
based on what operations are valid for the selected item.
The method handles complex logic for:
- Determining partition hierarchy (disk/slice/label)
- Checking partition scheme compatibility
- Validating boot partition requirements
- Managing button sensitivity states
Args:
widget: TreeSelection widget that triggered the selection change
"""
efi_already_exist = False
model, cls.iter, = widget.get_selected()
if cls.iter is None:
Button.next_button.set_sensitive(False)
return None
cls.path = model.get_path(cls.iter)
main_tree_iter = model.get_iter(cls.path)
cls.size = model.get_value(main_tree_iter, 1)
tree_iter1 = model.get_iter(cls.path[0])
cls.scheme = model.get_value(tree_iter1, 3)
cls.disk = model.get_value(tree_iter1, 0)
if len(cls.path) >= 2:
tree_iter2 = model.get_iter(cls.path[:2])
cls.slice = model.get_value(tree_iter2, 0)
cls.change_schemes = False
else:
if len(cls.path) == 1:
if DiskPartition.how_partition(cls.disk) == 0:
cls.change_schemes = True
elif DiskPartition.how_partition(cls.disk) == 1:
slice_path = f'{cls.path[0]}:0'
try:
tree_iter2 = model.get_iter(slice_path)
if 'freespace' in model.get_value(tree_iter2, 0):
cls.change_schemes = True
else:
cls.change_schemes = False
except ValueError:
cls.change_schemes = True
else:
cls.change_schemes = False
cls.slice = 'Not selected'
else:
cls.slice = 'Not selected'
cls.change_schemes = False
if len(cls.path) == 3:
tree_iter3 = model.get_iter(cls.path[:3])
cls.label = model.get_value(tree_iter3, 0)
else:
cls.label = 'Not selected'
# Get previous partition info for context
if len(cls.path) == 2 and cls.path[1] > 0 and cls.scheme == "GPT":
path_behind = f'{cls.path[0]}:{str(int(cls.path[1] - 1))}'
tree_iter4 = model.get_iter(path_behind)
cls.mount_point_behind = model.get_value(tree_iter4, 2)
cls.fs_behind = model.get_value(tree_iter4, 3)
elif len(cls.path) == 3 and cls.path[2] > 0 and cls.scheme == "MBR":
path1 = cls.path[0]
path2 = str(cls.path[1])
path3 = str(int(cls.path[2] - 1))
path_behind2 = f'{path1}:{path2}:{path3}'
tree_iter1 = model.get_iter(path_behind2)
cls.mount_point_behind = model.get_value(tree_iter1, 2)
cls.fs_behind = model.get_value(tree_iter1, 3)
else:
cls.mount_point_behind = None
cls.fs_behind = None
# Set button states based on selection
if 'freespace' in cls.slice:
cls.create_bt.set_sensitive(True)
cls.delete_bt.set_sensitive(False)
cls.auto_bt.set_sensitive(True)
# Scan for efi partition
for num in range(cls.path[1]):
partition_path = f"{cls.path[0]}:{num}"
tree_iter_1 = model.get_iter(partition_path)
first_fs = model.get_value(tree_iter_1, 3)
if first_fs == "UEFI" or 'efi' in first_fs:
cls.efi_exist = True
break
else:
cls.efi_exist = False
elif 'freespace' in cls.label:
if cls.path[1] > 3:
cls.create_bt.set_sensitive(False)
else:
cls.create_bt.set_sensitive(True)
cls.auto_bt.set_sensitive(True)
cls.delete_bt.set_sensitive(False)
elif 's' in cls.slice and len(cls.path) > 1:
cls.create_bt.set_sensitive(False)
cls.delete_bt.set_sensitive(True)
cls.auto_bt.set_sensitive(False)
elif 'p' in cls.slice and len(cls.path) > 1:
cls.create_bt.set_sensitive(False)
cls.delete_bt.set_sensitive(True)
cls.auto_bt.set_sensitive(False)
else:
cls.delete_bt.set_sensitive(False)
cls.auto_bt.set_sensitive(False)
if DiskPartition.how_partition(cls.disk) == 0:
cls.create_bt.set_sensitive(True)
elif cls.change_schemes is True:
cls.create_bt.set_sensitive(True)
else:
cls.create_bt.set_sensitive(False)
# Handle partition validation
if InstallationData.new_partition:
cls.partitions = InstallationData.new_partition
if not cls.partitions:
Button.next_button.set_sensitive(False)
return None
if 'GPT' in InstallationData.scheme:
if InstallationData.disk:
disk = InstallationData.disk
disk_id = cls.disk_index.index(disk)
num = 0
while True:
partition_path = f"{disk_id}:{num}"
try:
tree_iter_1 = model.get_iter(partition_path)
first_fs = model.get_value(tree_iter_1, 3)
if 'efi' in first_fs:
efi_already_exist = True
break
except ValueError:
efi_already_exist = False
break
num += 1
if 'BOOT' in cls.partitions[0] and bios_type == 'BIOS':
if len(cls.partitions) >= 2 and 'ZFS' in cls.partitions[1]:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif efi_already_exist is True and bios_type == 'UEFI':
if 'ZFS' in cls.partitions[0]:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif len(cls.partitions) >= 2 and 'UEFI' in cls.partitions[0] and 'ZFS' in cls.partitions[1]:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif 'MBR' in InstallationData.scheme:
cls.efi_exist = False
if len(cls.partitions) >= 1:
if "/boot\n" in cls.partitions[0]:
if len(cls.partitions) >= 2 and 'ZFS' in cls.partitions[1]:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif 'ZFS' in cls.partitions[0]:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
else:
Button.next_button.set_sensitive(False)
else:
Button.next_button.set_sensitive(False)
else:
Button.next_button.set_sensitive(False)
# Check if any configuration exists to enable revert button
path_exist = [
bool(InstallationData.create),
bool(InstallationData.scheme),
bool(InstallationData.disk),
bool(InstallationData.slice),
bool(InstallationData.delete),
bool(InstallationData.destroy),
bool(InstallationData.new_partition)
]
if any(path_exist):
cls.revert_bt.set_sensitive(True)
else:
cls.revert_bt.set_sensitive(False)
+77
View File
@@ -0,0 +1,77 @@
"""
Contains the data class and some commonly use variables
"""
be_name = "default"
logo = "/usr/local/lib/install-station/image/logo.png"
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 = "/," \
"/home(mountpoint=/home)," \
"/tmp(mountpoint=/tmp|exec=on|setuid=off)," \
"/usr(mountpoint=/usr|canmount=off)," \
"/usr/ports(setuid=off)," \
"/usr/src," \
"/var(mountpoint=/var|canmount=off)," \
"/var/audit(exec=off|setuid=off)," \
"/var/crash(exec=off|setuid=off)," \
"/var/log(exec=off|setuid=off)," \
"/var/mail(atime=on)," \
"/var/tmp(setuid=off)"
class InstallationData:
"""
Centralized data storage for installation configuration
"""
# Partition configuration
destroy: dict = {}
delete: list = []
new_partition: list = []
create: list = []
scheme: str = ""
disk: str = ""
slice: str = ""
boot: str = ""
# ZFS configuration data (instead of zfs_config file)
zfs_config_data: list = []
# UFS configuration data (instead of ufs_config file)
ufs_config_data: list = []
# Installation type and mode
install_mode: str = "" # "install" or "try"
filesystem_type: str = "" # "zfs", "ufs", or "custom"
# Language and localization
language: str = ""
language_code: str = ""
# Boot manager configuration
boot_manager: str = ""
# Network configuration (for live mode)
network_config: dict = {}
@classmethod
def reset(cls):
"""Reset all installation data"""
cls.destroy = {}
cls.delete = []
cls.new_partition = []
cls.create = []
cls.scheme = ""
cls.disk = ""
cls.slice = ""
cls.boot = ""
cls.zfs_config_data = []
cls.ufs_config_data = []
cls.install_mode = ""
cls.filesystem_type = ""
cls.language = ""
cls.language_code = ""
cls.boot_manager = ""
cls.network_config = {}
+61
View File
@@ -0,0 +1,61 @@
#!/usr/bin/env python
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
from subprocess import Popen
import gettext
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
lyrics = _("""Installation is complete. You need to restart the
computer in order to use the new installation.
You can continue to use this live media, although
any changes you make or documents you save will
not be preserved on reboot.""")
class PyApp:
@classmethod
def on_reboot(cls, _widget):
Popen('shutdown -r now', shell=True)
Gtk.main_quit()
@classmethod
def on_close(cls, _widget):
Gtk.main_quit()
def __init__(self):
window = Gtk.Window()
window.set_border_width(8)
window.connect("destroy", Gtk.main_quit)
window.set_title(_("Installation Completed"))
window.set_icon_from_file("/usr/local/lib/install-station/image/logo.png")
box1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
window.add(box1)
box1.show()
box2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=10)
box2.set_border_width(10)
box1.pack_start(box2, True, True, 0)
box2.show()
label = Gtk.Label(label=lyrics)
box2.pack_start(label, True, True, 0)
box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=10)
box2.set_border_width(5)
box1.pack_start(box2, False, True, 0)
box2.show()
table = Gtk.Table(1, 2, True)
restart = Gtk.Button(label=_("Restart"))
restart.connect("clicked", self.on_reboot)
continue_button = Gtk.Button(label=_("Continue"))
continue_button.connect("clicked", self.on_close)
table.attach(continue_button, 0, 1, 0, 1)
table.attach(restart, 1, 2, 0, 1)
box2.pack_start(table, True, True, 0)
window.show_all()
PyApp()
Gtk.main()
+55
View File
@@ -0,0 +1,55 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
import gettext
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
class ErrorWindow:
@classmethod
def on_close(cls, _widget):
Gtk.main_quit()
def __init__(self):
window = Gtk.Window()
window.set_border_width(8)
window.connect("destroy", Gtk.main_quit)
window.set_title(_("Installation Error"))
# window.set_icon_from_file("/usr/local/lib/install-station/image/logo.png")
box1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
window.add(box1)
box1.show()
box2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=10)
box2.set_border_width(10)
box1.pack_start(box2, True, True, 0)
box2.show()
title = Gtk.Label()
title.set_use_markup(True)
title_text = _("Installation has failed!")
title.set_markup(f'<b><span size="larger">{title_text}</span></b>')
label = Gtk.Label()
label.set_use_markup(True)
url = 'https://github.com/ghostbsd/ghostbsd-src/issues/new/choose'
anchor = f"<a href='{url}'>{_('GhostBSD issue system')}</a>"
message = _("Please report the issue to {anchor}, and \nbe sure to provide /tmp/.pc-sysinstall/pc-sysinstall.log.").format(anchor=anchor)
label.set_markup(message)
box2.pack_start(title, True, True, 0)
box2.pack_start(label, True, True, 0)
box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=10)
box2.set_border_width(5)
box1.pack_start(box2, False, True, 0)
box2.show()
table = Gtk.Table(n_rows=1, n_columns=2, homogeneous=True)
ok = Gtk.Button(label=_("Ok"))
ok.connect("clicked", self.on_close)
table.attach(ok, 0, 2, 0, 1)
box2.pack_start(table, True, True, 0)
window.show_all()
ErrorWindow()
Gtk.main()
+140
View File
@@ -0,0 +1,140 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, GLib, Gdk
import threading
from subprocess import Popen, PIPE, STDOUT
from time import sleep
from install_station.partition import delete_partition, destroy_partition, add_partition
from install_station.create_cfg import Configuration
from install_station.window import Window
from install_station.data import InstallationData, installation_config, pc_sysinstall
import sys
import gettext
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('/usr/local/lib/install-station/ghostbsd-style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
screen,
cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
def update_progress(progressbar, text):
"""
This method
"""
new_val = progressbar.get_fraction() + 0.000003
progressbar.set_fraction(new_val)
progressbar.set_text(text[0:80])
def read_output(command, progressbar):
GLib.idle_add(update_progress, progressbar, _("Creating ghostbsd_installation.cfg"))
Configuration.create_cfg()
sleep(1)
if InstallationData.delete:
GLib.idle_add(update_progress, progressbar, _("Deleting partition"))
delete_partition()
sleep(1)
# destroy disk partition and create scheme
if InstallationData.destroy:
GLib.idle_add(update_progress, progressbar, _("Creating disk partition"))
destroy_partition()
sleep(1)
# create partition
if InstallationData.create:
GLib.idle_add(update_progress, progressbar, _("Creating new partitions"))
add_partition()
sleep(1)
progressbar_text = None
process = Popen(
command, shell=True, stdin=PIPE, stdout=PIPE, stderr=STDOUT, close_fds=True, universal_newlines=True
)
while True:
line = process.stdout.readline()
if not line:
break
progressbar_text = line.rstrip()
GLib.idle_add(update_progress, progressbar, progressbar_text)
# Those for next 4 line is for debugging only.
# filer = open(f"{tmp}/tmp", "a")
# filer.writelines(progressbar_text)
# filer.close
print(progressbar_text)
if progressbar_text.rstrip() == "Installation finished!":
Popen(f'python {install-station_dir}/end.py', shell=True, close_fds=True)
else:
Popen(f'python {install-station_dir}/error.py', shell=True, close_fds=True)
Window.hide()
class InstallWindow:
def __init__(self):
self.vBox = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
self.vBox.show()
label = Gtk.Label(label=_("Installation in progress"), name="Header")
label.set_property("height-request", 50)
self.vBox.pack_start(label, False, False, 0)
hbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=0, name="install")
hbox.show()
self.vBox.pack_end(hbox, True, True, 0)
vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
vbox2.show()
label2 = Gtk.Label(name="sideText")
label2.set_markup(_(
"Thank you for choosing GhostBSD!\n\n"
"We believe every computer operating system should "
"be simple, elegant, secure and protect your privacy"
" while being easy to use. GhostBSD is simplifying "
"FreeBSD for those who lack the technical expertise "
"required to use it and lower the entry-level of "
"using BSD. \n\nWe hope you'll enjoy our BSD "
"operating system."
))
label2.set_justify(Gtk.Justification.LEFT)
label2.set_line_wrap(True)
# label2.set_max_width_chars(10)
label2.set_alignment(0.0, 0.2)
hbox2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=0, name="TransBox")
hbox2.show()
hbox.pack_start(hbox2, True, True, 0)
hbox2.pack_start(label2, True, True, 30)
image = Gtk.Image()
image.set_from_file(f"{install-station_dir}/image/G_logo.gif")
# image.set_size_request(width=256, height=256)
image.show()
hbox.pack_end(image, True, True, 20)
def get_model(self):
return self.vBox
class InstallProgress:
def __init__(self):
self.pbar = Gtk.ProgressBar()
self.pbar.set_show_text(True)
command = f'sudo {pc_sysinstall} -c {installation_config}'
thread = threading.Thread(
target=read_output,
args=(
command,
self.pbar
),
daemon=True
)
thread.start()
self.pbar.show()
def get_progressbar(self):
return self.pbar
+80
View File
@@ -0,0 +1,80 @@
"""
Module to create the inner window for select what type of installation.
"""
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk
from install_station.data import InstallationData
import gettext
_ = gettext.gettext
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('/usr/local/lib/install-station/ghostbsd-style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
screen,
cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
class InstallTypes:
# Class variables instead of instance variables
ne = 'zfs'
vbox1 = None
@classmethod
def filesystem_type(cls, widget, val):
# Only respond to activation, not deactivation
if widget.get_active():
cls.ne = val
InstallationData.filesystem_type = val
return
@classmethod
def get_type(cls):
return InstallationData.filesystem_type or cls.ne
@classmethod
def get_model(cls):
if cls.vbox1 is None:
cls.initialize()
return cls.vbox1
@classmethod
def initialize(cls):
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)
hbox1 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=0)
InstallationData.filesystem_type = cls.ne
cls.vbox1.pack_start(hbox1, True, False, 0)
hbox1.set_halign(Gtk.Align.CENTER)
label = Gtk.Label(label=_("How do you want to install GhostBSD?"))
label.set_alignment(0, 0.5)
vbox2.pack_start(label, False, False, 10)
# Create radio button group
cls.full_zfs_button = Gtk.RadioButton(
label=_("<b>Disks Configuration</b>\nInstall GhostBSD using Stripe, Mirror, RAIDZ1, RAIDZ2, or RAIDZ3 configurations.")
)
cls.full_zfs_button.get_child().set_use_markup(True)
cls.full_zfs_button.get_child().set_line_wrap(True)
vbox2.pack_start(cls.full_zfs_button, True, True, 10)
cls.full_zfs_button.connect("toggled", cls.filesystem_type, "zfs")
cls.full_zfs_button.show()
cls.custom_button = Gtk.RadioButton.new_with_label_from_widget(
cls.full_zfs_button,
_("<b>Multi-Boot Configuration</b>\nInstall GhostBSD with ZFS alongside other operating systems.")
)
cls.custom_button.get_child().set_use_markup(True)
cls.custom_button.get_child().set_line_wrap(True)
vbox2.pack_start(cls.custom_button, False, True, 10)
cls.custom_button.connect("toggled", cls.filesystem_type, "custom")
cls.custom_button.show()
hbox1.pack_start(vbox2, True, False, 150)
vbox2.set_halign(Gtk.Align.CENTER)
cls.full_zfs_button.set_active(True)
+242
View File
@@ -0,0 +1,242 @@
from gi.repository import Gtk
import gettext
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
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
class Button:
back_button = Gtk.Button(label=_('Back'))
"""This button is used to go back to the previous page."""
cancel_button = Gtk.Button(label=_('Cancel'))
"""This button is used to quit and clean up."""
next_button = Gtk.Button(label=_('Next'))
"""This button is used to go to the next page."""
_box = None
@classmethod
def hide_all(cls):
"""
This method hides all buttons.
"""
cls.back_button.hide()
cls.cancel_button.hide()
cls.next_button.hide()
@classmethod
def show_initial(cls):
"""
This method shows the initial buttons. Cancel and Next.
"""
cls.cancel_button.show()
cls.next_button.show()
@classmethod
def show_back(cls):
"""
This method shows the back button.
"""
cls.back_button.show()
@classmethod
def hide_back(cls):
"""
This method hides the back button.
"""
@classmethod
def box(cls):
"""
This method creates a box container of buttons aligned to the right.
Returns:
Box container with buttons aligned to the right for navigation.
"""
if cls._box is None:
# Use Box instead of Grid for better right-alignment control
cls._box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=5)
cls._box.set_halign(Gtk.Align.END) # Align the entire box to the right
cls.back_button.connect("clicked", Interface.back_page)
cls._box.pack_start(cls.back_button, False, False, 0)
cls.cancel_button.connect("clicked", Interface.delete)
cls._box.pack_start(cls.cancel_button, False, False, 0)
cls.next_button.connect("clicked", Interface.next_page)
cls._box.pack_start(cls.next_button, False, False, 0)
cls._box.show()
return cls._box
class Interface:
welcome = None
installation_type = None
custom_partition = None
full_zfs = None
boot_manager = None
network_setup = None
page = Gtk.Notebook()
@classmethod
def get_interface(cls):
interface_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
interface_box.show()
interface_box.pack_start(cls.page, True, True, 0)
cls.page.show()
cls.page.set_show_tabs(False)
cls.page.set_show_border(False)
welcome_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
welcome_box.show()
cls.welcome.initialize()
get_types = cls.welcome.get_model()
welcome_box.pack_start(get_types, True, True, 0)
Window.set_title(_("Welcome to GhostBSD"))
label = Gtk.Label(label=_("Welcome to GhostBSD"))
cls.page.insert_page(welcome_box, label, 0)
# Set what page to start at type of installation
cls.page.set_current_page(0)
cls.nbButton = Gtk.Notebook()
interface_box.pack_end(cls.nbButton, False, False, 5)
cls.nbButton.show()
cls.nbButton.set_show_tabs(False)
cls.nbButton.set_show_border(False)
label = Gtk.Label(label=_("Button"))
cls.nbButton.insert_page(Button.box(), label, 0)
return interface_box
@classmethod
def delete(cls, _widget, _event=None):
"""Close the main window."""
InstallationData.reset()
Gtk.main_quit()
@classmethod
def next_page(cls, _widget):
if InstallationData.install_mode == "install":
cls.next_install_page()
else:
cls.next_setup_page()
@classmethod
def next_install_page(cls):
"""Go to the next window."""
page = cls.page.get_current_page()
if page == 0:
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=_("Installation Types"))
cls.page.insert_page(type_box, label, 1)
cls.page.next_page()
cls.page.show_all()
Button.show_initial()
elif page == 1:
Button.show_back()
if InstallationData.filesystem_type == "custom":
custom_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
custom_box.show()
get_part = cls.custom_partition.get_model()
custom_box.pack_start(get_part, True, True, 0)
label = Gtk.Label(label=_("Custom Configuration"))
cls.page.insert_page(custom_box, label, 2)
cls.page.next_page()
cls.page.show_all()
Button.next_button.set_sensitive(False)
elif InstallationData.filesystem_type == "zfs":
zfs_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
zfs_box.show()
get_zfs = cls.full_zfs.get_model()
zfs_box.pack_start(get_zfs, True, True, 0)
label = Gtk.Label(label=_("ZFS Configuration"))
cls.page.insert_page(zfs_box, label, 2)
cls.page.next_page()
cls.page.show_all()
Button.next_button.set_sensitive(False)
elif page == 2:
boot_manager_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
boot_manager_box.show()
get_root = cls.boot_manager.get_model()
boot_manager_box.pack_start(get_root, True, True, 0)
label = Gtk.Label(label=_("Boot Option"))
cls.page.insert_page(boot_manager_box, label, 3)
Button.next_button.set_label(_("Install"))
cls.page.next_page()
cls.page.show_all()
Button.next_button.set_sensitive(True)
elif page == 3:
installation_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
installation_box.show()
install_window = InstallWindow()
get_install = install_window.get_model()
installation_box.pack_start(get_install, True, True, 0)
label = Gtk.Label(label=_("Installation Progress"))
cls.page.insert_page(installation_box, label, 8)
cls.page.next_page()
installation_progressbar = InstallProgress()
progressbar = installation_progressbar.get_progressbar()
box1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
box1.show()
label = Gtk.Label(label=_("Progress Bar"))
box1.pack_end(progressbar, False, False, 0)
cls.nbButton.insert_page(box1, label, 4)
cls.nbButton.next_page()
current_page_widget = cls.page.get_nth_page(cls.page.get_current_page())
title_text = cls.page.get_tab_label_text(current_page_widget)
Window.set_title(title_text)
@classmethod
def next_setup_page(cls):
page = cls.page.get_current_page()
if page == 0:
Button.next_button.show()
Button.next_button.set_sensitive(False)
Window.set_title(_("Network Setup"))
net_setup_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
net_setup_box.show()
model = cls.network_setup.get_model()
net_setup_box.pack_start(model, True, True, 0)
label = Gtk.Label(label=_("Network Setup"))
cls.page.insert_page(net_setup_box, label, 1)
cls.page.next_page()
cls.page.show_all()
if page == 1:
with open('/usr/home/ghostbsd/.xinitrc', 'w') as xinitrc:
xinitrc.writelines('gsettings set org.mate.SettingsDaemon.plugins.housekeeping active true &\n')
xinitrc.writelines('gsettings set org.mate.screensaver lock-enabled false &\n')
xinitrc.writelines('exec ck-launch-session mate-session\n')
Gtk.main_quit()
@classmethod
def back_page(cls, _widget):
"""Go back to the previous window."""
current_page = cls.page.get_current_page()
if current_page == 1:
Button.hide_back()
if current_page == 2:
Button.hide_back()
elif current_page == 3:
Button.next_button.set_label(_("Next"))
cls.page.prev_page()
new_page = cls.page.get_current_page()
if current_page == 2 and new_page == 1:
# Reset partition configuration data when going back
InstallationData.destroy = {}
InstallationData.delete = []
InstallationData.create = []
InstallationData.new_partition = []
InstallationData.scheme = ""
InstallationData.disk = ""
InstallationData.slice = ""
InstallationData.zfs_config_data = []
InstallationData.ufs_config_data = []
# Clean up temporary directory if it exists
DiskPartition.create_partition_database()
Button.next_button.set_sensitive(True)
+143
View File
@@ -0,0 +1,143 @@
from gi.repository import Gtk, Gdk
import os
from install_station.system_calls import (
language_dictionary,
localize_system
)
from install_station.data import InstallationData, tmp, logo
import gettext
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
# Ensure temp directory exists
if not os.path.exists(tmp):
os.makedirs(tmp)
lang_dictionary = language_dictionary()
# Text to be replace be multiple language file.
welltext = _("Select the language you want to use with GhostBSD.")
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('/usr/local/lib/install-station/ghostbsd-style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
screen,
cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
class Language:
_instance = None
def __new__(cls):
"""Implement singleton pattern."""
if cls._instance is None:
cls._instance = super(Language, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
def __init__(self):
"""Initialize only once."""
if not self._initialized:
self._language = None
self._vbox1 = None
self._initialize_ui()
self._initialized = True
# On selection it overwrite the default language file.
def _language_selection(self, tree_selection):
model, treeiter = tree_selection.get_selected()
if treeiter is not None:
value = model[treeiter][0]
self._language = lang_dictionary[value]
InstallationData.language = value
InstallationData.language_code = lang_dictionary[value]
return
def _setup_language_columns(self, treeView):
cell = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(None, cell, text=0)
column_header = Gtk.Label(label=_('Language'))
column_header.set_use_markup(True)
column_header.show()
column.set_widget(column_header)
column.set_sort_column_id(0)
treeView.append_column(column)
return
# Initial definition.
def _initialize_ui(self):
# Add a Default vertical box
self._vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
self._vbox1.show()
# Add a second vertical box
grid = Gtk.Grid()
title = Gtk.Label(label=_('Welcome To GhostBSD!'), name="Header")
title.set_property("height-request", 50)
self._vbox1.pack_start(title, False, False, 0)
self._vbox1.pack_start(grid, True, True, 0)
grid.set_row_spacing(10)
grid.set_column_spacing(3)
grid.set_column_homogeneous(True)
grid.set_row_homogeneous(True)
grid.set_margin_left(10)
grid.set_margin_right(10)
grid.set_margin_top(10)
grid.set_margin_bottom(10)
# Adding a Scrolling Window
sw = Gtk.ScrolledWindow()
sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
# Adding a treestore and store language in it.
store = Gtk.TreeStore(str)
for line in lang_dictionary:
store.append(None, [line])
treeview = Gtk.TreeView()
treeview.set_model(store)
treeview.set_rules_hint(True)
self._setup_language_columns(treeview)
tree_selection = treeview.get_selection()
tree_selection.set_mode(Gtk.SelectionMode.SINGLE)
tree_selection.connect("changed", self._language_selection)
sw.add(treeview)
sw.show()
grid.attach(sw, 1, 2, 1, 9)
# add text in a label.
vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
vbox2.set_border_width(10)
vbox2.show()
wellcome_text = Gtk.Label(label=welltext)
wellcome_text.set_use_markup(True)
table = Gtk.Table()
table.attach(wellcome_text, 0, 1, 3, 4)
vbox2.pack_start(table, False, False, 5)
image = Gtk.Image()
image.set_from_file(logo)
image.show()
# grid.attach(self.wellcome, 1, 1, 3, 1)
vbox2.pack_start(image, True, True, 5)
grid.attach(vbox2, 2, 2, 2, 9)
grid.show()
@classmethod
def get_model(cls):
return cls()._vbox1
@classmethod
def get_language(cls):
"""Get the selected language."""
return InstallationData.language_code or cls()._language
def save_selection(self):
# Language is now saved in InstallationData automatically
pass
@classmethod
def save_language(cls):
language_code = InstallationData.language_code or cls()._language
if language_code:
localize_system(language_code)
+339
View File
@@ -0,0 +1,339 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GLib, GdkPixbuf
import re
import _thread
from time import sleep
from NetworkMgr.net_api import (
networkdictionary,
connectToSsid,
delete_ssid_wpa_supplicant_config,
nic_status
)
import gettext
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
logo = "/usr/local/lib/install-station/logo.png"
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('/usr/local/lib/install-station/ghostbsd-style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
screen,
cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
class NetworkSetup:
_instance = None
def __new__(cls, button3=None):
"""Implement singleton pattern."""
if cls._instance is None:
cls._instance = super(NetworkSetup, cls).__new__(cls)
cls._instance._initialized = False
return cls._instance
@classmethod
def get_model(cls):
return cls().vbox1
@staticmethod
def wifi_stat(bar):
if bar > 75:
return 'nm-signal-100'
elif bar > 50:
return 'nm-signal-75'
elif bar > 25:
return 'nm-signal-50'
elif bar > 5:
return 'nm-signal-25'
else:
return 'nm-signal-00'
def update_network_detection(self):
cards = self.network_info['cards']
card_list = list(cards.keys())
r = re.compile("wlan")
wlan_list = list(filter(r.match, card_list))
wire_list = list(set(card_list).difference(wlan_list))
cards = self.network_info['cards']
if wire_list:
for card in wire_list:
if cards[card]['state']['connection'] == 'Connected':
wire_text = _('Network card connected to the internet')
self.wire_connection_image.set_from_stock(Gtk.STOCK_YES, 5)
print('Connected True')
self.next_button.set_sensitive(True)
break
else:
wire_text = _('Network card not connected to the internet')
self.wire_connection_image.set_from_stock(Gtk.STOCK_NO, 5)
else:
wire_text = _('No network card detected')
self.wire_connection_image.set_from_stock(Gtk.STOCK_NO, 5)
self.wire_connection_label.set_label(wire_text)
if wlan_list:
for wlan_card in wlan_list:
if cards[wlan_card]['state']['connection'] == 'Connected':
wifi_text = _('WiFi card detected and connected to an access point')
self.wifi_connection_image.set_from_stock(Gtk.STOCK_YES, 5)
break
else:
wifi_text = _('WiFi card detected but not connected to an access point')
self.wifi_connection_image.set_from_stock(Gtk.STOCK_NO, 5)
else:
wifi_text = _("WiFi card not detected or not supported")
self.wifi_connection_image.set_from_stock(Gtk.STOCK_NO, 5)
self.wifi_connection_label.set_label(wifi_text)
def __init__(self, next_button=None):
"""Initialize only once."""
if not self._initialized:
self.next_button = next_button
self._initialize_ui()
self._initialized = True
def _initialize_ui(self):
self.network_info = networkdictionary()
print(self.network_info)
self.vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
self.vbox1.show()
title = Gtk.Label(label=_('Network Setup'), name="Header")
title.set_property("height-request", 50)
self.vbox1.pack_start(title, False, False, 0)
cards = self.network_info['cards']
card_list = list(cards.keys())
r = re.compile("wlan")
wlan_list = list(filter(r.match, card_list))
wire_list = list(set(card_list).difference(wlan_list))
self.wire_connection_label = Gtk.Label()
self.wire_connection_label.set_xalign(0.01)
self.wire_connection_image = Gtk.Image()
self.wifi_connection_label = Gtk.Label()
self.wifi_connection_label.set_xalign(0.01)
self.wifi_connection_image = Gtk.Image()
if wire_list:
for card in wire_list:
if cards[card]['state']['connection'] == 'Connected':
wire_text = _('Network card connected to the internet')
self.wire_connection_image.set_from_stock(Gtk.STOCK_YES, 5)
print('Connected True')
self.next_button.set_sensitive(True)
break
else:
wire_text = _('Network card not connected to the internet')
self.wire_connection_image.set_from_stock(Gtk.STOCK_NO, 5)
else:
wire_text = _('No network card detected')
self.wire_connection_image.set_from_stock(Gtk.STOCK_NO, 5)
self.wire_connection_label.set_label(wire_text)
wlan_card = ""
if wlan_list:
for wlan_card in wlan_list:
if cards[wlan_card]['state']['connection'] == 'Connected':
wifi_text = _('WiFi card detected and connected to an access point')
self.wifi_connection_image.set_from_stock(Gtk.STOCK_YES, 5)
break
else:
wifi_text = _('WiFi card detected but not connected to an access point')
self.wifi_connection_image.set_from_stock(Gtk.STOCK_NO, 5)
else:
wifi_text = _('WiFi card not detected or not supported')
self.wifi_connection_image.set_from_stock(Gtk.STOCK_NO, 5)
self.wifi_connection_label.set_label(wifi_text)
self.connection_box = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=True, spacing=20)
if wlan_card:
# add a default card variable
sw = Gtk.ScrolledWindow()
sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
self.store = Gtk.ListStore(GdkPixbuf.Pixbuf, str, str)
for ssid in self.network_info['cards'][wlan_card]['info']:
ssid_info = self.network_info['cards'][wlan_card]['info'][ssid]
bar = ssid_info[4]
stat = NetworkSetup.wifi_stat(bar)
pixbuf = Gtk.IconTheme.get_default().load_icon(stat, 32, 0)
self.store.append([pixbuf, ssid, f'{ssid_info}'])
treeview = Gtk.TreeView()
treeview.set_model(self.store)
treeview.set_rules_hint(True)
pixbuf_cell = Gtk.CellRendererPixbuf()
pixbuf_column = Gtk.TreeViewColumn('Stat', pixbuf_cell)
pixbuf_column.add_attribute(pixbuf_cell, "pixbuf", 0)
pixbuf_column.set_resizable(True)
treeview.append_column(pixbuf_column)
cell = Gtk.CellRendererText()
column = Gtk.TreeViewColumn('SSID', cell, text=1)
column.set_sort_column_id(1)
treeview.append_column(column)
tree_selection = treeview.get_selection()
tree_selection.set_mode(Gtk.SelectionMode.SINGLE)
tree_selection.connect("changed", self.wifi_setup, wlan_card)
sw.add(treeview)
self.connection_box.pack_start(sw, True, True, 50)
main_grid = Gtk.Grid()
main_grid.set_row_spacing(10)
main_grid.set_column_spacing(10)
main_grid.set_column_homogeneous(True)
main_grid.set_row_homogeneous(True)
self.vbox1.pack_start(main_grid, True, True, 10)
main_grid.attach(self.wire_connection_image, 2, 1, 1, 1)
main_grid.attach(self.wire_connection_label, 3, 1, 8, 1)
main_grid.attach(self.wifi_connection_image, 2, 2, 1, 1)
main_grid.attach(self.wifi_connection_label, 3, 2, 8, 1)
main_grid.attach(self.connection_box, 1, 4, 10, 5)
def wifi_setup(self, tree_selection, wifi_card):
model, treeiter = tree_selection.get_selected()
if treeiter is not None:
ssid = model[treeiter][1]
ssid_info = self.network_info['cards'][wifi_card]['info'][ssid]
caps = ssid_info[6]
print(ssid) # added the code to authenticate.
print(ssid_info)
if caps == 'E' or caps == 'ES':
if f'"{ssid}"' in open("/etc/wpa_supplicant.conf").read():
self.try_to_connect_to_ssid(ssid, ssid_info, wifi_card)
else:
NetworkSetup.open_wpa_supplicant(ssid)
self.try_to_connect_to_ssid(ssid, ssid_info, wifi_card)
else:
if f'"{ssid}"' in open('/etc/wpa_supplicant.conf').read():
self.try_to_connect_to_ssid(ssid, ssid_info, wifi_card)
else:
self.authentication(ssid_info, wifi_card, False)
def add_to_wpa_supplicant(self, _widget, ssid_info, card):
pwd = self.password.get_text()
NetworkSetup.setup_wpa_supplicant(ssid_info[0], ssid_info, pwd)
_thread.start_new_thread(
self.try_to_connect_to_ssid,
(ssid_info[0], ssid_info, card)
)
self.window.hide()
def try_to_connect_to_ssid(self, ssid, ssid_info, card):
if connectToSsid(ssid, card) is False:
delete_ssid_wpa_supplicant_config(ssid)
GLib.idle_add(self.restart_authentication, ssid_info, card)
else:
for _ in list(range(30)):
if nic_status(card) == 'associated':
self.network_info = networkdictionary()
print(self.network_info)
self.update_network_detection()
break
sleep(1)
else:
delete_ssid_wpa_supplicant_config(ssid)
GLib.idle_add(self.restart_authentication, ssid_info, card)
return
def restart_authentication(self, ssid_info, card):
self.authentication(ssid_info, card, True)
def on_check(self, widget):
if widget.get_active():
self.password.set_visibility(True)
else:
self.password.set_visibility(False)
def authentication(self, ssid_info, card, failed):
self.window = Gtk.Window()
self.window.set_title(_("Wi-Fi Network Authentication Required"))
self.window.set_border_width(0)
self.window.set_size_request(500, 200)
box1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
self.window.add(box1)
box1.show()
box2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=10)
box2.set_border_width(10)
box1.pack_start(box2, True, True, 0)
box2.show()
# Creating MBR or GPT drive
if failed:
title = _("{ssid} Wi-Fi Network Authentication failed").format(ssid=ssid_info[0])
else:
title = _("Authentication required by {ssid} Wi-Fi Network").format(ssid=ssid_info[0])
label = Gtk.Label(label=f"<b><span size='large'>{title}</span></b>")
label.set_use_markup(True)
pwd_label = Gtk.Label(label=_("Password:"))
self.password = Gtk.Entry()
self.password.set_visibility(False)
check = Gtk.CheckButton(label=_("Show password"))
check.connect("toggled", self.on_check)
table = Gtk.Table(1, 2, True)
table.attach(label, 0, 5, 0, 1)
table.attach(pwd_label, 1, 2, 2, 3)
table.attach(self.password, 2, 4, 2, 3)
table.attach(check, 2, 4, 3, 4)
box2.pack_start(table, False, False, 0)
box2 = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=10)
box2.set_border_width(5)
box1.pack_start(box2, False, True, 0)
box2.show()
# Add create_scheme button
cancel = Gtk.Button(stock=Gtk.STOCK_CANCEL)
cancel.connect("clicked", self.close)
connect = Gtk.Button(stock=Gtk.STOCK_CONNECT)
connect.connect("clicked", self.add_to_wpa_supplicant, ssid_info, card)
table = Gtk.Table(1, 2, True)
table.set_col_spacings(10)
table.attach(connect, 4, 5, 0, 1)
table.attach(cancel, 3, 4, 0, 1)
box2.pack_end(table, True, True, 5)
self.window.show_all()
return 'Done'
def close(self, _widget):
self.window.hide()
@staticmethod
def setup_wpa_supplicant(ssid, ssid_info, pwd):
if 'RSN' in ssid_info[-1]:
ws = '\nnetwork={'
ws += f'\n ssid="{ssid}"'
ws += '\n key_mgmt=WPA-PSK'
ws += '\n proto=RSN'
ws += f'\n psk="{pwd}"\n'
ws += '}\n'
elif 'WPA' in ssid_info[-1]:
ws = '\nnetwork={'
ws += f'\n ssid="{ssid}"'
ws += '\n key_mgmt=WPA-PSK'
ws += '\n proto=WPA'
ws += f'\n psk="{pwd}"\n'
ws += '}\n'
else:
ws = '\nnetwork={'
ws += f'\n ssid="{ssid}"'
ws += '\n key_mgmt=NONE'
ws += '\n wep_tx_keyidx=0'
ws += f'\n wep_key0={pwd}\n'
ws += '}\n'
wsf = open("/etc/wpa_supplicant.conf", 'a')
wsf.writelines(ws)
wsf.close()
@staticmethod
def open_wpa_supplicant(ssid):
ws = '\nnetwork={'
ws += f'\n ssid={ssid}'
ws += '\n key_mgmt=NONE\n}\n'
with open("/etc/wpa_supplicant.conf", 'a') as wsf:
wsf.writelines(ws)
File diff suppressed because it is too large Load Diff
+160
View File
@@ -0,0 +1,160 @@
#!/usr/bin/env python
import re
import os
from subprocess import Popen, run, PIPE
from install_station.data import pc_sysinstall
def replace_pattern(current, new, file):
parser_file = open(file, 'r').read()
parser_patched = re.sub(current, new, parser_file)
save_parser_file = open(file, 'w')
save_parser_file.writelines(parser_patched)
save_parser_file.close()
def language_dictionary():
langs = Popen(f'{pc_sysinstall} query-langs', shell=True, stdin=PIPE,
stdout=PIPE, universal_newlines=True,
close_fds=True).stdout.readlines()
dictionary = {}
for line in langs:
lang_list = line.rstrip()
lang_name = lang_list.partition(' ')[2]
lang_code = lang_list.partition(' ')[0]
dictionary[lang_name] = lang_code
return dictionary
def localize_system(locale):
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')
replace_pattern('en_US', locale, '/etc/profile')
replace_pattern('en_US', locale, '/usr/share/skel/dot.profile')
if os.path.exists(slick_greeter):
replace_pattern(
'Exec=slick-greeter',
f'Exec=env LANG={locale}.UTF-8 slick-greeter',
slick_greeter
)
elif os.path.exists(gtk_greeter):
replace_pattern(
'Exec=lightdm-gtk-greete',
f'Exec=env LANG={locale}.UTF-8 lightdm-gtk-greeter',
gtk_greeter
)
def keyboard_dictionary():
xkeyboard_layouts = Popen(f'{pc_sysinstall} xkeyboard-layouts', shell=True,
stdout=PIPE,
universal_newlines=True).stdout.readlines()
dictionary = {}
for line in xkeyboard_layouts:
keyboard_list = list(filter(None, line.rstrip().split(' ')))
kb_name = keyboard_list[1].strip()
kb_layouts = keyboard_list[0].strip()
kb_variant = None
dictionary[kb_name] = {'layout': kb_layouts, 'variant': kb_variant}
xkeyboard_variants = Popen(f'{pc_sysinstall} xkeyboard-variants',
shell=True, stdout=PIPE,
universal_newlines=True).stdout.readlines()
for line in xkeyboard_variants:
xkb_variant = line.rstrip()
kb_name = xkb_variant.partition(':')[2].strip()
keyboard_list = list(filter
(None, xkb_variant.partition(':')[0].split()))
kb_layouts = keyboard_list[1].strip()
kb_variant = keyboard_list[0].strip()
dictionary[kb_name] = {'layout': kb_layouts, 'variant': kb_variant}
return dictionary
def keyboard_models():
xkeyboard_models = Popen(f'{pc_sysinstall} xkeyboard-models', shell=True,
stdout=PIPE,
universal_newlines=True).stdout.readlines()
dictionary = {}
for line in xkeyboard_models:
kbm_name = line.rstrip().partition(' ')[2]
kbm_code = line.rstrip().partition(' ')[0]
dictionary[kbm_name] = kbm_code
return dictionary
def change_keyboard(kb_layout, kb_variant=None, kb_model=None):
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:
run(f"setxkbmap -layout {kb_layout} -variant {kb_variant}", shell=True)
elif kb_variant is not None and kb_model is not None:
set_kb_cmd = f"setxkbmap -layout {kb_layout} -variant {kb_variant} " \
f"-model {kb_model}"
run(set_kb_cmd, shell=True)
else:
run(f"setxkbmap -layout {kb_layout}", shell=True)
def set_keyboard(kb_layout, kb_variant=None, kb_model=None):
pass
def timezone_dictionary():
tz_list = Popen(f'{pc_sysinstall} list-tzones', shell=True,
stdout=PIPE, universal_newlines=True).stdout.readlines()
city_list = []
dictionary = {}
last_continent = ''
for zone in tz_list:
zone_list = zone.partition(':')[0].rstrip().split('/')
continent = zone_list[0]
if continent != last_continent:
city_list = []
if len(zone_list) == 3:
city = zone_list[1] + '/' + zone_list[2]
elif len(zone_list) == 4:
city = zone_list[1] + '/' + zone_list[2] + '/' + zone_list[3]
else:
city = zone_list[1]
city_list.append(city)
dictionary[continent] = city_list
last_continent = continent
return dictionary
def zfs_disk_query():
disk_output = Popen(
f"{pc_sysinstall} disk-list",
shell=True,
stdin=PIPE,
stdout=PIPE,
universal_newlines=True,
close_fds=True
)
return disk_output.stdout.readlines()
def zfs_disk_size_query(disk):
disk_info_output = Popen(
f"{pc_sysinstall} disk-info {disk}",
shell=True,
stdin=PIPE,
stdout=PIPE,
universal_newlines=True,
close_fds=True
)
return disk_info_output.stdout.readlines()[3].partition('=')[2]
def set_admin_user(username, name, password, shell, homedir, hostname):
# 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" \
f" -s {shell} -m -d {homedir} -g wheel,operator"
run(cmd, shell=True)
run(f"sysrc hostname={hostname}", shell=True)
run(f"hostname {hostname}", shell=True)
+624
View File
@@ -0,0 +1,624 @@
from gi.repository import Gtk, Gdk
import gettext
from install_station.common import password_strength
from install_station.data import InstallationData, zfs_datasets, be_name, logo
from install_station.partition import bios_or_uefi
from install_station.system_calls import (
zfs_disk_query,
zfs_disk_size_query,
)
from install_station.interface_controller import Button
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('/usr/local/lib/install-station/ghostbsd-style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
screen,
cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
class ZFS:
"""
Utility class for ZFS configuration and disk management following the utility class pattern.
This class provides a GTK+ interface for configuring ZFS installations including:
- Disk selection and validation
- Pool type configuration (stripe, mirror, RAIDZ1/2/3)
- Partition scheme selection (GPT/MBR)
- Disk encryption setup with password verification
- ZFS pool name configuration
The class follows a utility pattern with class methods and variables for state management,
designed to integrate with the InstallationData system for configuration persistence.
"""
# Class variables instead of instance variables
zfs_disk_list = []
pool_type = 'stripe'
scheme = 'GPT'
zpool = False
disk_encrypt = False
mirror = 'single disk'
vbox1 = None
# UI elements as class variables
pool = None
password = None
repassword = None
mirrorTips = None
strenght_label = None
img = None
check_cell = None
store = None
@classmethod
def save_selection(cls):
"""
Save the current ZFS configuration to InstallationData.
Validates required fields and generates ZFS configuration data including:
- Pool name and type (stripe, mirror, RAIDZ1/2/3)
- Disk partitioning scheme and encryption settings
- Boot environment and dataset configuration
Raises:
ValueError: If required fields are missing or invalid
"""
# Validate required fields are populated
if not cls.zfs_disk_list:
raise ValueError("No disks selected for ZFS configuration")
if cls.zpool and not cls.pool.get_text().strip():
raise ValueError("Pool name cannot be empty when zpool is enabled")
if cls.disk_encrypt and not cls.password.get_text().strip():
raise ValueError("Password cannot be empty when disk encryption is enabled")
size = int(cls.zfs_disk_list[0].partition('-')[2].rstrip()) - 512
swap = 0
zfs_num = size - swap
if cls.disk_encrypt is True:
dgeli = '.eli'
else:
dgeli = ''
# Store configuration data in InstallationData instead of writing to file
InstallationData.zfs_config_data = []
if cls.zpool is True:
InstallationData.zfs_config_data.append(f"zpoolName={cls.pool.get_text()}\n")
else:
InstallationData.zfs_config_data.append("#zpoolName=None\n")
InstallationData.zfs_config_data.append(f"beName={be_name}\n")
InstallationData.zfs_config_data.append('ashift=12\n\n')
disk = cls.zfs_disk_list[0].partition('-')[0].rstrip()
InstallationData.zfs_config_data.append(f'disk0={disk}\n')
InstallationData.zfs_config_data.append('partition=ALL\n')
InstallationData.zfs_config_data.append(f'partscheme={cls.scheme}\n')
InstallationData.zfs_config_data.append('commitDiskPart\n\n')
if cls.pool_type == 'none':
pool_disk = '\n'
else:
zfs_disk = cls.zfs_disk_list
disk_len = len(zfs_disk) - 1
num = 1
mirror_dsk = ''
while disk_len != 0:
mirror_dsk += ' ' + zfs_disk[num].partition('-')[0].rstrip()
print(mirror_dsk)
num += 1
disk_len -= 1
pool_disk = f' ({cls.pool_type}:{mirror_dsk})\n'
if bios_or_uefi() == "UEFI":
zfs_num = zfs_num - 100
else:
zfs_num = zfs_num - 1
# adding zero to use remaining space
zfs_part = f'disk0-part=ZFS{dgeli} {zfs_num} {zfs_datasets}{pool_disk}'
InstallationData.zfs_config_data.append(zfs_part)
if swap != 0:
InstallationData.zfs_config_data.append('disk0-part=swap 0 none\n')
if cls.disk_encrypt is True:
InstallationData.zfs_config_data.append(f'encpass={cls.password.get_text()}\n')
else:
InstallationData.zfs_config_data.append('#encpass=None\n')
InstallationData.zfs_config_data.append('commitDiskLabel\n')
@classmethod
def scheme_selection(cls, combobox):
"""
Handle partition scheme selection from combo box.
Args:
combobox: ComboBox widget containing scheme options (GPT/MBR)
"""
model = combobox.get_model()
index = combobox.get_active()
data = model[index][0]
cls.scheme = data.partition(':')[0]
@classmethod
def mirror_selection(cls, combobox):
"""
Handle pool type selection and update UI accordingly.
Sets the pool type (stripe, mirror, RAIDZ1/2/3) and updates the tip text
and next button sensitivity based on the number of selected disks.
Args:
combobox: ComboBox widget containing pool type options
"""
model = combobox.get_model()
index = combobox.get_active()
data = model[index][0] # Get the internal value (English)
cls.mirror = data
if cls.mirror == "1+ disks Stripe":
cls.pool_type = 'stripe'
cls.mirrorTips.set_text(
_("Please select 1 or more drive for stripe (select the smallest disk first)"))
if len(cls.zfs_disk_list) >= 1:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "2+ disks Mirror":
cls.pool_type = 'mirror'
mir_msg1 = _("Please select 2 drive for mirroring (select the smallest disk first)")
cls.mirrorTips.set_text(mir_msg1)
if len(cls.zfs_disk_list) >= 2:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "3 disks RAIDZ1":
cls.pool_type = 'raidz1'
cls.mirrorTips.set_text(_("Please select 3 drive for RAIDZ1 (select the smallest disk first)"))
if len(cls.zfs_disk_list) == 3:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "4 disks RAIDZ2":
cls.pool_type = 'raidz2'
cls.mirrorTips.set_text(_("Please select 4 drive for RAIDZ2 (select the smallest disk first)"))
if len(cls.zfs_disk_list) == 4:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "5 disks RAIDZ3":
cls.pool_type = 'raidz3'
cls.mirrorTips.set_text(_("Please select 5 drive for RAIDZ3 (select the smallest disk first)"))
if len(cls.zfs_disk_list) == 5:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
@classmethod
def on_check_poll(cls, widget):
"""
Handle custom pool name checkbox toggle.
Enables or disables the pool name entry field based on checkbox state.
Args:
widget: CheckButton widget for pool name enable/disable
"""
if widget.get_active():
cls.pool.set_sensitive(True)
cls.zpool = True
else:
cls.pool.set_sensitive(False)
cls.zpool = False
@classmethod
def on_check_encrypt(cls, widget):
"""
Handle disk encryption checkbox toggle.
Enables or disables password fields and updates next button sensitivity
based on encryption state and current disk selection.
Args:
widget: CheckButton widget for disk encryption enable/disable
"""
if widget.get_active():
cls.password.set_sensitive(True)
cls.repassword.set_sensitive(True)
cls.disk_encrypt = True
Button.next_button.set_sensitive(False)
else:
cls.password.set_sensitive(False)
cls.repassword.set_sensitive(False)
cls.disk_encrypt = False
if cls.mirror == "1+ disks Stripe":
if len(cls.zfs_disk_list) >= 1:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "2+ disks Mirror":
if len(cls.zfs_disk_list) >= 2:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "3 disks RAIDZ1":
if len(cls.zfs_disk_list) == 3:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "4 disks RAIDZ2":
if len(cls.zfs_disk_list) == 4:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "5 disks RAIDZ3":
if len(cls.zfs_disk_list) == 5:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
@classmethod
def initialize(cls):
"""
Initialize the ZFS configuration UI following the utility class pattern.
Creates the main interface including:
- Disk selection tree view with checkboxes
- Pool type selection (stripe, mirror, RAIDZ1/2/3)
- Pool name configuration
- Partition scheme selection (GPT/MBR)
- Disk encryption options with password fields
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()
# Chose disk
sw = Gtk.ScrolledWindow(hexpand=True, vexpand=True)
sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
cls.store = Gtk.TreeStore(str, str, str, 'gboolean')
for disk in zfs_disk_query():
dsk = disk.partition(':')[0].rstrip()
dsk_name = disk.partition(':')[2].rstrip()
dsk_size = zfs_disk_size_query(dsk).rstrip()
cls.store.append(None, [dsk, dsk_size, dsk_name, False])
treeview = Gtk.TreeView()
treeview.set_model(cls.store)
treeview.set_rules_hint(True)
cls.check_cell = Gtk.CellRendererToggle()
cls.check_cell.set_property('activatable', True)
cls.check_cell.connect('toggled', cls.col1_toggled_cb, cls.store)
cell = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(None, cell, text=0)
column_header = Gtk.Label(label=_('Disk'))
column_header.set_use_markup(True)
column_header.show()
column.set_widget(column_header)
column.set_sort_column_id(0)
cell2 = Gtk.CellRendererText()
column2 = Gtk.TreeViewColumn(None, cell2, text=0)
column_header2 = Gtk.Label(label=_('Size(MB)'))
column_header2.set_use_markup(True)
column_header2.show()
column2.set_widget(column_header2)
cell3 = Gtk.CellRendererText()
column3 = Gtk.TreeViewColumn(None, cell3, text=0)
column_header3 = Gtk.Label(label=_('Name'))
column_header3.set_use_markup(True)
column_header3.show()
column3.set_widget(column_header3)
column1 = Gtk.TreeViewColumn(_("Check"), cls.check_cell)
column1.add_attribute(cls.check_cell, "active", 3)
column.set_attributes(cell, text=0)
column2.set_attributes(cell2, text=1)
column3.set_attributes(cell3, text=2)
treeview.append_column(column1)
treeview.append_column(column)
treeview.append_column(column2)
treeview.append_column(column3)
tree_selection = treeview.get_selection()
tree_selection.set_mode(Gtk.SelectionMode.SINGLE)
sw.add(treeview)
sw.show()
cls.mirrorTips = Gtk.Label(label=_('Please select one drive'))
cls.mirrorTips.set_justify(Gtk.Justification.LEFT)
cls.mirrorTips.set_alignment(0.01, 0.5)
# Mirror, raidz and stripe
cls.mirror = 'none'
mirror_label = Gtk.Label(label=_('<b>Pool Type</b>'))
mirror_label.set_use_markup(True)
mirror_box = Gtk.ComboBox()
mirror_store = Gtk.ListStore(str, str) # value, display_text
mirror_store.append(["1+ disks Stripe", _("1+ disks Stripe")])
mirror_store.append(["2+ disks Mirror", _("2+ disks Mirror")])
mirror_store.append(["3 disks RAIDZ1", _("3 disks RAIDZ1")])
mirror_store.append(["4 disks RAIDZ2", _("4 disks RAIDZ2")])
mirror_store.append(["5 disks RAIDZ3", _("5 disks RAIDZ3")])
mirror_box.set_model(mirror_store)
renderer = Gtk.CellRendererText()
mirror_box.pack_start(renderer, True)
mirror_box.add_attribute(renderer, "text", 1) # Display column 1 (translated text)
mirror_box.connect('changed', cls.mirror_selection)
mirror_box.set_active(0)
# Pool Name
cls.zpool = False
pool_name_label = Gtk.Label(label=_('<b>Pool Name</b>'))
pool_name_label.set_use_markup(True)
cls.pool = Gtk.Entry()
cls.pool.set_text('zroot')
# Creating MBR or GPT drive
scheme_label = Gtk.Label(label='<b>Partition Scheme</b>')
scheme_label.set_use_markup(True)
# Adding a combo box to selecting MBR or GPT sheme.
cls.scheme = 'GPT'
shemebox = Gtk.ComboBoxText()
shemebox.append_text("GPT")
shemebox.append_text("MBR")
shemebox.connect('changed', cls.scheme_selection)
shemebox.set_active(0)
if bios_or_uefi() == "UEFI":
shemebox.set_sensitive(False)
else:
shemebox.set_sensitive(True)
# GELI Disk encryption
cls.disk_encrypt = False
encrypt_check = Gtk.CheckButton(label=_("Encrypt Disk"))
encrypt_check.connect("toggled", cls.on_check_encrypt)
encrypt_check.set_sensitive(True)
# password
cls.passwd_label = Gtk.Label(label=_("Password"))
cls.password = Gtk.Entry()
cls.password.set_sensitive(False)
cls.password.set_visibility(False)
cls.password.connect("changed", password_strength)
cls.strenght_label = Gtk.Label()
cls.strenght_label.set_alignment(0.1, 0.5)
cls.vpasswd_label = Gtk.Label(label=_("Verify it"))
cls.repassword = Gtk.Entry()
cls.repassword.set_sensitive(False)
cls.repassword.set_visibility(False)
cls.repassword.connect("changed", cls.password_verification)
# set image for password matching
cls.img = Gtk.Image()
cls.img.set_alignment(0.2, 0.5)
# table = Gtk.Table(12, 12, True)
grid = Gtk.Grid()
grid.set_row_spacing(10)
# grid.set_column_homogeneous(True)
# grid.set_row_homogeneous(True)
# grid.attach(Title, 1, 1, 10, 1)
grid.attach(mirror_label, 1, 2, 1, 1)
grid.attach(mirror_box, 2, 2, 1, 1)
grid.attach(pool_name_label, 7, 2, 2, 1)
grid.attach(cls.pool, 9, 2, 2, 1)
grid.attach(cls.mirrorTips, 1, 3, 8, 1)
# grid.attach(zfs4kcheck, 9, 3, 2, 1)
grid.attach(sw, 1, 4, 10, 3)
# grid.attach(scheme_label, 1, 9, 1, 1)
# grid.attach(shemebox, 2, 9, 1, 1)
# grid.attach(cls.swap_encrypt_check, 9, 15, 11, 12)
# grid.attach(swap_mirror_check, 9, 15, 11, 12)
# grid.attach(encrypt_check, 2, 8, 2, 1)
# grid.attach(cls.passwd_label, 1, 9, 1, 1)
# grid.attach(cls.password, 2, 9, 2, 1)
# grid.attach(cls.strenght_label, 4, 9, 2, 1)
# grid.attach(cls.vpasswd_label, 1, 10, 1, 1)
# grid.attach(cls.repassword, 2, 10, 2, 1)
# grid.attach(cls.img, 4, 10, 2, 1)
cls.vbox1.pack_start(grid, True, True, 10)
return
@classmethod
def get_model(cls):
"""
Return the GTK widget model for the ZFS configuration interface.
Creates and initializes the UI if it doesn't exist yet.
Returns:
Gtk.Box: The main container widget for the ZFS configuration interface
"""
if cls.vbox1 is None:
cls.initialize()
return cls.vbox1
@classmethod
def check_if_small_disk(cls, size):
"""
Check if any selected disk is larger than the specified size.
Used to enforce the requirement that the smallest disk must be selected first
for ZFS pool configurations.
Args:
size: Size in MB to compare against selected disks
Returns:
bool: True if any selected disk is larger than the specified size
"""
if len(cls.zfs_disk_list) != 0:
for line in cls.zfs_disk_list:
if int(line.partition('-')[2]) > int(size):
return True
else:
return False
else:
return False
@classmethod
def col1_toggled_cb(cls, _cell, path, model):
"""
Handle disk selection checkbox toggle events.
Manages the disk selection list and updates next button sensitivity
based on pool type requirements. Enforces the rule that the smallest
disk must be selected first.
Args:
_cell: CellRendererToggle that was clicked (unused)
path: TreePath of the toggled row
model: TreeStore model containing disk data
Returns:
bool: Always returns True to indicate the event was handled
"""
model[path][3] = not model[path][3]
if model[path][3] is False:
cls.zfs_disk_list.remove(model[path][0] + "-" + model[path][1])
if cls.mirror == "1+ disks Stripe":
if len(cls.zfs_disk_list) >= 1:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "2+ disks Mirror":
if len(cls.zfs_disk_list) >= 2:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "3 disks RAIDZ1":
if len(cls.zfs_disk_list) == 3:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "4 disks RAIDZ2":
if len(cls.zfs_disk_list) == 4:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "5 disks RAIDZ3":
if len(cls.zfs_disk_list) == 5:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
else:
if cls.check_if_small_disk(model[path][1]) is False:
cls.zfs_disk_list.extend([model[path][0] + "-" + model[path][1]])
if cls.mirror == "1+ disks Stripe":
if len(cls.zfs_disk_list) >= 1:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "2+ disks Mirror":
if len(cls.zfs_disk_list) >= 2:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "3 disks RAIDZ1":
if len(cls.zfs_disk_list) == 3:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "4 disks RAIDZ2":
if len(cls.zfs_disk_list) == 4:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "5 disks RAIDZ3":
if len(cls.zfs_disk_list) == 5:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
else:
cls.check_cell.set_sensitive(False)
cls.small_disk_warning()
print(cls.zfs_disk_list)
return True
@classmethod
def small_disk_warning(cls):
"""
Display a warning dialog when disks are selected out of size order.
Shows a dialog informing the user that the smallest disk must be
selected first and offers to reset all selections.
"""
window = Gtk.Window()
window.set_title(_("Warning"))
window.set_border_width(0)
# window.set_size_request(480, 200)
window.set_icon_from_file(logo)
box1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
window.add(box1)
box1.show()
box2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=10)
box2.set_border_width(10)
box1.pack_start(box2, True, True, 0)
box2.show()
warning_text = _("Smallest disk need to be SELECTED first!\n")
warning_text += _("All the disk selected will reset.")
label = Gtk.Label(label=warning_text)
# Add button
box2.pack_start(label, True, True, 0)
bbox = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL, homogeneous=False, spacing=10)
bbox.set_border_width(5)
button = Gtk.Button(stock=Gtk.STOCK_OK)
button.connect("clicked", cls.resset_selection, window)
bbox.add(button)
box2.pack_end(bbox, True, True, 5)
window.show_all()
@classmethod
def resset_selection(cls, _widget, window):
"""
Reset all disk selections and close the warning dialog.
Clears the disk selection list and unchecks all checkboxes in the tree view.
Args:
_widget: Button widget that triggered the reset (unused)
window: Warning dialog window to close
"""
cls.zfs_disk_list = []
rows = len(cls.store)
for row in range(0, rows):
cls.store[row][3] = False
row += 1
cls.check_cell.set_sensitive(True)
window.hide()
@classmethod
def password_verification(cls, _widget):
"""
Verify that password and confirmation password fields match.
Updates the verification image and next button sensitivity based on
password match status and current disk selection requirements.
Args:
_widget: Entry widget that triggered the verification (unused)
"""
if cls.password.get_text() == cls.repassword.get_text():
cls.img.set_from_stock(Gtk.STOCK_YES, 5)
if cls.mirror == "1+ disks Stripe":
if len(cls.zfs_disk_list) >= 1:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "2+ disks Mirror":
if len(cls.zfs_disk_list) >= 2:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "3 disks RAIDZ1":
if len(cls.zfs_disk_list) == 3:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "4 disks RAIDZ2":
if len(cls.zfs_disk_list) == 4:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
elif cls.mirror == "5 disks RAIDZ3":
if len(cls.zfs_disk_list) == 5:
Button.next_button.set_sensitive(True)
else:
Button.next_button.set_sensitive(False)
else:
cls.img.set_from_stock(Gtk.STOCK_NO, 5)
Button.next_button.set_sensitive(False)
+293
View File
@@ -0,0 +1,293 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk, Gdk, GdkPixbuf
import gettext
from install_station.system_calls import language_dictionary
from install_station.interface_controller import Interface
from install_station.data import InstallationData
from install_station.window import Window
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
_ = gettext.gettext
lang_dictionary = language_dictionary()
Messages = _("""To run GhostBSD without installing, select "Try GhostBSD."
To install GhostBSD on your computer hard disk drive, click "Install GhostBSD."
Note: Language selection only works when selecting "Try GhostBSD."
When installing GhostBSD, the installation program is only in English.""")
cssProvider = Gtk.CssProvider()
cssProvider.load_from_path('/usr/local/lib/install-station/ghostbsd-style.css')
screen = Gdk.Screen.get_default()
styleContext = Gtk.StyleContext()
styleContext.add_provider_for_screen(
screen,
cssProvider,
Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
)
class Welcome:
"""
Utility class for the welcome screen and initial mode selection following the utility class pattern.
This class provides a GTK+ interface for the initial GhostBSD welcome screen including:
- Language selection from available system languages
- Mode selection between "Install GhostBSD" and "Try GhostBSD"
- Visual elements with images and instructional text
- 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
what = None
language = None
install_ghostbsd = None
try_ghostbsd = None
vbox1 = None
@classmethod
def language_selection(cls, tree_selection):
"""
Handle language selection from the treeview.
Extracts the selected language from the tree view and updates both
class variables and InstallationData with the language name and code.
Also updates the UI text to reflect the new language selection.
Args:
tree_selection: TreeSelection widget containing the user's language choice
"""
model, tree_iter = tree_selection.get_selected()
if tree_iter is not None:
value = model[tree_iter][0]
language_code = lang_dictionary[value]
cls.language = language_code
InstallationData.language = value
InstallationData.language_code = language_code
print(f"Language selected: {value} ({language_code})")
# Update gettext to use the new language
import os
os.environ['LANGUAGE'] = language_code
gettext.bindtextdomain('install-station', '/usr/local/share/locale')
gettext.textdomain('install-station')
# Update the UI text with new translations
cls.update_ui_text()
return
@classmethod
def update_ui_text(cls):
"""
Update all UI text elements with new translations after language change.
"""
# Reload the gettext function to pick up new locale
_ = gettext.gettext
# Update UI elements if they exist
if hasattr(cls, 'install_button') and cls.install_button:
cls.install_button.set_label(_('Install GhostBSD'))
if hasattr(cls, 'try_button') and cls.try_button:
cls.try_button.set_label(_('Try GhostBSD'))
if hasattr(cls, 'text_label') and cls.text_label:
cls.text_label.set_text(_("""To run GhostBSD without installing, select "Try GhostBSD."
To install GhostBSD on your computer hard disk drive, click "Install GhostBSD."
Note: Language selection only works when selecting "Try GhostBSD."
When installing GhostBSD, the installation program is only in English."""))
if hasattr(cls, 'language_column_header') and cls.language_column_header:
cls.language_column_header.set_text(_('Language'))
Window.set_title(_("Welcome to GhostBSD"))
@classmethod
def language_columns(cls, treeview):
"""
Configure the language selection treeview with appropriate columns.
Creates a single column with a "Language" header for displaying
available languages in the tree view.
Args:
treeview: TreeView widget to configure with language column
"""
cell = Gtk.CellRendererText()
column = Gtk.TreeViewColumn(None, cell, text=0)
column_header = Gtk.Label(label=_('Language'))
column_header.set_use_markup(True)
column_header.show()
column.set_widget(column_header)
# Store reference for updating
cls.language_column_header = column_header
column.set_sort_column_id(0)
treeview.append_column(column)
@classmethod
def save_selection(cls):
"""
Save the current language selection.
This method is maintained for compatibility but language selection
is now automatically saved to InstallationData when chosen.
"""
# Language is now saved in InstallationData automatically
pass
@classmethod
def install_system(cls, _widget):
"""
Handle "Install GhostBSD" button click.
Sets the installation mode to 'install' and navigates to the
installation workflow pages.
Args:
_widget: Button widget that triggered the action (unused)
"""
cls.what = 'install'
InstallationData.install_mode = 'install'
Interface.next_install_page()
@classmethod
def try_system(cls, _widget):
"""
Handle "Try GhostBSD" button click.
Sets the installation mode to 'try' and navigates to the
live system setup pages (typically network configuration).
Args:
_widget: Button widget that triggered the action (unused)
"""
cls.what = 'try'
InstallationData.install_mode = 'try'
Interface.next_setup_page()
@classmethod
def get_what(cls):
"""
Get the current installation mode.
Returns the installation mode from InstallationData if available,
otherwise falls back to the class variable.
Returns:
str: Current installation mode ('install' or 'try')
"""
return InstallationData.install_mode or cls.what
@classmethod
def initialize(cls):
"""
Initialize the welcome screen UI following the utility class pattern.
Creates the main interface including:
- Language selection tree view on the left side
- Install and Try buttons with images on the right side
- Instructional text explaining the options
- Grid-based layout with proper spacing and margins
This method is called automatically by get_model() when the interface is first accessed.
"""
cls.what = None
cls.language = None
cls.vbox1 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
cls.vbox1.show()
main_grid = Gtk.Grid()
cls.vbox1.pack_start(main_grid, True, True, 0)
sw = Gtk.ScrolledWindow()
sw.set_shadow_type(Gtk.ShadowType.ETCHED_IN)
sw.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
store = Gtk.TreeStore(str)
for line in lang_dictionary:
store.append(None, [line])
treeview = Gtk.TreeView()
treeview.set_model(store)
treeview.set_rules_hint(True)
cls.language_columns(treeview)
tree_selection = treeview.get_selection()
tree_selection.set_mode(Gtk.SelectionMode.SINGLE)
tree_selection.connect("changed", cls.language_selection)
sw.add(treeview)
sw.show()
vbox2 = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, homogeneous=False, spacing=0)
vbox2.set_border_width(10)
vbox2.show()
pix_buf1 = GdkPixbuf.Pixbuf().new_from_file_at_scale(
filename='/usr/local/lib/install-station/laptop.png',
width=190,
height=190,
preserve_aspect_ratio=True
)
image1 = Gtk.Image.new_from_pixbuf(pix_buf1)
image1.show()
pix_buf2 = GdkPixbuf.Pixbuf().new_from_file_at_scale(
filename='/usr/local/lib/install-station/disk.png',
width=120,
height=120,
preserve_aspect_ratio=True)
image2 = Gtk.Image.new_from_pixbuf(pix_buf2)
image2.show()
install_button = Gtk.Button(label=_('Install GhostBSD'), image=image1,
image_position=2)
install_button.set_always_show_image(True)
install_button.connect("clicked", cls.install_system)
try_button = Gtk.Button(label=_('Try GhostBSD'), image=image2,
image_position=2)
try_button.set_always_show_image(True)
try_button.connect("clicked", cls.try_system)
text_label = Gtk.Label(label=Messages)
text_label.set_line_wrap(True)
# Store references for updating
cls.install_button = install_button
cls.try_button = try_button
cls.text_label = text_label
right_grid = Gtk.Grid()
right_grid.set_row_spacing(10)
right_grid.set_column_spacing(2)
right_grid.set_column_homogeneous(True)
right_grid.set_row_homogeneous(True)
right_grid.set_margin_left(10)
right_grid.set_margin_right(10)
right_grid.set_margin_top(10)
right_grid.set_margin_bottom(10)
right_grid.attach(install_button, 1, 1, 1, 5)
right_grid.attach(try_button, 2, 1, 1, 5)
right_grid.attach(text_label, 1, 6, 2, 5)
main_grid.set_row_spacing(10)
main_grid.set_column_spacing(4)
main_grid.set_column_homogeneous(True)
main_grid.set_row_homogeneous(True)
main_grid.set_margin_left(10)
main_grid.set_margin_right(10)
main_grid.set_margin_top(10)
main_grid.set_margin_bottom(10)
main_grid.attach(sw, 1, 1, 1, 10)
main_grid.attach(right_grid, 2, 1, 3, 10)
main_grid.show()
@classmethod
def get_model(cls):
"""
Return the GTK widget model for the welcome screen interface.
Returns the main container widget that was created during initialization.
Returns:
Gtk.Box: The main container widget for the welcome screen interface
"""
if cls.vbox1 is None:
cls.initialize()
return cls.vbox1
+49
View File
@@ -0,0 +1,49 @@
import gi
gi.require_version('Gtk', '3.0')
from gi.repository import Gtk
class Window:
window = Gtk.Window()
@classmethod
def connect(cls, signal, callback):
return cls.window.connect(signal, callback)
@classmethod
def set_border_width(cls, width):
return cls.window.set_border_width(width)
@classmethod
def set_default_size(cls, width, height):
return cls.window.set_default_size(width, height)
@classmethod
def set_size_request(cls, width, height):
return cls.window.set_size_request(width, height)
@classmethod
def set_title(cls, title):
return cls.window.set_title(title)
@classmethod
def set_icon_from_file(cls, filename):
return cls.window.set_icon_from_file(filename)
@classmethod
def add(cls, widget):
return cls.window.add(widget)
@classmethod
def show_all(cls):
return cls.window.show_all()
@classmethod
def hide(cls):
"""Hide the window."""
return cls.window.hide()
@classmethod
def __getattr__(cls, name):
"""Fallback for any methods not explicitly defined."""
return getattr(cls.window, name)