feat: implement real-name to username auto-derivation, add root privilege check, and improve manual partitioning UI with smart defaults.

This commit is contained in:
2026-06-01 14:36:03 +03:00
parent 64db88d12e
commit 09ffa5346d
13 changed files with 264 additions and 67 deletions
+1 -1
View File
@@ -15,7 +15,7 @@ pisi_logo_dark = "assets/pisi-logo-dark.png"
pisi_logo_light = "assets/pisi-logo-light.png"
# Title of the installation window
title = "PiSi Linux Installation"
title = "YALI Installation Wizard"
# Subtitle displayed in the left sidebar
subtitle = "YALI"
+1 -1
View File
@@ -20,7 +20,7 @@ pisi_logo_dark = "assets/pisi-logo-dark.png"
pisi_logo_light = "assets/pisi-logo-light.png"
# Kurulum penceresinin başlığı (pencere çubuğu)
title = "PiSi Linux Kurulumu"
title = "YALI Kurulum Aracı"
# Sol kenar çubuğunda görüntülenen dağıtım ismi
subtitle = "YALI"
+6
View File
@@ -45,6 +45,7 @@ erase_warning = "WARNING: All data on the selected disk will be erased"
users = "Users"
users_title = "User Account"
users_description = "Create a user account for your system."
real_name_label = "Full Name"
username_label = "Username"
password_label = "Password"
password_confirm_label = "Confirm password"
@@ -154,6 +155,10 @@ pwd_very_strong = "Very strong"
use_same_for_admin = "Use the same password for the administrator (root) account"
# ── Checker ───────────────────────────────────────────────
check_root = "Permissions"
check_root_pass = "Running with root privileges."
check_root_fail = "Not running as root! The installer requires root privileges. Please run with 'sudo yali-rs' or 'pkexec yali-rs'."
check_disk_space = "Disk Space"
check_disk_fail = "Maximum disk size is %{size} GB. Minimum 10 GB is required for installation."
check_disk_warn = "%{size} GB disk available. 20 GB or more recommended."
@@ -248,6 +253,7 @@ mp_error_empty_mountpoint = "Mount point cannot be empty."
mp_plan_valid = "✓ Partition plan is valid. You can proceed."
mp_select_disk_first = "← Select a disk first."
mp_add_dialog_hint = "/ or /home or swap"
mp_select_hint = "Select…"
mp_add_btn = "✓ Add"
mp_size_hint = "Size (MB, 0=remaining):"
mp_delete_confirm_yes = "✓ Yes, Delete"
+7 -1
View File
@@ -45,6 +45,7 @@ erase_warning = "UYARI: Seçilen diskteki tüm veriler silinecektir"
users = "Kullanıcı"
users_title = "Kullanıcı Bilgileri"
users_description = "Sisteminiz için bir kullanıcı hesabı oluşturun."
real_name_label = "Ad Soyad"
username_label = "Kullanıcı adı"
password_label = "Şifre"
password_confirm_label = "Şifre (tekrar)"
@@ -86,7 +87,7 @@ retry_install = "Yeniden Dene"
finish_next = "Tamamlandı →"
# ── Finish ────────────────────────────────────────────────
finish_description = "PisiLinux başarıyla kuruldu. Şimdi sisteminizi yeniden başlatabilirsiniz."
finish_description = "Pisi GNU/Linux başarıyla kuruldu. Şimdi sisteminizi yeniden başlatabilirsiniz."
finish_restart = "🔄 Yeniden Başlat"
finish_live = "Canlı Masaüstüne Dön"
install_in_progress = "Kurulum devam ediyor, lütfen bekleyin…"
@@ -131,6 +132,10 @@ pwd_strong = "Güçlü"
pwd_very_strong = "Çok güçlü"
# ── Checker ───────────────────────────────────────────────
check_root = "Yetki"
check_root_pass = "Root yetkisi mevcut."
check_root_fail = "Root yetkisi yok! Kurulum için yönetici (root) olarak çalıştırılmalıdır. Lütfen 'sudo yali-rs' veya 'pkexec yali-rs' ile tekrar deneyin."
check_disk_space = "Disk Alanı"
check_disk_fail = "Maksimum disk boyutu %{size} GB. Kurulum için minimum 10 GB gerekli."
check_disk_warn = "%{size} GB disk mevcut. 20 GB veya üzeri önerilir."
@@ -220,6 +225,7 @@ mp_error_empty_mountpoint = "Bağlama noktası boş olamaz."
mp_plan_valid = "✓ Bölüm planı geçerli. İleri tuşuna basabilirsiniz."
mp_select_disk_first = "← Önce bir disk seçin."
mp_add_dialog_hint = "/ veya /home veya swap"
mp_select_hint = "Seç…"
mp_add_btn = "✓ Ekle"
mp_size_hint = "Boyut (MB, 0=kalan):"
mp_delete_confirm_yes = "✓ Evet, Sil"
+24
View File
@@ -39,6 +39,7 @@ impl CheckResult {
pub fn run_all() -> Vec<CheckResult> {
vec![
check_root(),
check_ram(),
check_disk_space(),
check_internet(),
@@ -234,6 +235,29 @@ fn check_live_env() -> CheckResult {
}
}
/// Kök kullanıcı (root) yetkisi kontrolü.
/// mount, swapon, parted gibi komutlar root gerektirdiğinden
/// installer'ın root olarak çalıştırılması zorunludur.
fn check_root() -> CheckResult {
let is_root = std::process::Command::new("id")
.arg("-u")
.output()
.ok()
.and_then(|o| {
if o.status.success() {
Some(String::from_utf8_lossy(&o.stdout).trim() == "0")
} else {
None
}
})
.unwrap_or(false);
if is_root {
CheckResult::pass(t!("check_root"), t!("check_root_pass"))
} else {
CheckResult::fail(t!("check_root"), t!("check_root_fail"))
}
}
/// NVMe ve diğer modern disk arayüzlerinin çekirdek desteğini kontrol eder.
fn check_nvme_support() -> CheckResult {
// /dev/nvme* cihazları var mı?
+45 -13
View File
@@ -2,6 +2,48 @@ use eframe::egui;
use sysinfo::System;
use rust_i18n::t;
/// Aktif masaüstü ortamını algılar.
/// Önce `XDG_CURRENT_DESKTOP` ortam değişkenine bakar (normal kullanıcı).
/// sudo ile çalışıldığında env var kaybolur, bu durumda `pgrep` ile
/// çalışan desktop session process'lerini kontrol eder.
pub fn detect_desktop_environment() -> String {
// 1. XDG_CURRENT_DESKTOP ortam değişkeni
if let Ok(de) = std::env::var("XDG_CURRENT_DESKTOP") {
let de = de.trim().to_lowercase();
if !de.is_empty() {
return de;
}
}
// 2. sudo ile çalışıldıysa env kaybolur — aktif session process'lerini kontrol et
let de_map: &[(&str, &[&str])] = &[
("plasma", &["plasmashell", "startplasma"]),
("xfce", &["xfce4-session", "xfdesktop"]),
("gnome", &["gnome-session"]),
("cinnamon", &["cinnamon-session"]),
("budgie", &["budgie-wm"]),
("lxqt", &["lxqt-session"]),
("mate", &["mate-session"]),
("lumina", &["lumina-desktop"]),
("lxde", &["lxsession"]),
];
for (de_name, procs) in de_map {
for proc in *procs {
if let Ok(out) = std::process::Command::new("pgrep")
.args(["-x", proc])
.output()
{
if out.status.success() {
return de_name.to_string();
}
}
}
}
// 3. Hiçbiri bulunamazsa varsayılan
"plasma".to_string()
}
/// Disk adı ve bölüm numarasından tam aygıt yolunu oluşturur.
///
/// - SATA/SCSI: `/dev/sda` + 1 → `/dev/sda1`
@@ -154,6 +196,7 @@ pub struct GlobalState {
pub logical_volumes: Vec<LogicalVolume>,
pub keyboard_layout: String,
pub keyboard_variant: String,
pub real_name: String,
pub username: String,
pub password: String,
pub password_confirm: String,
@@ -169,7 +212,6 @@ pub struct GlobalState {
pub bootloader_timeout: u32,
pub bootloader_password: String,
pub kernel_options: String,
pub license_accepted: bool,
pub root_password: String,
pub root_password_confirm: String,
pub display_manager: String,
@@ -217,6 +259,7 @@ impl Default for GlobalState {
logical_volumes: Vec::new(),
keyboard_layout: "tr".to_string(),
keyboard_variant: String::new(),
real_name: String::new(),
username: String::new(),
password: String::new(),
password_confirm: String::new(),
@@ -232,7 +275,6 @@ impl Default for GlobalState {
bootloader_timeout: 5,
bootloader_password: String::new(),
kernel_options: String::new(),
license_accepted: false,
root_password: String::new(),
root_password_confirm: String::new(),
network_wifi_password: String::new(),
@@ -251,17 +293,7 @@ impl Default for GlobalState {
selected_package_groups: Vec::new(),
display_manager: "sddm".to_string(),
autologin: true,
desktop_environment: {
let de = std::env::var("XDG_CURRENT_DESKTOP")
.unwrap_or_default()
.trim()
.to_lowercase();
if de.is_empty() {
"plasma".to_string()
} else {
de
}
},
desktop_environment: detect_desktop_environment(),
branding_icons: std::collections::HashMap::new(),
branding_pisi_dark: String::new(),
branding_pisi_light: String::new(),
+33 -6
View File
@@ -167,7 +167,8 @@ impl Job for MountBindJob {
("/run", format!("{}/run", mt)), // COMAR/dbus için gerekli
];
for (src, target) in &bind_dirs {
run_cmd("mkdir", &["-p", target]).await?;
tokio::fs::create_dir_all(target).await
.map_err(|e| format!("{} oluşturulamadı: {}", target, e))?;
ui.log(format!("mount --bind {} {}", src, target));
run_cmd("mount", &["--bind", src, target]).await?;
}
@@ -175,7 +176,8 @@ impl Job for MountBindJob {
// efivarfs — UEFI kurulumunda grub-install için zorunlu
let efivarfs_target = format!("{}/sys/firmware/efi/efivars", mt);
if std::path::Path::new("/sys/firmware/efi/efivars").exists() {
run_cmd("mkdir", &["-p", &efivarfs_target]).await?;
tokio::fs::create_dir_all(&efivarfs_target).await
.map_err(|e| format!("{} oluşturulamadı: {}", efivarfs_target, e))?;
ui.log(format!("mount --bind /sys/firmware/efi/efivars {}", efivarfs_target));
run_cmd("mount", &["--bind", "/sys/firmware/efi/efivars", &efivarfs_target]).await?;
}
@@ -308,6 +310,12 @@ impl Job for InstallGrubJob {
fn name(&self) -> &str { "job_bootloader" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// Ana dizinleri oluştur (demo modunda rsync çalışmaz)
let etc_default = format!("{}/etc/default", self.mount);
let _ = tokio::fs::create_dir_all(&etc_default).await;
let grub_d = format!("{}/etc/grub.d", self.mount);
let _ = tokio::fs::create_dir_all(&grub_d).await;
// GRUB şifresi (varsa) — bcrypt hash ile /etc/grub.d/40_custom yazılır
if !self.password.is_empty() {
ui.log("GRUB şifresi yapılandırılıyor…");
@@ -776,7 +784,8 @@ impl Job for MountPartitionsJob {
if self.plan.efi_mb > 0 {
let efi_dir = format!("{}/boot/efi", mount);
ui.log(format!("mkdir -p {}", efi_dir));
run_cmd("mkdir", &["-p", &efi_dir]).await?;
tokio::fs::create_dir_all(&efi_dir).await
.map_err(|e| format!("{} oluşturulamadı: {}", efi_dir, e))?;
let efi_part = part_path(disk, 1);
ui.log(format!("mount {} {}", efi_part, efi_dir));
run_cmd("mount", &[&efi_part, &efi_dir]).await?;
@@ -1175,6 +1184,9 @@ impl Job for ConfigureHostnameJob {
fn name(&self) -> &str { "job_hostname" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let etc_dir = format!("{}/etc", self.mount);
let _ = tokio::fs::create_dir_all(&etc_dir).await;
// /etc/hostname
let hostname_path = format!("{}/etc/hostname", self.mount);
ui.log(format!("Yazılıyor: {}", hostname_path));
@@ -1210,6 +1222,10 @@ impl Job for ConfigureLocaleJob {
fn name(&self) -> &str { "job_locale" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let etc_dir = format!("{}/etc", self.mount);
let _ = tokio::fs::create_dir_all(&etc_dir).await;
let _ = tokio::fs::create_dir_all(format!("{}/etc/env.d", self.mount)).await;
// /etc/locale.conf (systemd-style)
let conf_path = format!("{}/etc/locale.conf", self.mount);
let conf = format!(
@@ -1361,7 +1377,8 @@ impl Job for ConfigureKeyboardJob {
// /etc/X11/xorg.conf.d/00-keyboard.conf (X11 için)
let x11_dir = format!("{}/etc/X11/xorg.conf.d", self.mount);
let x11_path = format!("{}/00-keyboard.conf", x11_dir);
run_cmd("mkdir", &["-p", &x11_dir]).await?;
tokio::fs::create_dir_all(&x11_dir).await
.map_err(|e| format!("{} oluşturulamadı: {}", x11_dir, e))?;
let options_line = if self.variant.is_empty() {
String::new()
@@ -1450,7 +1467,10 @@ impl Job for CreateUserJob {
}
// sudoers: wheel grubu sudo yetkisi alsın
let sudoers_path = format!("{}/etc/sudoers.d/wheel", self.mount);
let sudoers_dir = format!("{}/etc/sudoers.d", self.mount);
tokio::fs::create_dir_all(&sudoers_dir).await
.map_err(|e| format!("{} oluşturulamadı: {}", sudoers_dir, e))?;
let sudoers_path = format!("{}/wheel", sudoers_dir);
ui.log(format!("Yazılıyor: {}", sudoers_path));
tokio::fs::write(&sudoers_path, "%wheel ALL=(ALL:ALL) ALL\n")
.await
@@ -1510,6 +1530,9 @@ impl Job for GenerateFstabJob {
fn name(&self) -> &str { "job_fstab" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let etc_dir = format!("{}/etc", self.mount);
let _ = tokio::fs::create_dir_all(&etc_dir).await;
let fstab_path = format!("{}/etc/fstab", self.mount);
ui.log(format!("Yazılıyor: {}", fstab_path));
@@ -1872,7 +1895,8 @@ impl Job for MountCustomPartitionsJob {
for item in mount_items {
let target_path = format!("{}{}", self.mount, item.mountpoint);
ui.log(format!("Bağlama noktası dizini oluşturuluyor: {}", target_path));
run_cmd("mkdir", &["-p", &target_path]).await?;
tokio::fs::create_dir_all(&target_path).await
.map_err(|e| format!("{} oluşturulamadı: {}", target_path, e))?;
ui.log(format!("Bağlanıyor: {}{}", item.device, target_path));
run_cmd("mount", &[&item.device, &target_path]).await?;
}
@@ -1893,6 +1917,9 @@ impl Job for FstabFromCustomPartitionsJob {
fn name(&self) -> &str { "job_fstab" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let etc_dir = format!("{}/etc", self.mount);
let _ = tokio::fs::create_dir_all(&etc_dir).await;
let fstab_path = format!("{}/etc/fstab", self.mount);
ui.log(format!("Yazılıyor: {}", fstab_path));
+1 -2
View File
@@ -6,7 +6,7 @@ mod autoinstall;
mod branding;
use installer::{GlobalState, InstallerStep, WelcomeStep, LocationStep};
use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, LicenseStep, NetworkStep, DisplayManagerStep};
use steps::{KeyboardStep, UsersStep, SummaryStep, ExecutionStep, FinishStep, PartitionStep, BootloaderStep, NetworkStep, DisplayManagerStep};
use eframe::egui;
use rust_i18n::t;
@@ -219,7 +219,6 @@ impl Default for YaliApp {
steps: vec![
Box::new(WelcomeStep::default()),
Box::new(LocationStep::default()),
Box::new(LicenseStep),
Box::new(KeyboardStep::default()),
Box::new(NetworkStep::default()),
Box::new(PartitionStep::default()),
+17 -10
View File
@@ -3,6 +3,7 @@ use rust_i18n::t;
use crate::installer::{GlobalState, InstallerStep};
const DM_OPTIONS: &[&str] = &["sddm", "lightdm", "gdm"];
const DE_OPTIONS: &[&str] = &["plasma", "xfce", "gnome", "cinnamon", "budgie", "lxde", "lxqt", "mate", "lumina"];
#[derive(Default)]
pub struct DisplayManagerStep;
@@ -13,14 +14,9 @@ impl InstallerStep for DisplayManagerStep {
}
fn on_enter(&mut self, state: &mut GlobalState) {
let desktop = std::env::var("XDG_CURRENT_DESKTOP")
.unwrap_or_default()
.trim()
.to_lowercase();
let desktop = crate::installer::detect_desktop_environment();
if !desktop.is_empty() {
state.desktop_environment = desktop;
} else if state.desktop_environment.is_empty() {
state.desktop_environment = "plasma".to_string();
}
}
@@ -48,13 +44,24 @@ impl InstallerStep for DisplayManagerStep {
});
ui.end_row();
// Masaüstü ortamı (otomatik algılanır)
// Masaüstü ortamı (seçilebilir)
ui.label(t!("display_manager_de"));
if !state.desktop_environment.is_empty() && state.desktop_environment != "default" {
ui.colored_label(egui::Color32::from_rgb(120, 180, 120), &state.desktop_environment);
let current_de = if state.desktop_environment.is_empty() || state.desktop_environment == "default" {
"plasma"
} else {
ui.colored_label(egui::Color32::from_rgb(200, 160, 40), t!("display_manager_de_not_found"));
state.desktop_environment.as_str()
};
egui::ComboBox::from_id_source("de_combo")
.selected_text(current_de)
.show_ui(ui, |ui| {
for de in DE_OPTIONS {
ui.selectable_value(
&mut state.desktop_environment,
de.to_string(),
*de,
);
}
});
ui.end_row();
// Otomatik oturum açma
-2
View File
@@ -5,7 +5,6 @@ pub mod execution;
pub mod finish;
pub mod partition;
pub mod bootloader;
pub mod license;
pub mod network;
pub mod rescue;
pub mod display_manager;
@@ -18,7 +17,6 @@ pub use execution::ExecutionStep;
pub use finish::FinishStep;
pub use partition::PartitionStep;
pub use bootloader::BootloaderStep;
pub use license::LicenseStep;
pub use network::NetworkStep;
pub use rescue::RescueStep;
pub use display_manager::DisplayManagerStep;
+50 -4
View File
@@ -172,10 +172,12 @@ use crate::installer::{
};
const MIN_DISK_GB: u64 = 10;
const EFI_MB: u64 = 512;
const EFI_MB: u64 = 300;
const SWAP_MB_MIN: u64 = 512;
const SWAP_MB_MAX: u64 = 8 * 1024;
const COMMON_MOUNTPOINTS: &[&str] = &["/", "/boot", "/boot/efi", "/home", "/var", "/tmp", "swap"];
// ─────────────────────────────────────────────
// Manuel bölümleme için yerel durum
// ─────────────────────────────────────────────
@@ -597,8 +599,9 @@ impl PartitionStep {
.spacing([12.0, 8.0])
.show(ui, |ui| {
// Dosya sistemi
// Dosya sistemi (boyut önerisi ile)
ui.label(t!("mp_fstype"));
let prev_fs = self.manual.add_fstype;
egui::ComboBox::from_id_source("fs_combo")
.selected_text(FS_OPTIONS.get(self.manual.add_fstype).map(|x| x.0).unwrap_or("?"))
.show_ui(ui, |ui| {
@@ -608,6 +611,15 @@ impl PartitionStep {
);
}
});
// FS tipi değişince akıllı boyut öner
if prev_fs != self.manual.add_fstype && self.manual.add_size_str.is_empty() {
let fs = FS_OPTIONS.get(self.manual.add_fstype).map(|x| &x.1);
match fs {
Some(FsType::Fat32) => self.manual.add_size_str = "300".to_string(),
Some(FsType::Swap) => self.manual.add_size_str = format!("{}", recommended_swap_mb()),
_ => {}
}
}
ui.end_row();
// Boyut
@@ -615,11 +627,28 @@ impl PartitionStep {
ui.text_edit_singleline(&mut self.manual.add_size_str);
ui.end_row();
// Bağlama noktası
// Bağlama noktası (2 aşamalı: combo + input)
ui.label(t!("mp_mountpoint"));
ui.horizontal(|ui| {
let current_mp = if self.manual.add_mountpoint.is_empty() {
t!("mp_select_hint").to_string()
} else {
self.manual.add_mountpoint.clone()
};
egui::ComboBox::from_id_source("mp_combo")
.selected_text(&current_mp)
.width(120.0)
.show_ui(ui, |ui| {
for mp in COMMON_MOUNTPOINTS {
if ui.selectable_label(false, *mp).clicked() {
self.manual.add_mountpoint = mp.to_string();
}
}
});
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_mountpoint)
.hint_text(t!("mp_add_dialog_hint"));
ui.add(mp_field);
});
ui.end_row();
// LUKS şifreleme
@@ -858,11 +887,28 @@ impl PartitionStep {
ui.text_edit_singleline(&mut self.manual.add_lv_size_str);
ui.end_row();
// Bağlama noktası
// Bağlama noktası (2 aşamalı: combo + input)
ui.label(t!("mp_mountpoint"));
ui.horizontal(|ui| {
let current_mp = if self.manual.add_lv_mountpoint.is_empty() {
t!("mp_select_hint").to_string()
} else {
self.manual.add_lv_mountpoint.clone()
};
egui::ComboBox::from_id_source("lv_mp_combo")
.selected_text(&current_mp)
.width(120.0)
.show_ui(ui, |ui| {
for mp in COMMON_MOUNTPOINTS {
if ui.selectable_label(false, *mp).clicked() {
self.manual.add_lv_mountpoint = mp.to_string();
}
}
});
let mp_field = egui::TextEdit::singleline(&mut self.manual.add_lv_mountpoint)
.hint_text(t!("mp_add_dialog_hint"));
ui.add(mp_field);
});
ui.end_row();
});
+57 -5
View File
@@ -23,6 +23,33 @@ impl Default for UsersStep {
}
}
/// Gerçek addan geçerli bir Linux kullanıcı adı türetir.
/// - Küçük harfe çevirir
/// - Türkçe karakterleri ASCII'ye dönüştürür (ş→s, ğ→g, ı→i, ö→o, ü→u, ç→c)
/// - Boşlukları `-`'ye çevirir
/// - Geçersiz karakterleri temizler
fn derive_username(real_name: &str) -> String {
let mut name = real_name.to_lowercase();
name = name
.replace('ş', "s")
.replace('ğ', "g")
.replace('ı', "i")
.replace('ö', "o")
.replace('ü', "u")
.replace('ç', "c")
.replace(' ', "-")
.replace('_', "-");
let valid: String = name.chars().filter(|c| c.is_ascii_alphanumeric() || *c == '-' || *c == '_').collect();
let trimmed = valid.trim_matches('-').to_string();
if trimmed.is_empty() {
"user".to_string()
} else if trimmed.len() > 32 {
trimmed[..32].trim_end_matches('-').to_string()
} else {
trimmed
}
}
/// Kullanıcı adının geçerliliğini kontrol eder.
/// Linux kuralları: küçük harf veya alt çizgi ile başlamalı,
/// en fazla 32 karakter, yalnızca [a-z0-9_-] içermeli.
@@ -75,7 +102,21 @@ impl InstallerStep for UsersStep {
}
fn on_enter(&mut self, state: &mut GlobalState) {
// Hostname boşsa username'den öner
// Gerçek ad varsa ama kullanıcı adı yoksa türet
if !state.real_name.is_empty() && state.username.is_empty() {
state.username = derive_username(&state.real_name);
}
// Hostname boşsa önce /etc/hostname dene
if state.hostname.is_empty() {
let sys_hostname = std::fs::read_to_string("/etc/hostname")
.unwrap_or_default()
.trim()
.to_string();
if !sys_hostname.is_empty() && sys_hostname != "localhost" {
state.hostname = sys_hostname;
}
}
// Hâlâ boşsa ve username varsa username-pc öner
if state.hostname.is_empty() && !state.username.is_empty() {
state.hostname = format!("{}-pc", state.username);
}
@@ -103,7 +144,21 @@ impl InstallerStep for UsersStep {
.num_columns(2)
.spacing([12.0, 10.0])
.show(ui, |ui| {
// Kullanıcı adı
// Gerçek ad
ui.label(egui::RichText::new(t!("real_name_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
let prev = state.real_name.clone();
ui.text_edit_singleline(&mut state.real_name);
if state.real_name != prev && !state.real_name.is_empty() {
state.username = derive_username(&state.real_name);
}
if !state.real_name.is_empty() {
ui.colored_label(theme::c_success(), "");
}
});
ui.end_row();
// Kullanıcı adı (otomatik türetilir, düzenlenebilir)
ui.label(egui::RichText::new(t!("username_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
ui.text_edit_singleline(&mut state.username);
@@ -159,9 +214,6 @@ impl InstallerStep for UsersStep {
// Hostname
ui.label(egui::RichText::new(t!("hostname_label")).strong().color(theme::c_text()));
ui.horizontal(|ui| {
if state.hostname.is_empty() && !state.username.is_empty() {
state.hostname = format!("{}-pc", state.username);
}
ui.text_edit_singleline(&mut state.hostname);
if !state.hostname.is_empty() {
if is_valid_hostname(&state.hostname) {
BIN
View File
Binary file not shown.