1 Commits

Author SHA1 Message Date
ayhanyalcinsoy f20cb86756 Merge branch 'main' into 'main'
feat: keyboard layout/variant search with custom combobox, hex color constants, various UI fixes

See merge request ayhanyalcinsoy/yali-rs!8
2026-07-07 12:39:04 +00:00
80 changed files with 4597 additions and 9765 deletions
+1 -13
View File
@@ -1,18 +1,6 @@
# Rust build outputs
/target
.cargo/registry/
# Lock files (bırakalım, bazen kontrol edilir)
Cargo.lock
# IDE settings
.idea/
.vscode/
# Temporary files
*.swp
*.swo
*.bak
# OS files
.DS_Store
.vscode/
View File
View File
View File
View File
+3
View File
@@ -0,0 +1,3 @@
{
"rust-analyzer.checkOnSave": false
}
-111
View File
@@ -1,111 +0,0 @@
# AGENTS.md — Yali-rs Geliştirme Notları
> Ajan oturumları arasında süreklilik için. Son güncelleme: parted MiB düzeltmesi (23 Ağu)
## Proje
- PisiLinux kurulum aracı (eski Python Yali'nin Rust/egui yeniden yazımı).
- Ana dallar: `main`. Push: `git push origin main`.
- Referans kod: `/media/yedekleme/github/erkanisik/yali` (eski Python Yali —
storage mimarisi: `yali/storage/{operations,partitioning,devicetree}.py`).
## Mimari Kararlar (2026-08 dönemi)
### Çoklu Disk Manuel Bölümleme
- `CustomPartition` alanları (`src/installer.rs`):
- `disk`: bölümün ait olduğu disk ("/dev/sda") — gruplama bununla.
- `orig_device`: lsblk'tan gelen fiziksel cihaz adı; **boş = kullanıcı tanımı yeni bölüm**.
Silme/işaret mantığı buna dayanır.
- `delete: bool`: eski Yali `OperationDestroyDevice` karşılığı → kurulumda `parted rm`.
- UI (`src/steps/partition.rs`):
- Manuel moda girişte TÜM diskler otomatik taranıp listelenir (disk kartına tıklamak
artık sadece "yeni bölüm hedef diski" seçer; tanımları sıfırlamaz — merge eder,
`orig_device` eşleşmesinde kullanıcı tanımı kazanır).
- Tablo `TableRow::Part | TableRow::FreeSpace` enum'u ile kurulur; HER disk grubu
kendi sütun başlıklarını çizer (`draw_partition_table_header`).
- Silinmek işaretli bölümler ayrı satır çizmez; İLK silinenin konumunda TEK
"Boş Alan" satırında birleşir (GParted tarzı).
`Boş Alan = disk − (korunan fiziksel + yeni tanımlar)`; 0 ise satır gizlenir.
- "Kalan alan" SEMBOLİKTİR (size_mb=0): kaydetme anında sayıya çevrilmez!
Tabloda/özetde kapasite olarak gösterilir; gerçek boyut kurulum anında boşluktan alınır.
(Somutlaştırma taşmaya yol açtığı için kaldırıldı — swap sonradan büyütülünce kök patlıyordu.)
- Slot ataması (`installer::assign_partition_numbers`): parted'ın gerçek davranışını
simüle eder — fiziksel (silinmeyen) bölümler numarasını korur, silinenlerin slotu
boşalır, yeni tanımlar en küçük boş slotu alır. UI ile job tutarlılığı buradan gelir.
### Granular Kurulum Job'ı (`jobs/partition.rs::CustomPartitionJob`)
Eski wipe&rebuild (mktable + hepsini baştan yarat) KALDIRILDI. Yeni fazlar:
1. FAZ A: `delete=true` olanlar `parted rm N` — N, `orig_device`'tan;
**büyükten küçüğe** sil (kernel slot kayması).
2. FAZ B: yeni tanımlar `disk_free_regions_mb()` ile sysfs'ten hesaplanan boş
bölgelere first-fit yerleşir ("kalan" = en büyük bölge). Sabit boyutlular ÖNCE,
sembolikler SONRA (`creates.sort_by_key(size==0)`) işlenir.
3. FAZ C: yalnızca istenenler mkfs (LUKS akışı korundu).
- İşlemi olmayan disklere ve `delete=true` satırlara mount/fstab/summary dokunmaz.
### Diğer Düzeltmeler
- **parted birim tuzağı (KRİTİK):** parted `"MB"` sonekini **ondalık** (10^6 byte) okur,
bizim hesaplar binary (2^20). `mkpart 3944MB` → sektör 7703125 (hizasız, boş alana
uymaz) → "Becerebildiğimiz buna en yakın konum..." hatası. ÇÖZÜM: parted'a daima
`"{}MiB"` sonekiyle geç (`jobs/partition.rs`, hem otomatik hem CustomPartitionJob).
Tamsayı MiB = 2048 sektörün katı → hizalama hatası çıkmaz. UI'da kullanıcıya MB
gösterilir ama parted argümanı asla "MB" olmasın.
- MB dönüşümleri tek bölme: `x / 1_048_576` (çift `x/1024/1024` hassasiyet kaybı yapardı).
- Disk seçiminde `size_gb * 1024` yerine `size_bytes / 1_048_576`.
- Mountpoint değişince boyut alanı SADECE boş/"0"/"remaining" ise varsayılanlanır
(kullanıcının elle girdiği değer ezilmez).
- Simge fontu: `assets/remixicon.ttf` egui fallback fontu (`ui/theme.rs setup_fonts`).
Kutucuk görürsen karakter fontta yok demektir — fontTools ile cmap kontrol et:
kullanılanlar: EB7B check, EA6B arrow, EC28 trash, EA13 add, EFE0 pencil, EECD lock,
ECA1 error, EDFB hard-drive-line, ECB5/B7 eye, F4C6 close.
- Alt bar: "Mevcut Durum / Kurulum Sonrası" çift şerit (`manual_dual_bars`,
gri segment = boş) — SADECE seçili disk için gösterilir (`state.selected_disk`).
## Bilinen Açıklar / Yapılacaklar
- [ ] VM testinde rapor edilen hatalar düzeltilecek (kullanıcı listeleyecek)
- [ ] `manual_capacity_error` yalnızca sabit boyutları kontrol eder; LVM PV/LV toplamları ayrıca doğrulanmalı
- [ ] Silinmek işaretli bölüm bir VG'nin PV'siyse SetupLvmJob tarafında kontrol yok
- [ ] Otomatik mod (erase disk) hâlâ tek disk; çoklu otomatik plan yok
- [ ] Debug amaçlı kalan `eprintln!` satırları temizlenebilir
## Test / Derleme
- `cargo check` + `cargo test` (22 birim testi; partition modülü ağırlıklı).
- VM'de denemek: ISO'yu boot edip manuel bölümlemede sdb üzerindeki senaryolar:
sil→boş-alan birleşimi, geri al, kalan alan, farklı diske bölüm ekleme.
## ✅ Düzenlenmiş Manuel Partition Sorunları (18 Eylül 2026)
Bu gün, manual partition modundaki 5 kritik sorun düzeltilmiştir:
1. **`manual_capacity_error` birim tutarlılığı** (`src/steps/partition.rs`):
- `cap` → `cap_mb`, `fixed` → `fixed_mb` değişken isimleri standardized
- `d.size_bytes / 1_048_576` (MiB) ile `defs.iter().map(|p| p.size_mb).sum()` consistently binary MiB (2^20) hesaplaması
- `cargo test` ve `cargo check` passed
2. **`disk_free_regions_mb` delete flagı yönetimi** (`src/jobs/partition.rs`):
- `.filter(|p| p.delete && !p.orig_device.is_empty())` → `.filter(|p| p.delete)`
- Tüm `delete=true` flag'li bölümler (incl. newly defined with empty orig_device) free alan hesaplamasından exclude ediliyor
3. **`validate_manual` ekstra validasyonlar** (`src/steps/partition.rs`):
- **Duplicate mountpoint kontrolü**: Aynı mountpoint'ta iki live bölüm tanımlı mı? → hata döndürür
- **Swap boyut validasyonu**: 512MB minimum, 8GB maksimum kontrolü eklendi
- **Size 0 partition uyarısı**: Boyut 0 ama mountpoint boşsa uyarı mesajı
4. **`is_complete` disk seçim kontrolü** (`src/steps/partition.rs`):
- `state.partition_plan.is_some()` → `state.selected_disk.is_some()` eklendi
- Kullanıcı disk seçmeden partition tanımları yapmış gibi görünmesini engelledi
- `is_complete` false döndürür, kullanıcıya disk seçme hatası verir
5. **Test doğrulaması**:
- `cargo check` - başarıyla derlendi (43.22s)
- `cargo test` - 1 test passed (parse_progress2_percent)
### Etkilenen Dosyalar
- `src/steps/partition.rs` - 3 değişikling (manual_capacity_error, validate_manual, is_complete)
- `src/jobs/partition.rs` - 1 değişiklik (disk_free_regions_mb delete flagı)
### Bilinen Açıklar / Yapılacaklar Güncellemesi
- [x] `manual_capacity_error` sorunu çözüldü - birim tutarlılığı sağlandı
- [ ] `manual_capacity_error` yalnızca sabit boyutları kontrol eder; LVM PV/LV toplamları ayrıca doğrulanmalı (hala open)
- [ ] Silinmek işaretli bölüm bir VG'nin PV'siyse SetupLvmJob tarafında kontrol yok
- [ ] Otomatik mod (erase disk) hâlâ tek disk; çoklu otomatik plan yok
- [ ] Debug amaçlı kalan `eprintln!` satırları temizlenebilir
-23
View File
@@ -1,28 +1,5 @@
# Changelog — [English](CHANGELOG_en.md)
## [4.0.5] — 2026-07-10
### Added
- `slides/slides_tr.toml`, `slides/slides_en.toml` — dil bazlı slayt içerik dosyaları (`[icons]` eşlemesi yerine doğrudan dosya yolu)
- `src/bin/slides_test.rs` — bağımsız slayt gösterisi test ikilisi (arg: `[desktop] [lang]`)
- `default-run = "yali-rs"` (`Cargo.toml`) — `cargo run` belirsizliği giderildi
### Changed
- Slayt görselleri `assets/*.png` → `slides/img/` altına taşındı
- `branding.toml` / `branding_tr.toml` / `branding_en.toml` içindeki `[slides]` bölümü kaldırıldı; içerik `slides/` dizinindeki dil dosyalarına taşındı
- `branding.rs::load_for_lang()` — slaytları `slides/slides_{lang}.toml`'den yükler (fallback: `slides/slides.toml`)
- `slideshow.rs` — `slide.icon` doğrudan dosya yolu içeriyorsa `file://` URI'sine çevirir (artık `[icons]` eşlemesi gerekmez)
- `CopyFilesJob` — rsync çıktı formatı `--info=progress2` → `--out-format=%n`, ilerleme `find` ile toplam dosya sayısı üzerinden hesaplanır
- Otomatik kurulum CLI çıktısına alt ilerleme ve dosya adı bilgisi eklendi
### Fixed
- Edit diyalog penceresinin her frame'de büyümesi (`default_size` + min/max genişlik)
- TextEdit alanı `available_width * 0.90` yerine `desired_width()` ile düzgün genişlik
- `cargo run -- --demo` belirsizliği (çift ikili)
- `locales/tr.toml` "Hoş Geldiniz12" yazım hatası
---
## [4.0.4] — 2026-06-25
### Added
-23
View File
@@ -1,28 +1,5 @@
# Changelog — [Türkçe](CHANGELOG.md)
## [4.0.5] — 2026-07-10
### Added
- `slides/slides_tr.toml`, `slides/slides_en.toml` — language-specific slide content files (direct file paths instead of `[icons]` mapping)
- `src/bin/slides_test.rs` — standalone slideshow test binary (args: `[desktop] [lang]`)
- `default-run = "yali-rs"` (`Cargo.toml`) — fixes `cargo run` ambiguity
### Changed
- Slide images moved from `assets/*.png` → `slides/img/`
- `[slides]` section removed from `branding.toml`/`branding_tr.toml`/`branding_en.toml`; content moved to `slides/slides_{lang}.toml`
- `branding.rs::load_for_lang()` — loads slides from `slides/slides_{lang}.toml` (fallback: `slides/slides.toml`)
- `slideshow.rs` — if `slide.icon` contains a file path, converts it to `file://` URI directly (no `[icons]` mapping needed)
- `CopyFilesJob` — rsync output format `--info=progress2` → `--out-format=%n`, progress calculated via `find` total file count
- Auto-install CLI output now shows sub-progress and current file name
### Fixed
- Edit dialog window growing every frame (`default_size` + min/max width)
- TextEdit field width using `desired_width()` instead of `available_width * 0.90`
- `cargo run -- --demo` ambiguity (dual binary)
- `locales/tr.toml` "Hoş Geldiniz12" typo
---
## [4.0.4] — 2026-06-25
### Added
+1 -7
View File
@@ -2,7 +2,6 @@
name = "yali-rs"
version = "4.0.0"
edition = "2024"
default-run = "yali-rs"
[dependencies]
eframe = { version = "0.27", features = ["default", "wgpu"] }
@@ -16,13 +15,8 @@ sysinfo = "0.30"
rust-i18n = "3.1"
lazy_static = "1.4"
async-trait = "0.1"
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = "0.8"
chrono = "0.4"
xkbcommon = "0.9"
[[bin]]
name = "yali-rs"
path = "src/main.rs"
[patch.crates-io]
# Yerel mudur veya comar-api kütüphaneleri buraya eklenebilir
+2 -6
View File
@@ -12,7 +12,6 @@ BINDIR ?= $(PREFIX)/bin
DATADIR ?= $(PREFIX)/share/yali
LOCALEDIR ?= $(DATADIR)/locales
ASSETSDIR ?= $(DATADIR)/assets
SLIDESDIR ?= $(DATADIR)/slides
DESKTOPDIR ?= $(PREFIX)/share/applications
PIXMAPDIR ?= $(PREFIX)/share/pixmaps
@@ -129,13 +128,11 @@ install: release
install -dm755 $(DESTDIR)$(LOCALEDIR)
install -m644 locales/*.toml $(DESTDIR)$(LOCALEDIR)/
# Slayt dosyaları
install -dm755 $(DESTDIR)$(SLIDESDIR)
cp -r slides/* $(DESTDIR)$(SLIDESDIR)/
# .desktop girdisi
install -Dm644 assets/yali.desktop $(DESTDIR)$(DESKTOPDIR)/yali.desktop
# Logo (pixmap)
install -Dm644 assets/yali.png $(DESTDIR)$(PIXMAPDIR)/yali.png
@echo "$(BOLD)$(GREEN)✓ Kurulum tamamlandı$(RESET)"
@@ -146,7 +143,6 @@ uninstall:
rm -rf $(DESTDIR)$(DATADIR)
rm -f $(DESTDIR)$(DESKTOPDIR)/yali.desktop
rm -f $(DESTDIR)$(PIXMAPDIR)/yali.png
rm -rf $(DESTDIR)$(SLIDESDIR)
@echo "$(BOLD)$(GREEN)✓ Kaldırıldı$(RESET)"
# ── Temizlik ─────────────────────────────────────────────────────────────────
+1 -12
View File
@@ -34,10 +34,6 @@ cargo run --release -- --demo
# Otomatik kurulum (cevap dosyası ile)
sudo cargo run --release -- --auto-install answer.toml
# Slayt gösterisi testi (bağımsız pencere, root gerekmez)
cargo run --bin slides_test [desktop] [lang]
# Örn: cargo run --bin slides_test xfce en
```
## Proje Yapısı
@@ -45,12 +41,9 @@ cargo run --bin slides_test [desktop] [lang]
```
├── src/
│ ├── main.rs # Giriş noktası, eframe App, GUI düzeni
│ ├── lib.rs # Kütüphane kökü (yedek DLL yapısı için ayrıldı)
│ ├── installer.rs # InstallerStep trait'i, GlobalState, WelcomeStep
│ ├── funct.rs # Paylaşılan UI yardımcıları (form alanları, dil seçici)
│ ├── branding.rs # Branding.toml yükleyici (logo, slayt, tema, OEM)
│ ├── bin/
│ │ └── slides_test.rs # Slayt gösterisi test aracı (bağımsız ikili)
│ ├── ui/
│ │ ├── theme.rs # Renkler, fontlar, Visuals, section_heading, error/warning/success kutuları
│ │ ├── buttons.rs # Gradient butonlar (primary, secondary, danger, vb.)
@@ -78,14 +71,10 @@ cargo run --bin slides_test [desktop] [lang]
├── locales/
│ ├── tr.toml # Türkçe çeviriler
│ └── en.toml # İngilizce çeviriler
├── slides/
│ ├── img/ # Slayt görselleri (PNG)
│ ├── slides_tr.toml # Türkçe slayt içeriği
│ └── slides_en.toml # İngilizce slayt içeriği
├── assets/
│ ├── icons/ # UI ikonları (YALI_icon_LIGHT.svg, language-icon.svg, vb.)
│ ├── flags/ # Ülke bayrakları (~250 SVG)
│ └── pisi-logo-*.png # Dağıtım logoları (branding `[general]`)
│ └── *.png # Slayt ve logo görselleri
├── branding.toml # Varsayılan markalama yapılandırması
├── branding_en.toml # İngilizce markalama
├── branding_tr.toml # Türkçe markalama
+1 -12
View File
@@ -31,10 +31,6 @@ cargo run --release -- --demo
# Automatic installation (with answer file)
sudo cargo run --release -- --auto-install answer.toml
# Slideshow test (standalone window, no root required)
cargo run --bin slides_test [desktop] [lang]
# E.g.: cargo run --bin slides_test xfce en
```
## Project Structure
@@ -42,12 +38,9 @@ cargo run --bin slides_test [desktop] [lang]
```
├── src/
│ ├── main.rs # Entry point, eframe App, GUI layout
│ ├── lib.rs # Library root (reserved for future DLL structure)
│ ├── installer.rs # InstallerStep trait, GlobalState, WelcomeStep
│ ├── funct.rs # Shared UI helpers (form fields, language selector)
│ ├── branding.rs # Branding.toml loader (logo, slides, theme, OEM)
│ ├── bin/
│ │ └── slides_test.rs # Standalone slideshow test binary
│ ├── ui/
│ │ ├── theme.rs # Colors, fonts, Visuals, section_heading, error/warning/success boxes
│ │ ├── buttons.rs # Gradient buttons (primary, secondary, danger, etc.)
@@ -75,14 +68,10 @@ cargo run --bin slides_test [desktop] [lang]
├── locales/
│ ├── tr.toml # Turkish translations
│ └── en.toml # English translations
├── slides/
│ ├── img/ # Slide images (PNG)
│ ├── slides_tr.toml # Turkish slide content
│ └── slides_en.toml # English slide content
├── assets/
│ ├── icons/ # UI icons (YALI_icon_LIGHT.svg, language-icon.svg, etc.)
│ ├── flags/ # Country flags (~250 SVG)
│ └── pisi-logo-*.png # Distribution logos (branding `[general]`)
│ └── *.png # Slide and logo images
├── branding.toml # Default branding configuration
├── branding_en.toml # English branding
├── branding_tr.toml # Turkish branding

Before

Width:  |  Height:  |  Size: 9.9 KiB

After

Width:  |  Height:  |  Size: 9.9 KiB

Before

Width:  |  Height:  |  Size: 2.9 KiB

After

Width:  |  Height:  |  Size: 2.9 KiB

Before

Width:  |  Height:  |  Size: 27 KiB

After

Width:  |  Height:  |  Size: 27 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 3.1 KiB

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

+6 -6
View File
@@ -2,14 +2,14 @@
<!-- Generator: Adobe Illustrator 27.7.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
<svg version="1.1" id="Optimized" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
viewBox="0 0 841.8897705 595.2755737" style="enable-background:new 0 0 841.8897705 595.2755737;" xml:space="preserve">
<polygon style="fill:#FFFFFF;" points="512.5472412,488.2385864 542.3688354,537.3009033 558.0941772,491.724762 "/>
<path style="fill:#FFFFFF;" d="M277.7966003,213.2223663c-1.1176147-1.0975342,1.4554749,8.9684143,5.0365906,12.5897064
<polygon style="fill:#020203;" points="512.5472412,488.2385864 542.3688354,537.3009033 558.0941772,491.724762 "/>
<path style="fill:#020203;" d="M277.7966003,213.2223663c-1.1176147-1.0975342,1.4554749,8.9684143,5.0365906,12.5897064
c6.3496399,6.4062805,11.3096008,7.23172,13.9502563,7.3376465c5.8437805,0.2337646,13.0553894-1.4554596,17.3377991-3.2506104
c4.1435852-1.767746,11.404541-5.4748993,14.1529236-10.8822479c0.5825195-1.1559601,2.1731262-3.0972137,1.1742859-7.8927765
c-0.7579041-3.6888733-3.1064148-4.980011-5.9698181-4.7754517c-2.8634644,0.1935883-11.5323792,2.5055237-15.7253113,3.7948151
c-4.1947021,1.2728424-12.8344116,3.9025269-16.6000366,4.7188416
C287.3968811,215.6767426,279.1151123,214.4842987,277.7966003,213.2223663z"/>
<path style="fill:#FFFFFF;" d="M383.9637451,333.5533752c-1.6582031-0.6026001-35.9649353-14.8140564-40.828064-17.1424255
<path style="fill:#020203;" d="M383.9637451,333.5533752c-1.6582031-0.6026001-35.9649353-14.8140564-40.828064-17.1424255
c-3.979248-1.9137573-13.7365417-6.0391541-18.3276062-7.9127808c12.9312439-19.9383545,21.0942688-34.984314,22.1808472-37.276123
c2.0106506-4.1929321,15.697876-30.9757843,16.0174561-32.6248322c0.3104858-1.6709595,0.6994324-7.8434601,0.3981323-9.3098907
c-0.3013-1.495636-5.3196411,1.3787689-12.1331482,3.6889343c-6.8244629,2.3009491-19.7940674,10.7361145-24.8032837,11.7934723
@@ -26,7 +26,7 @@
c2.0288696,0.8454895,18.4335022,14.2113342,22.7140503,17.1734619c4.2806396,2.9602051,21.172821,12.3851318,24.832489,13.9483643
c3.6596985,1.5832825,17.7249756,8.0681152,18.3166809,5.8730469
C388.7592468,347.1237793,385.6236877,334.1834412,383.9637451,333.5533752z"/>
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#FFFFFF;" d="M304.6889954,512.2145996
<path style="fill-rule:evenodd;clip-rule:evenodd;fill:#020203;" d="M304.6889954,512.2145996
c3.2871399,2.0087891,6.3916626,3.6523438,9.8614197,5.2958984c6.9394836,3.4697876,14.7920837,7.1221313,22.2794495,9.8614502
c10.2266541,3.8349609,20.4532776,6.9395142,30.6799316,9.3135376c5.6611633,1.2783203,11.8701782,2.3740234,17.8966064,3.2871094
c0.5478516,0,16.8009033,2.0087891,20.0880432,2.0087891h16.4356689c6.3916321-0.5478516,12.4180603-0.9130859,18.8096924-1.8261719
@@ -43,7 +43,7 @@
c-12.6006775-4.7481079-25.7492065-10.9571533-38.1672668-19.1749878c-2.1914062-1.4609375-2.3740234-2.921875-2.3740234-4.5654297
c0-2.7392883,2.0087891-5.2959595,5.295929-5.2959595C297.7495117,507.4664917,303.5932922,511.6667175,304.6889954,512.2145996z"/>
<g>
<path style="fill:#FFFFFF;" d="M639.8383789,180.4025879l-40.7682495-12.975708V48.3634644
<path style="fill:#020203;" d="M639.8383789,180.4025879l-40.7682495-12.975708V48.3634644
c0-3.4697266-2.5567017-5.843811-5.843811-5.843811c-2.5567017,0-84.9176636,28.3059082-91.4918823,30.6799316
c-22.3344727,7.4448242-86.798645,29.7334595-86.798645,29.7334595l-0.000061,0.0002441
c-1.0336304,0.2964478-2.6376953,0.7871704-4.7411499,1.4482422L252.854248,48.8499756
@@ -57,6 +57,6 @@
V182.0402222l199.0542603-66.2905273V409.4003296z M587.9303589,55.6682129v108.2130737l-164.4921875-52.3546143
L587.9303589,55.6682129z M567.6870728,385.267334l-10.5206299-38.4284668l-60.5161133-18.3422241l-13.0134277,31.3044434
l-29.2919922-8.8861084l62.1779785-152.5870361l28.5085449,8.6360474l51.9385376,187.1876831L567.6870728,385.267334z"/>
<polygon style="fill:#FFFFFF;" points="507.8375244,300.3788452 547.7670288,312.4828491 529.5562744,247.8833618 "/>
<polygon style="fill:#020203;" points="507.8375244,300.3788452 547.7670288,312.4828491 529.5562744,247.8833618 "/>
</g>
</svg>

Before

Width:  |  Height:  |  Size: 6.3 KiB

After

Width:  |  Height:  |  Size: 6.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

Before

Width:  |  Height:  |  Size: 21 KiB

After

Width:  |  Height:  |  Size: 21 KiB

Before

Width:  |  Height:  |  Size: 2.8 KiB

After

Width:  |  Height:  |  Size: 2.8 KiB

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

Before

Width:  |  Height:  |  Size: 9.8 KiB

After

Width:  |  Height:  |  Size: 9.8 KiB

Before

Width:  |  Height:  |  Size: 4.3 KiB

After

Width:  |  Height:  |  Size: 4.3 KiB

Before

Width:  |  Height:  |  Size: 73 KiB

After

Width:  |  Height:  |  Size: 73 KiB

Before

Width:  |  Height:  |  Size: 43 KiB

After

Width:  |  Height:  |  Size: 43 KiB

Before

Width:  |  Height:  |  Size: 42 KiB

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 25 KiB

After

Width:  |  Height:  |  Size: 25 KiB

Before

Width:  |  Height:  |  Size: 3.7 KiB

After

Width:  |  Height:  |  Size: 3.7 KiB

Before

Width:  |  Height:  |  Size: 23 KiB

After

Width:  |  Height:  |  Size: 23 KiB

BIN
View File
Binary file not shown.

After

Width:  |  Height:  |  Size: 19 KiB

+309
View File
@@ -22,6 +22,315 @@ pisi_logo_light = "assets/pisi-logo-light.png"
# Kurulum penceresinin başlığı (pencere çubuğu)
title = "PiSi Linux Kurulumu"
# ──────────────────────────────────────────────────────────────────
# [slides] – Kurulum sırasında gösterilen slaytlar
# ──────────────────────────────────────────────────────────────────
[slides]
# Slayt geçiş süresi (saniye)
interval_secs = 6
# ── Slayt ikonları → görsel dosya yolu eşlemesi ──────────────────
# Geliştirici notu: Yeni ikon eklemek için sadece buraya satır ekle.
[slides.icons]
pisilogo_dark = "assets/pisi-logo-dark.png"
pisilogo_light = "assets/pisi-logo-light.png"
pisi = "assets/pisi-software-center.png"
community = "assets/community.png"
pisi_speed = "assets/pisi_speed.png"
securety = "assets/securety.png"
customization = "assets/customization.png"
budgie = "assets/budgie.png"
budgie_applets = "assets/budgie_applets.png"
budgie_raven = "assets/budgie_raven.png"
budgie_gtk_theme = "assets/budgie_gtk_theme.png"
cinnamon = "assets/cinnamon.png"
cinnamon_panel = "assets/cinnamon_panel.png"
cinnamon_settings = "assets/cinnamon_settings.png"
cinnamon_desklets = "assets/cinnamon_desklets.png"
gnome = "assets/gnome.png"
gnome_search = "assets/gnome_search.png"
gnome_extensions = "assets/gnome_extensions.png"
kde = "assets/kde.png"
kde_connect = "assets/kde_connect.png"
discover = "assets/discover.png"
file_manager = "assets/file_manager.png"
lxde = "assets/lxde.png"
lxqt = "assets/lxqt.png"
lumina = "assets/lumina.png"
mate = "assets/mate.png"
mate_settings = "assets/mate_settings.png"
xfce = "assets/xfce.png"
xfce_settings = "assets/xfce_settings.png"
xfce_panel = "assets/xfce_panel.png"
# ══════════════════════════════════════════════════════════════════
# [[slides.items]] – Slayt içerikleri
# ══════════════════════════════════════════════════════════════════
#
# Her [[slides.items]] bloğu bir slaytı tanımlar.
#
# Alanlar:
# icon = Görsel için yukarıdaki [slides.icons] anahtarı
# title = Slayt başlığı
# description = Slayt açıklama metni
# desktop = (isteğe bağlı) Yalnızca belirli masaüstünde göster.
# Boş bırakılırsa tüm masaüstlerinde gösterilir.
# Değerler: "xfce", "kde", "gnome", "mate", "budgie",
# "cinnamon", "lxde", "lxqt", "lumina"
#
# Geliştirici notu:
# Yeni bir slayt eklemek veya metni değiştirmek için yalnızca
# buraya [[slides.items]] bloğu ekleyin / düzenleyin.
# Rust koduna dokunmanıza gerek yoktur.
# ──────────────────────────────────────────────────────────────────
# ── Tüm masaüstlerinde gösterilen ortak slaytlar ──────────────────
[[slides.items]]
icon = "pisilogo"
title = "Pisi GNU/Linux'a Hoş Geldiniz"
description = "Pisi; hız, kararlılık ve kullanım kolaylığını bir araya getiren Türkçe destekli bir GNU/Linux dağıtımıdır."
[[slides.items]]
icon = "pisi"
title = "PISI Paket Yöneticisi"
description = "Binlerce pakete tek komutla erişin. `pisi install <paket>` ile ihtiyacınız olan her şeyi kolayca kurabilirsiniz."
[[slides.items]]
icon = "community"
title = "Topluluk Desteği"
description = "forum.pisilinux.org adresindeki aktif topluluğumuz sorularınızı yanıtlamak için hazır. Wiki sayfalarımız da her an erişilebilir."
[[slides.items]]
icon = "pisi_speed"
title = "Hız ve Performans"
description = "Optimize edilmiş çekirdek ve başlangıç servisleri sayesinde PisiLinux saniyeler içinde açılır ve minimum kaynak kullanır."
[[slides.items]]
icon = "securety"
title = "Güvenlik Önce Gelir"
description = "Düzenli güvenlik güncellemeleri, AppArmor profilleri ve güvenli varsayılan ayarlarla sisteminizi koruyun."
[[slides.items]]
icon = "customization"
title = "Özelleştirme"
description = "Masaüstü ortamından tema renklerine, panel düzeninden klavye kısayollarına kadar her şeyi zevkinize göre ayarlayın."
# ── XFCE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "xfce"
icon = "xfce"
title = "PisiLinux XFCE"
description = "Hafif, hızlı ve kullanıcı dostu XFCE masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir."
[[slides.items]]
desktop = "xfce"
icon = "xfce_panel"
title = "XFCE Panel"
description = "Geleneksel panel düzeni ile görev çubuğu, uygulama menüsü ve sistem tepsisi her zaman elinizin altında."
[[slides.items]]
desktop = "xfce"
icon = "file_manager"
title = "Thunar Dosya Yöneticisi"
description = "Hızlı ve hafif Thunar ile dosya işlemlerinizi kolayca yapın. Ağaç görünümü, sekmeli gezinme ve özelleştirilebilir özellikler sunar."
[[slides.items]]
desktop = "xfce"
icon = "xfce_settings"
title = "Ayarlar Merkezi"
description = "Klasik uygulama menüsüne modern bir alternatif. Whisker Menu ile uygulamaları kolayca bulun, favorilerinizi düzenleyin."
# ── KDE Plasma ────────────────────────────────────────────────────
[[slides.items]]
desktop = "kde"
icon = "kde"
title = "PisiLinux KDE Plasma"
description = "Modern, özelleştirilebilir ve güçlü KDE Plasma masaüstü ortamıyla tanışın. Her şey elinizin altında."
[[slides.items]]
desktop = "kde"
icon = "file_manager"
title = "Dolphin Dosya Yöneticisi"
description = "Sekmeli gezinme, ikili panel ve güçlü arama özelliğiyle Dolphin dosyalarınızı yönetmeyi kolaylaştırır."
[[slides.items]]
desktop = "kde"
icon = "kde_connect"
title = "KDE Bağlan"
description = "Telefonunuzu bilgisayarınıza bağlayın. Bildirimler, dosya aktarımı ve klipboard paylaşımı KDE Connect ile mümkün."
[[slides.items]]
desktop = "kde"
icon = "discover"
title = "Discover Yazılım Merkezi"
description = "Discover ile uygulamaları görsel arayüzden kolayca bulun, kurun ve güncelleyin."
# ── GNOME ─────────────────────────────────────────────────────────
[[slides.items]]
desktop = "gnome"
icon = "gnome"
title = "PisiLinux GNOME"
description = "Sade ve odaklı GNOME masaüstü ortamı ile dikkat dağıtıcı unsurları azaltın, işinize konsantre olun."
[[slides.items]]
desktop = "gnome"
icon = "gnome_search"
title = "GNOME Arama"
description = "Super tuşuna basıp yazmaya başlayın. Uygulama, dosya ve ayarlara anında ulaşın."
[[slides.items]]
desktop = "gnome"
icon = "gnome_extensions"
title = "GNOME Eklentileri"
description = "extensions.gnome.org adresinden yüzlerce eklentiyle masaüstünüzü ihtiyaçlarınıza göre şekillendirin."
# ── MATE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "mate"
icon = "mate"
title = "PisiLinux MATE"
description = "Geleneksel masaüstü düzenini sevenler için MATE. GTK+ 3 ile geliştirilmiş, modern ve akıcı bir deneyim sunar."
[[slides.items]]
desktop = "mate"
icon = "file_manager"
title = "Caja Dosya Yöneticisi"
description = "MATE'in güçlü dosya yöneticisi Caja ile dosya işlemlerinizi kolayca yapın. Çift panel, sekmeli gezinme ve geniş özellikler sunar."
[[slides.items]]
desktop = "mate"
icon = "mate_settings"
title = "MATE Ayarları"
description = "Görünüm, ekran, klavye ve daha fazlası için kapsamlı ayarlarla MATE masaüstünüzü özelleştirin."
[[slides.items]]
desktop = "mate"
icon = "customization"
title = "Zengin Uygulama Paketi"
description = "MATE; metin editöründen görüntü görüntüleme yazılımına kadar geniş, iyi entegre edilmiş klasik uygulamalar sunar."
# ── Budgie ────────────────────────────────────────────────────────
[[slides.items]]
desktop = "budgie"
icon = "budgie"
title = "PisiLinux Budgie"
description = "Modern, sade ve şık Budgie masaüstü. GNOME altyapısı üzerine inşa edilmiş, kullanıcı dostudur."
[[slides.items]]
desktop = "budgie"
icon = "budgie_applets"
title = "Budgie Desktop Applets"
description = "Budgie'nin panel appletleri sayesinde hava durumu, sistem izleyici, takvim gibi araçları kolayca ekleyin."
[[slides.items]]
desktop = "budgie"
icon = "budgie_raven"
title = "Raven Bildirim Merkezi"
description = "Raven panelinden ses seviyesi, medya kontrolleri, bildirimler ve hızlı ayarlara erişin."
[[slides.items]]
desktop = "budgie"
icon = "budgie_gtk_theme"
title = "GTK Tema Desteği"
description = "Budgie, GTK temalarını ve GNOME uygulamalarını sorunsuz çalıştırır, görsel bütünlük sağlar."
# ── Cinnamon ──────────────────────────────────────────────────────
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon"
title = "PisiLinux Cinnamon"
description = "Geleneksel masaüstü deneyimini modern özelliklerle birleştiren Cinnamon, güçlü ve esnek bir ortam sunar."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_panel"
title = "Cinnamon Paneli"
description = "Panelinizi applet'ler, başlatıcılar ve sistem göstergeleriyle özelleştirin; klasik ama güçlü bir masaüstü deneyimi yaşayın."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_settings"
title = "Ayarlar Yöneticisi"
description = "Cinnamon'un kapsamlı Ayarlar Yöneticisi aracılığıyla masaüstünüzün her yönünü özelleştirin."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_desklets"
title = "Cinnamon Desklets"
description = "Masaüstü widget'ları ile notlar, saatler ve sistem bilgileri her zaman görünür kalsın."
# ── LXQt ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lxqt"
icon = "lxqt"
title = "PisiLinux LXQt"
description = "Hafif, hızlı ve modern LXQt masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir."
[[slides.items]]
desktop = "lxqt"
icon = "file_manager"
title = "LXQt Panel"
description = "Geleneksel panel düzeni ile görev çubuğu, uygulama başlatıcı ve sistem tepsisi her zaman elinizin altında."
[[slides.items]]
desktop = "lxqt"
icon = "file_manager"
title = "PCManFM-Qt Dosya Yöneticisi"
description = "Hafif ve hızlı PCManFM-Qt ile dosya işlemlerini kolayca yönetin. Sekmeli gezinme ve özelleştirilebilir arayüz sunar."
# ── LXDE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lxde"
icon = "lxde"
title = "PisiLinux LXDE"
description = "Efsanevi hafif masaüstü LXDE. Çok düşük kaynak tüketimi ve güvenilir yapısıyla tanınır."
[[slides.items]]
desktop = "lxde"
icon = "lxde"
title = "Openbox Tabanlı"
description = "LXDE, Openbox pencere yöneticisini kullanır. İnce ayarlar ile akıcı bir deneyim sunar."
[[slides.items]]
desktop = "lxde"
icon = "file_manager"
title = "PCManFM Dosya Yöneticisi"
description = "PCManFM, hızlı ve hafif yapısıyla dosya işlemlerinizi kolaylaştırır, ağaç görünümü ve sekme destekler."
# ── Lumina ────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lumina"
icon = "lumina"
title = "PisiLinux Lumina"
description = "Hafif ve bağımsız Lumina masa üstü. Qt tabanlı, BSD ve Linux sistemlerde düzgün çalışan minimal bir ortam."
[[slides.items]]
desktop = "lumina"
icon = "customization"
title = "Tema Özelleştirme"
description = "Lumina'nın tema sistemiyle renk şemaları ve arayüz stilini kolayca özelleştirin."
# ──────────────────────────────────────────────────────────────────
# [theme] – Arayüz renk şeması (Hex)
# ──────────────────────────────────────────────────────────────────
+298
View File
@@ -20,6 +20,304 @@ title = "YALI Installation Wizard"
# Subtitle displayed in the left sidebar
subtitle = "YALI"
# ──────────────────────────────────────────────────────────────────
# [slides] – Slides shown during installation
# ──────────────────────────────────────────────────────────────────
[slides]
# Slide transition duration (seconds)
interval_secs = 6
# ── Slide Icons → File Path Mappings ─────────────────────────────
[slides.icons]
pisilogo = "assets/pisilinux-logo.png"
pisi = "assets/pisi-software-center.png"
community = "assets/community.png"
pisi_speed = "assets/pisi_speed.png"
securety = "assets/securety.png"
customization = "assets/customization.png"
# Masaüstü İkonları
budgie = "assets/budgie.png"
budgie_applets = "assets/budgie_applets.png"
budgie_raven = "assets/budgie_raven.png"
budgie_gtk_theme = "assets/budgie_gtk_theme.png"
cinnamon = "assets/cinnamon.png"
cinnamon_panel = "assets/cinnamon_panel.png"
cinnamon_settings = "assets/cinnamon_settings.png"
cinnamon_desklets = "assets/cinnamon_desklets.png"
gnome = "assets/gnome.png"
gnome_search = "assets/gnome_search.png"
gnome_extensions = "assets/gnome_extensions.png"
kde = "assets/kde.png"
file_manager = "assets/file_manager.png"
kde_connect = "assets/kde_connect.png"
discover = "assets/discover.png"
lxde = "assets/lxde.png"
lxde_openbox = "assets/lxde_openbox.png"
lxde_lxpanel = "assets/lxde_lxpanel.png"
lxqt = "assets/lxqt.png"
lxqt_panel = "assets/lxqt_panel.png"
lumina = "assets/lumina.png"
lumina_applications = "assets/lumina_applications.png"
lumina_theme = "assets/lumina_theme.png"
lumina_panel = "assets/lumina_panel.png"
mate = "assets/mate.png"
mate_settings = "assets/mate_settings.png"
mate_applications = "assets/mate_applications.png"
xfce = "assets/xfce.png"
xfce_panel = "assets/xfce_panel.png"
xfce_settings = "assets/xfce_settings.png"
# ── Common Slides ────────────────────────────────────────────────
[[slides.items]]
icon = "pisilogo"
title = "Welcome to Pisi GNU/Linux"
description = "Pisi is a Turkish-supported GNU/Linux distribution that combines speed, stability, and ease of use."
[[slides.items]]
icon = "pisi"
title = "PISI Package Manager"
description = "Access thousands of packages with a single command. Install anything you need easily with `pisi install <package>`."
[[slides.items]]
icon = "community"
title = "Community Support"
description = "Our active community at forum.pisilinux.org is ready to answer your questions. Our wiki pages are always accessible."
[[slides.items]]
icon = "pisi_speed"
title = "Speed & Performance"
description = "Thanks to an optimized kernel and startup services, PisiLinux boots in seconds and uses minimal resources."
[[slides.items]]
icon = "securety"
title = "Security First"
description = "Keep your system secure with regular security updates, AppArmor profiles, and secure default settings."
[[slides.items]]
icon = "customization"
title = "Customization"
description = "Tailor everything to your taste — from the desktop environment and theme colors to panel layout and keyboard shortcuts."
# ── Budgie ────────────────────────────────────────────────────────
[[slides.items]]
desktop = "budgie"
icon = "budgie"
title = "PisiLinux Budgie"
description = "A modern, clean, and elegant Budgie desktop. Built on the GNOME stack and user-friendly."
[[slides.items]]
desktop = "budgie"
icon = "budgie_applets"
title = "Budgie Desktop Applets"
description = "Easily add tools like weather, system monitor, and calendar via Budgie's panel applets."
[[slides.items]]
desktop = "budgie"
icon = "budgie_raven"
title = "Raven Notification Center"
description = "Access volume, media controls, notifications, and quick settings from the Raven panel."
[[slides.items]]
desktop = "budgie"
icon = "budgie_gtk_theme"
title = "GTK Theme Support"
description = "Budgie runs GTK themes and GNOME applications seamlessly, ensuring visual consistency."
# ── Cinnamon ──────────────────────────────────────────────────────
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon"
title = "PisiLinux Cinnamon"
description = "Cinnamon combines the traditional desktop experience with modern features, offering a powerful and flexible environment."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_panel"
title = "Cinnamon Panel"
description = "Customize your panel with applets, launchers, and system indicators for a classic yet powerful desktop."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_settings"
title = "Settings Manager"
description = "Fine-tune every aspect of your Cinnamon desktop through the comprehensive Settings Manager."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_desklets"
title = "Cinnamon Desklets"
description = "Keep notes, clocks, and system info always visible on your desktop with Cinnamon Desklets."
# ── GNOME ─────────────────────────────────────────────────────────
[[slides.items]]
desktop = "gnome"
icon = "gnome"
title = "PisiLinux GNOME"
description = "Reduce distractions and focus on your work with the clean and focused GNOME desktop environment."
[[slides.items]]
desktop = "gnome"
icon = "gnome_search"
title = "GNOME Search"
description = "Press the Super key and start typing. Instantly reach applications, files, and settings."
[[slides.items]]
desktop = "gnome"
icon = "gnome_extensions"
title = "GNOME Extensions"
description = "Shape your desktop to your needs with hundreds of extensions from extensions.gnome.org."
# ── KDE ───────────────────────────────────────────────────────────
[[slides.items]]
desktop = "kde"
icon = "kde"
title = "PisiLinux KDE Plasma"
description = "Meet the modern, customizable, and powerful KDE Plasma desktop. Everything is at your fingertips."
[[slides.items]]
desktop = "kde"
icon = "file_manager"
title = "Dolphin File Manager"
description = "Dolphin makes managing your files easy with tabbed browsing, dual-panel view, and powerful search."
[[slides.items]]
desktop = "kde"
icon = "kde_connect"
title = "KDE Connect"
description = "Connect your phone to your computer. Notifications, file transfer, and clipboard sharing — all with KDE Connect."
[[slides.items]]
desktop = "kde"
icon = "discover"
title = "Discover Software Center"
description = "Easily find, install, and update applications from the visual interface with Discover."
# ── LXQt ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lxqt"
icon = "lxqt"
title = "PisiLinux LXQt"
description = "A lightweight, fast, and modern LXQt desktop environment. Low resource consumption, high performance."
[[slides.items]]
desktop = "lxqt"
icon = "lxqt_panel"
title = "LXQt Panel"
description = "The traditional panel layout puts the taskbar, application launcher, and system tray always within reach."
[[slides.items]]
desktop = "lxqt"
icon = "file_manager"
title = "PCManFM-Qt File Manager"
description = "Manage files effortlessly with the lightweight and fast PCManFM-Qt. Tabbed navigation and a customizable interface."
# ── LXDE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lxde"
icon = "lxde"
title = "PisiLinux LXDE"
description = "The legendary lightweight LXDE desktop. Known for extremely low resource usage and a reliable build."
[[slides.items]]
desktop = "lxde"
icon = "lxde_openbox"
title = "Openbox-Based"
description = "LXDE uses the Openbox window manager. Fine-tune it for a smooth and responsive experience."
[[slides.items]]
desktop = "lxde"
icon = "file_manager"
title = "PCManFM File Manager"
description = "PCManFM speeds up your file operations with its fast and lightweight build, supporting tree view and tabs."
# ── Lumina ────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lumina"
icon = "lumina"
title = "PisiLinux Lumina"
description = "A lightweight and independent Lumina desktop. Qt-based, designed to run cleanly on BSD and Linux systems."
[[slides.items]]
desktop = "lumina"
icon = "lumina_applications"
title = "Application Support"
description = "Lumina works well with core utilities and many popular Qt apps, providing a practical everyday experience."
[[slides.items]]
desktop = "lumina"
icon = "lumina_theme"
title = "Theme Customization"
description = "Easily customize color schemes and interface styles with Lumina's flexible theme system."
[[slides.items]]
desktop = "lumina"
icon = "lumina_panel"
title = "Lumina Panel"
description = "A clean panel with a taskbar, application launcher, and system tray keeps your desktop organized."
# ── MATE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "mate"
icon = "mate"
title = "PisiLinux MATE"
description = "MATE for those who love the traditional desktop layout. Developed with GTK+ 3 for a modern and smooth experience."
[[slides.items]]
desktop = "mate"
icon = "file_manager"
title = "Caja File Manager"
description = "Manage your files easily with MATE's powerful Caja. Dual panel, tabbed navigation, and rich features."
[[slides.items]]
desktop = "mate"
icon = "mate_settings"
title = "MATE Settings"
description = "Customize your MATE desktop with comprehensive settings for appearance, display, keyboard, and more."
[[slides.items]]
desktop = "mate"
icon = "mate_applications"
title = "Rich Application Suite"
description = "MATE comes with a rich set of classic applications — from text editors to image viewers, all well-integrated."
# ── XFCE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "xfce"
icon = "xfce"
title = "PisiLinux XFCE"
description = "A lightweight, fast, and user-friendly XFCE desktop environment. Low resource consumption, high performance."
[[slides.items]]
desktop = "xfce"
icon = "xfce_panel"
title = "XFCE Panel"
description = "The traditional panel layout puts the taskbar, application menu, and system tray always within reach."
[[slides.items]]
desktop = "xfce"
icon = "file_manager"
title = "Thunar File Manager"
description = "Manage your files easily with the fast and lightweight Thunar. Tree view, tabbed navigation, and customizable features."
[[slides.items]]
desktop = "xfce"
icon = "xfce_settings"
title = "Whisker Menu"
description = "A modern alternative to the classic application menu. Find apps easily and organize your favorites with Whisker Menu."
# ──────────────────────────────────────────────────────────────────
# [theme] – UI Style & Color Palette
# ──────────────────────────────────────────────────────────────────
+303
View File
@@ -30,6 +30,309 @@ subtitle = "YALI"
# ──────────────────────────────────────────────────────────────────
[slides]
# Slayt geçiş süresi (saniye)
interval_secs = 6
# ── Slayt ikonları → görsel dosya yolu eşlemesi ──────────────────
# Geliştirici notu: Yeni ikon eklemek için sadece buraya satır ekle.
[slides.icons]
pisilogo = "assets/pisilinux-logo.png"
pisi = "assets/pisi-software-center.png"
community = "assets/community.png"
pisi_speed = "assets/pisi_speed.png"
securety = "assets/securety.png"
customization = "assets/customization.png"
budgie = "assets/budgie.png"
budgie_applets = "assets/budgie_applets.png"
budgie_raven = "assets/budgie_raven.png"
budgie_gtk_theme = "assets/budgie_gtk_theme.png"
cinnamon = "assets/cinnamon.png"
cinnamon_panel = "assets/cinnamon_panel.png"
cinnamon_settings = "assets/cinnamon_settings.png"
cinnamon_desklets = "assets/cinnamon_desklets.png"
gnome = "assets/gnome.png"
gnome_search = "assets/gnome_search.png"
gnome_extensions = "assets/gnome_extensions.png"
kde = "assets/kde.png"
kde_connect = "assets/kde_connect.png"
discover = "assets/discover.png"
file_manager = "assets/file_manager.png"
lxde = "assets/lxde.png"
lxqt = "assets/lxqt.png"
lumina = "assets/lumina.png"
mate = "assets/mate.png"
mate_settings = "assets/mate_settings.png"
xfce = "assets/xfce.png"
xfce_settings = "assets/xfce_settings.png"
xfce_panel = "assets/xfce_panel.png"
# ══════════════════════════════════════════════════════════════════
# [[slides.items]] – Slayt içerikleri
# ══════════════════════════════════════════════════════════════════
#
# Her [[slides.items]] bloğu bir slaytı tanımlar.
#
# Alanlar:
# icon = Görsel için yukarıdaki [slides.icons] anahtarı
# title = Slayt başlığı
# description = Slayt açıklama metni
# desktop = (isteğe bağlı) Yalnızca belirli masaüstünde göster.
# Boş bırakılırsa tüm masaüstlerinde gösterilir.
# Değerler: "xfce", "kde", "gnome", "mate", "budgie",
# "cinnamon", "lxde", "lxqt", "lumina"
#
# Geliştirici notu:
# Yeni bir slayt eklemek veya metni değiştirmek için yalnızca
# buraya [[slides.items]] bloğu ekleyin / düzenleyin.
# Rust koduna dokunmanıza gerek yoktur.
# ──────────────────────────────────────────────────────────────────
# ── Tüm masaüstlerinde gösterilen ortak slaytlar ──────────────────
[[slides.items]]
icon = "pisilogo"
title = "Pisi GNU/Linux'a Hoş Geldiniz"
description = "Pisi; hız, kararlılık ve kullanım kolaylığını bir araya getiren Türkçe destekli bir GNU/Linux dağıtımıdır."
[[slides.items]]
icon = "pisi"
title = "PISI Paket Yöneticisi"
description = "Binlerce pakete tek komutla erişin. `pisi install <paket>` ile ihtiyacınız olan her şeyi kolayca kurabilirsiniz."
[[slides.items]]
icon = "community"
title = "Topluluk Desteği"
description = "forum.pisilinux.org adresindeki aktif topluluğumuz sorularınızı yanıtlamak için hazır. Wiki sayfalarımız da her an erişilebilir."
[[slides.items]]
icon = "pisi_speed"
title = "Hız ve Performans"
description = "Optimize edilmiş çekirdek ve başlangıç servisleri sayesinde PisiLinux saniyeler içinde açılır ve minimum kaynak kullanır."
[[slides.items]]
icon = "securety"
title = "Güvenlik Önce Gelir"
description = "Düzenli güvenlik güncellemeleri, AppArmor profilleri ve güvenli varsayılan ayarlarla sisteminizi koruyun."
[[slides.items]]
icon = "customization"
title = "Özelleştirme"
description = "Masaüstü ortamından tema renklerine, panel düzeninden klavye kısayollarına kadar her şeyi zevkinize göre ayarlayın."
# ── XFCE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "xfce"
icon = "xfce"
title = "PisiLinux XFCE"
description = "Hafif, hızlı ve kullanıcı dostu XFCE masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir."
[[slides.items]]
desktop = "xfce"
icon = "xfce_panel"
title = "XFCE Panel"
description = "Geleneksel panel düzeni ile görev çubuğu, uygulama menüsü ve sistem tepsisi her zaman elinizin altında."
[[slides.items]]
desktop = "xfce"
icon = "file_manager"
title = "Thunar Dosya Yöneticisi"
description = "Hızlı ve hafif Thunar ile dosya işlemlerinizi kolayca yapın. Ağaç görünümü, sekmeli gezinme ve özelleştirilebilir özellikler sunar."
[[slides.items]]
desktop = "xfce"
icon = "xfce_settings"
title = "Ayarlar Merkezi"
description = "Klasik uygulama menüsüne modern bir alternatif. Whisker Menu ile uygulamaları kolayca bulun, favorilerinizi düzenleyin."
# ── KDE Plasma ────────────────────────────────────────────────────
[[slides.items]]
desktop = "kde"
icon = "kde"
title = "PisiLinux KDE Plasma"
description = "Modern, özelleştirilebilir ve güçlü KDE Plasma masaüstü ortamıyla tanışın. Her şey elinizin altında."
[[slides.items]]
desktop = "kde"
icon = "file_manager"
title = "Dolphin Dosya Yöneticisi"
description = "Sekmeli gezinme, ikili panel ve güçlü arama özelliğiyle Dolphin dosyalarınızı yönetmeyi kolaylaştırır."
[[slides.items]]
desktop = "kde"
icon = "kde_connect"
title = "KDE Bağlan"
description = "Telefonunuzu bilgisayarınıza bağlayın. Bildirimler, dosya aktarımı ve klipboard paylaşımı KDE Connect ile mümkün."
[[slides.items]]
desktop = "kde"
icon = "discover"
title = "Discover Yazılım Merkezi"
description = "Discover ile uygulamaları görsel arayüzden kolayca bulun, kurun ve güncelleyin."
# ── GNOME ─────────────────────────────────────────────────────────
[[slides.items]]
desktop = "gnome"
icon = "gnome"
title = "PisiLinux GNOME"
description = "Sade ve odaklı GNOME masaüstü ortamı ile dikkat dağıtıcı unsurları azaltın, işinize konsantre olun."
[[slides.items]]
desktop = "gnome"
icon = "gnome_search"
title = "GNOME Arama"
description = "Super tuşuna basıp yazmaya başlayın. Uygulama, dosya ve ayarlara anında ulaşın."
[[slides.items]]
desktop = "gnome"
icon = "gnome_extensions"
title = "GNOME Eklentileri"
description = "extensions.gnome.org adresinden yüzlerce eklentiyle masaüstünüzü ihtiyaçlarınıza göre şekillendirin."
# ── MATE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "mate"
icon = "mate"
title = "PisiLinux MATE"
description = "Geleneksel masaüstü düzenini sevenler için MATE. GTK+ 3 ile geliştirilmiş, modern ve akıcı bir deneyim sunar."
[[slides.items]]
desktop = "mate"
icon = "file_manager"
title = "Caja Dosya Yöneticisi"
description = "MATE'in güçlü dosya yöneticisi Caja ile dosya işlemlerinizi kolayca yapın. Çift panel, sekmeli gezinme ve geniş özellikler sunar."
[[slides.items]]
desktop = "mate"
icon = "mate_settings"
title = "MATE Ayarları"
description = "Görünüm, ekran, klavye ve daha fazlası için kapsamlı ayarlarla MATE masaüstünüzü özelleştirin."
[[slides.items]]
desktop = "mate"
icon = "customization"
title = "Zengin Uygulama Paketi"
description = "MATE; metin editöründen görüntü görüntüleme yazılımına kadar geniş, iyi entegre edilmiş klasik uygulamalar sunar."
# ── Budgie ────────────────────────────────────────────────────────
[[slides.items]]
desktop = "budgie"
icon = "budgie"
title = "PisiLinux Budgie"
description = "Modern, sade ve şık Budgie masaüstü. GNOME altyapısı üzerine inşa edilmiş, kullanıcı dostudur."
[[slides.items]]
desktop = "budgie"
icon = "budgie_applets"
title = "Budgie Desktop Applets"
description = "Budgie'nin panel appletleri sayesinde hava durumu, sistem izleyici, takvim gibi araçları kolayca ekleyin."
[[slides.items]]
desktop = "budgie"
icon = "budgie_raven"
title = "Raven Bildirim Merkezi"
description = "Raven panelinden ses seviyesi, medya kontrolleri, bildirimler ve hızlı ayarlara erişin."
[[slides.items]]
desktop = "budgie"
icon = "budgie_gtk_theme"
title = "GTK Tema Desteği"
description = "Budgie, GTK temalarını ve GNOME uygulamalarını sorunsuz çalıştırır, görsel bütünlük sağlar."
# ── Cinnamon ──────────────────────────────────────────────────────
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon"
title = "PisiLinux Cinnamon"
description = "Geleneksel masaüstü deneyimini modern özelliklerle birleştiren Cinnamon, güçlü ve esnek bir ortam sunar."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_panel"
title = "Cinnamon Paneli"
description = "Panelinizi applet'ler, başlatıcılar ve sistem göstergeleriyle özelleştirin; klasik ama güçlü bir masaüstü deneyimi yaşayın."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_settings"
title = "Ayarlar Yöneticisi"
description = "Cinnamon'un kapsamlı Ayarlar Yöneticisi aracılığıyla masaüstünüzün her yönünü özelleştirin."
[[slides.items]]
desktop = "cinnamon"
icon = "cinnamon_desklets"
title = "Cinnamon Desklets"
description = "Masaüstü widget'ları ile notlar, saatler ve sistem bilgileri her zaman görünür kalsın."
# ── LXQt ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lxqt"
icon = "lxqt"
title = "PisiLinux LXQt"
description = "Hafif, hızlı ve modern LXQt masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir."
[[slides.items]]
desktop = "lxqt"
icon = "file_manager"
title = "LXQt Panel"
description = "Geleneksel panel düzeni ile görev çubuğu, uygulama başlatıcı ve sistem tepsisi her zaman elinizin altında."
[[slides.items]]
desktop = "lxqt"
icon = "file_manager"
title = "PCManFM-Qt Dosya Yöneticisi"
description = "Hafif ve hızlı PCManFM-Qt ile dosya işlemlerini kolayca yönetin. Sekmeli gezinme ve özelleştirilebilir arayüz sunar."
# ── LXDE ──────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lxde"
icon = "lxde"
title = "PisiLinux LXDE"
description = "Efsanevi hafif masaüstü LXDE. Çok düşük kaynak tüketimi ve güvenilir yapısıyla tanınır."
[[slides.items]]
desktop = "lxde"
icon = "lxde"
title = "Openbox Tabanlı"
description = "LXDE, Openbox pencere yöneticisini kullanır. İnce ayarlar ile akıcı bir deneyim sunar."
[[slides.items]]
desktop = "lxde"
icon = "file_manager"
title = "PCManFM Dosya Yöneticisi"
description = "PCManFM, hızlı ve hafif yapısıyla dosya işlemlerinizi kolaylaştırır, ağaç görünümü ve sekme destekler."
# ── Lumina ────────────────────────────────────────────────────────
[[slides.items]]
desktop = "lumina"
icon = "lumina"
title = "PisiLinux Lumina"
description = "Hafif ve bağımsız Lumina masa üstü. Qt tabanlı, BSD ve Linux sistemlerde düzgün çalışan minimal bir ortam."
[[slides.items]]
desktop = "lumina"
icon = "customization"
title = "Tema Özelleştirme"
description = "Lumina'nın tema sistemiyle renk şemaları ve arayüz stilini kolayca özelleştirin."
# ──────────────────────────────────────────────────────────────────
# [theme] – Arayüz renk şeması (Hex)
# ──────────────────────────────────────────────────────────────────
+2 -21
View File
@@ -57,7 +57,7 @@ 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_label = "Password (at least 8 characters...)"
password_confirm_label = "Confirm password"
hostname_label = "Computer name"
username_invalid = "Invalid username. Must start with a lowercase letter or underscore, max 32 characters."
@@ -98,7 +98,7 @@ finish_title = "Installation Complete"
# ── Execution (extra) ─────────────────────────────────────
install_failed = "Installation failed"
install_done = "Installation complete! Click Next to continue."
install_current_file = "Copy file"
install_current_file = "Current file"
retry_install = "Retry"
finish_next = "Finish →"
@@ -141,13 +141,6 @@ display_manager_autologin_hint = "(first user)"
display_manager_de_not_found = "(not detected)"
display_manager_note = "Note: The display manager will be enabled by default on the installed system."
mp_size_remaining = "Remaining Space"
mp_size_available = "Available"
mp_create_here = "Create Here"
mp_will_delete = "will be deleted"
mp_restore = "Restore"
mp_free_space = "Free Space"
mp_bar_now = "Current Layout"
mp_bar_after = "After Installation"
# ── UI / General ──────────────────────────────────────────
installer = "YALI Installation Tool"
@@ -211,7 +204,6 @@ job_timezone = "Setting timezone"
job_datetime = "Configuring date & time"
job_keyboard = "Configuring keyboard layout"
job_user = "Creating user account"
job_autologin = "Applying automatic login"
job_install_packages = "Installing selected packages"
job_ldconfig = "Configuring shared libraries (ldconfig)"
job_update_env = "Updating system environment"
@@ -224,7 +216,6 @@ mp_device = "Device"
mp_size = "Size"
mp_fstype = "Filesystem"
mp_mountpoint = "Mount Point"
mp_format_status = "Format"
mp_action = "Action"
mp_add = "Add Partition"
mp_delete = "Delete"
@@ -238,18 +229,9 @@ mp_fs_btrfs = "btrfs"
mp_fs_xfs = "xfs"
mp_fs_fat32 = "FAT32 (EFI)"
mp_fs_swap = "swap"
mp_skip_format = "Skip Formatting (preserve existing data)"
mp_size_gb = "%{n} GB"
mp_size_mb = "%{n} MB"
# ── Table Column Headers ────────────────────────────────────
column_device = "Device"
column_mount_point = "Mount Point"
column_label = "Label"
column_type = "Type"
column_format = "Format"
column_size = "Size"
check_ram = "RAM"
check_ram_fail = "%{mb} MB RAM available. Minimum 1 GB required."
@@ -317,7 +299,6 @@ admin_password_short = "Root password must be at least 8 characters."
datetime = "Date & Time"
datetime_title = "Date & Time Settings"
datetime_description = "Configure the system date and time."
datetime_current = "Current time:"
datetime_ntp_label = "Network Time (NTP):"
datetime_ntp_on = "Enabled — time syncs automatically"
datetime_ntp_off = "Disabled — set manually"
+9 -29
View File
@@ -57,7 +57,7 @@ users_title = "Kullanıcı Bilgileri"
users_description = "Sisteminiz için bir kullanıcı hesabı oluşturun."
real_name_label = "Gerçek Adınız."
username_label = "Kullanıcı adı"
password_label = "Parola"
password_label = "Parola (en az 8 karakter olmalıdır...)"
password_confirm_label = "Parola (tekrar)"
hostname_label = "Bilgisayar adı"
username_invalid = "Geçersiz kullanıcı adı. Küçük harf veya alt çizgi ile başlamalı, en fazla 32 karakter."
@@ -81,7 +81,7 @@ summary_hostname = "Bilgisayar adı"
summary_erase_mode = "Tüm disk silinecek"
summary_manual_mode = "Manuel bölümleme"
summary_not_selected = "Seçilmedi"
summary_warning = " Dikkat: İleri'ye basıldığında seçilen diskteki tüm veriler kalıcı olarak silinecektir!"
summary_warning = "⚠️ Dikkat: İleri'ye basıldığında seçilen diskteki tüm veriler kalıcı olarak silinecektir!"
# ── Execution ─────────────────────────────────────────────
execution = "Kurulum"
@@ -97,7 +97,7 @@ finish_title = "Kurulum Tamamlandı"
# ── Execution (ek) ────────────────────────────────────────
install_failed = "Kurulum başarısız"
install_done = "Kurulum tamamlandı! Devam etmek için İleri'ye basın."
install_current_file = "Kopyalanan dosya"
install_current_file = "İşlemdeki dosya"
retry_install = "Yeniden Dene"
finish_next = "Tamamlandı →"
@@ -117,14 +117,6 @@ disk_too_small = "Disk çok küçük"
summary_partition_plan = "Bölümleme"
summary_custom_partitions = "Manuel Bölümler"
mp_size_remaining = "Kalan Alan"
mp_size_available = "Kullanılabilir"
mp_create_here = "Buraya Oluştur"
mp_will_delete = "silinecek"
mp_restore = "Geri Al"
mp_free_space = "Boş Alan"
mp_bar_now = "Mevcut Durum"
mp_bar_after = "Kurulum Sonrası"
# ── UI / Genel ────────────────────────────────────────────
installer = "PisiLinux Kurulum Aracı"
@@ -196,13 +188,12 @@ mp_device = "Aygıt"
mp_size = "Boyut"
mp_fstype = "Dosya Sistemi"
mp_mountpoint = "Bağlama Noktası"
mp_format_status = "Biçim"
mp_action = "İşlem"
mp_add = "Bölüm Ekle"
mp_delete = "Sil"
mp_apply = "Değişiklikleri Uygula"
mp_no_efi = "EFI bölümü atanmadı. UEFI önyüklemesi için gerekli."
mp_no_root = "Kök (/) bölümü atanmadı."
mp_no_efi = "⚠️ EFI bölümü atanmadı. UEFI önyüklemesi için gerekli."
mp_no_root = "⚠️ Kök (/) bölümü atanmadı."
mp_confirm_delete = "%{dev} bölümü silinsin mi? Bu işlem geri alınamaz."
mp_plan_title = "Değişiklik Özeti"
mp_fs_ext4 = "ext4"
@@ -210,18 +201,9 @@ mp_fs_btrfs = "btrfs"
mp_fs_xfs = "xfs"
mp_fs_fat32 = "FAT32 (EFI)"
mp_fs_swap = "takas (swap)"
mp_skip_format = "Biçimlendirmeyi Atla (mevcut veri korunur)"
mp_size_gb = "%{n} GB"
mp_size_mb = "%{n} MB"
# ── Tablo Sütun Başlıkları ────────────────────────────────────
column_device = "Aygıt"
column_mount_point = "Bağlama Noktası"
column_label = "Etiket"
column_type = "Tür"
column_format = "Biçim"
column_size = "Boyut"
check_ram = "RAM"
check_ram_fail = "%{mb} MB RAM mevcut. Kurulum için minimum 1 GB gerekli."
@@ -255,16 +237,16 @@ job_mount_custom = "Manuel bölümler bağlanıyor"
# ── Ek Yerelleştirme / i18n ──────────────────────────────────
mp_delete_confirm_title = "Bölümü Sil"
mp_error_empty_mountpoint = "Bağlama noktası boş olamaz."
mp_plan_valid = "Bölüm planı geçerli. İleri tuşuna basabilirsiniz."
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_add_btn = "✓ Ekle"
mp_size_hint = "Boyut (MB, 0=kalan):"
mp_delete_confirm_yes = "Evet, Sil"
mp_delete_confirm_yes = "✓ Evet, Sil"
mp_new_partition = "Yeni Bölüm"
mp_edit_partition = "Düzenle"
mp_save = "Kaydet"
mp_save = "✓ Kaydet"
plan_root_suffix = " (kök)"
disk_min_required = " ↳ %{err} (min. %{n} GB gerekli)"
@@ -308,7 +290,6 @@ job_update_env = "Sistem ortamı güncelleniyor"
job_cleanup_live = "Canlı ortam izleri temizleniyor"
job_datetime = "Tarih & saat yapılandırılıyor"
job_display_manager = "Görüntü yöneticisi yapılandırılıyor"
job_autologin = "Otomatik oturum açma uygulanıyor"
job_netinstall = "Ek paketler kuruluyor"
job_setup_lvm = "LVM yapılandırılıyor"
@@ -330,7 +311,6 @@ admin_password_short = "Root parolası en az 8 karakter olmalıdır."
datetime = "Tarih & Saat"
datetime_title = "Tarih & Saat Ayarları"
datetime_description = "Sistem tarihini ve saatini yapılandırın."
datetime_current = "Şu anki zaman:"
datetime_ntp_label = "Ağ Zamanı (NTP):"
datetime_ntp_on = "Etkin — saat otomatik eşitlenir"
datetime_ntp_off = "Devre dışı — elle ayarlanır"
Binary file not shown.

Before

Width:  |  Height:  |  Size: 197 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.0 KiB

-243
View File
@@ -1,243 +0,0 @@
# ╔══════════════════════════════════════════════════════════════════╗
# ║ Yali-RS — Slide Content (English) ║
# ╚══════════════════════════════════════════════════════════════════╝
# Slide transition duration (seconds)
interval_secs = 6
[[items]]
icon = "slides/img/pisilinux-logo.png"
title = "Welcome to Pisi GNU/Linux"
description = "Pisi is a Turkish-supported GNU/Linux distribution that combines speed, stability, and ease of use."
[[items]]
icon = "slides/img/pisi-software-center.png"
title = "PISI Package Manager"
description = "Access thousands of packages with a single command. Install anything you need easily with `pisi install <package>`."
[[items]]
icon = "slides/img/community-101.png"
title = "Community Support"
description = "Our active community at forum.pisilinux.org is ready to answer your questions. Our wiki pages are always accessible."
[[items]]
icon = "slides/img/pisi_speed.png"
title = "Speed & Performance"
description = "Thanks to an optimized kernel and startup services, PisiLinux boots in seconds and uses minimal resources."
[[items]]
icon = "slides/img/securety.png"
title = "Security First"
description = "Keep your system secure with regular security updates, AppArmor profiles, and secure default settings."
[[items]]
icon = "slides/img/customization-dark.png"
title = "Customization"
description = "Tailor everything to your taste — from the desktop environment and theme colors to panel layout and keyboard shortcuts."
# ── Budgie ────────────────────────────────────────────────────────
[[items]]
desktop = "budgie"
icon = "slides/img/budgie.png"
title = "PisiLinux Budgie"
description = "A modern, clean, and elegant Budgie desktop. Built on the GNOME stack and user-friendly."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_applets.png"
title = "Budgie Desktop Applets"
description = "Easily add tools like weather, system monitor, and calendar via Budgie's panel applets."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_raven.png"
title = "Raven Notification Center"
description = "Access volume, media controls, notifications, and quick settings from the Raven panel."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_gtk_theme.png"
title = "GTK Theme Support"
description = "Budgie runs GTK themes and GNOME applications seamlessly, ensuring visual consistency."
# ── Cinnamon ──────────────────────────────────────────────────────
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon.png"
title = "PisiLinux Cinnamon"
description = "Cinnamon combines the traditional desktop experience with modern features, offering a powerful and flexible environment."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_panel.png"
title = "Cinnamon Panel"
description = "Customize your panel with applets, launchers, and system indicators for a classic yet powerful desktop."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_settings.png"
title = "Settings Manager"
description = "Fine-tune every aspect of your Cinnamon desktop through the comprehensive Settings Manager."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_desklets.png"
title = "Cinnamon Desklets"
description = "Keep notes, clocks, and system info always visible on your desktop with Cinnamon Desklets."
# ── GNOME ─────────────────────────────────────────────────────────
[[items]]
desktop = "gnome"
icon = "slides/img/gnome.png"
title = "PisiLinux GNOME"
description = "Reduce distractions and focus on your work with the clean and focused GNOME desktop environment."
[[items]]
desktop = "gnome"
icon = "slides/img/gnome_search.png"
title = "GNOME Search"
description = "Press the Super key and start typing. Instantly reach applications, files, and settings."
[[items]]
desktop = "gnome"
icon = "slides/img/gnome_extensions.png"
title = "GNOME Extensions"
description = "Shape your desktop to your needs with hundreds of extensions from extensions.gnome.org."
# ── KDE ───────────────────────────────────────────────────────────
[[items]]
desktop = "kde"
icon = "slides/img/kde.png"
title = "PisiLinux KDE Plasma"
description = "Meet the modern, customizable, and powerful KDE Plasma desktop. Everything is at your fingertips."
[[items]]
desktop = "kde"
icon = "slides/img/file_manager.png"
title = "Dolphin File Manager"
description = "Dolphin makes managing your files easy with tabbed browsing, dual-panel view, and powerful search."
[[items]]
desktop = "kde"
icon = "slides/img/kde_connect.png"
title = "KDE Connect"
description = "Connect your phone to your computer. Notifications, file transfer, and clipboard sharing — all with KDE Connect."
[[items]]
desktop = "kde"
icon = "slides/img/discover.png"
title = "Discover Software Center"
description = "Easily find, install, and update applications from the visual interface with Discover."
# ── LXQt ──────────────────────────────────────────────────────────
[[items]]
desktop = "lxqt"
icon = "slides/img/lxqt.png"
title = "PisiLinux LXQt"
description = "A lightweight, fast, and modern LXQt desktop environment. Low resource consumption, high performance."
[[items]]
desktop = "lxqt"
icon = "slides/img/lxqt_panel.png"
title = "LXQt Panel"
description = "The traditional panel layout puts the taskbar, application launcher, and system tray always within reach."
[[items]]
desktop = "lxqt"
icon = "slides/img/file_manager.png"
title = "PCManFM-Qt File Manager"
description = "Manage files effortlessly with the lightweight and fast PCManFM-Qt. Tabbed navigation and a customizable interface."
# ── LXDE ──────────────────────────────────────────────────────────
[[items]]
desktop = "lxde"
icon = "slides/img/lxde.png"
title = "PisiLinux LXDE"
description = "The legendary lightweight LXDE desktop. Known for extremely low resource usage and a reliable build."
[[items]]
desktop = "lxde"
icon = "slides/img/lxde_openbox.png"
title = "Openbox-Based"
description = "LXDE uses the Openbox window manager. Fine-tune it for a smooth and responsive experience."
[[items]]
desktop = "lxde"
icon = "slides/img/file_manager.png"
title = "PCManFM File Manager"
description = "PCManFM speeds up your file operations with its fast and lightweight build, supporting tree view and tabs."
# ── Lumina ────────────────────────────────────────────────────────
[[items]]
desktop = "lumina"
icon = "slides/img/lumina.png"
title = "PisiLinux Lumina"
description = "A lightweight and independent Lumina desktop. Qt-based, designed to run cleanly on BSD and Linux systems."
[[items]]
desktop = "lumina"
icon = "slides/img/lumina_applications.png"
title = "Application Support"
description = "Lumina works well with core utilities and many popular Qt apps, providing a practical everyday experience."
[[items]]
desktop = "lumina"
icon = "slides/img/lumina_theme.png"
title = "Theme Customization"
description = "Easily customize color schemes and interface styles with Lumina's flexible theme system."
[[items]]
desktop = "lumina"
icon = "slides/img/lumina_panel.png"
title = "Lumina Panel"
description = "A clean panel with a taskbar, application launcher, and system tray keeps your desktop organized."
# ── MATE ──────────────────────────────────────────────────────────
[[items]]
desktop = "mate"
icon = "slides/img/mate.png"
title = "PisiLinux MATE"
description = "MATE for those who love the traditional desktop layout. Developed with GTK+ 3 for a modern and smooth experience."
[[items]]
desktop = "mate"
icon = "slides/img/file_manager.png"
title = "Caja File Manager"
description = "Manage your files easily with MATE's powerful Caja. Dual panel, tabbed navigation, and rich features."
[[items]]
desktop = "mate"
icon = "slides/img/mate_settings.png"
title = "MATE Settings"
description = "Customize your MATE desktop with comprehensive settings for appearance, display, keyboard, and more."
[[items]]
desktop = "mate"
icon = "slides/img/mate_applications.png"
title = "Rich Application Suite"
description = "MATE comes with a rich set of classic applications — from text editors to image viewers, all well-integrated."
# ── XFCE ──────────────────────────────────────────────────────────
[[items]]
desktop = "xfce"
icon = "slides/img/xfce.png"
title = "PisiLinux XFCE"
description = "A lightweight, fast, and user-friendly XFCE desktop environment. Low resource consumption, high performance."
[[items]]
desktop = "xfce"
icon = "slides/img/xfce_panel.png"
title = "XFCE Panel"
description = "The traditional panel layout puts the taskbar, application menu, and system tray always within reach."
[[items]]
desktop = "xfce"
icon = "slides/img/file_manager.png"
title = "Thunar File Manager"
description = "Manage your files easily with the fast and lightweight Thunar. Tree view, tabbed navigation, and customizable features."
[[items]]
desktop = "xfce"
icon = "slides/img/xfce_settings.png"
title = "Whisker Menu"
description = "A modern alternative to the classic application menu. Find apps easily and organize your favorites with Whisker Menu."
-240
View File
@@ -1,240 +0,0 @@
# ╔══════════════════════════════════════════════════════════════════╗
# ║ Yali-RS — Slide Content (English) ║
# ╚══════════════════════════════════════════════════════════════════╝
# Slide transition duration (seconds)
interval_secs = 12
[[items]]
icon = "slides/img/pisilinux-logo.png"
title = "Welcome to Pisi GNU/Linux"
description = "Pisi GNU/Linux is a Linux distribution developed by the free software community, based on PiSi, Turkey's first native package management system. It focuses on easy installation, easy use, and meeting daily desktop requirements for end users."
[[items]]
icon = "slides/img/pisi-software-center.png"
title = "Native Software Components"
description = "Pisi GNU/Linux, equipped with native software components, stands out with original technologies such as YALI installation software, KAPTAN settings wizard, MÜDÜR for fast system startup, and ÇOMAR for hardware settings."
[[items]]
icon = "slides/img/community-102.png"
title = "Extensive Software Repository"
description = "Pisi GNU/Linux offers thousands of applications that users need through its extensive software repository and keeps them regularly updated."
[[items]]
icon = "slides/img/pisi_speed.png"
title = "Modern Desktop Experience"
description = "The free and open-source Pisi GNU/Linux comes with the KDE Plasma desktop environment by default, offering modern and comprehensive features. For users who prefer a desktop with lower system resource consumption, the lightweight, fast, and customizable XFCE desktop environment is also offered as an alternative."
[[items]]
icon = "slides/img/securety.png"
title = "Versatile Usage"
description = "Pisi GNU/Linux is a versatile operating system that can be easily used at home, office, and daily use. It provides a powerful infrastructure for music, movies, 3D modeling, graphic design, and professional applications, while also offering a comprehensive gaming platform for gaming enthusiasts."
[[items]]
icon = "slides/img/customization-dark.png"
title = "Customization"
description = "Customize everything from the desktop environment to theme colors, from panel layout to keyboard shortcuts according to your taste."
# ── XFCE ──────────────────────────────────────────────────────────
[[items]]
desktop = "xfce"
icon = "slides/img/xfce.png"
title = "PisiLinux XFCE"
description = "Lightweight, fast and user-friendly XFCE desktop environment. Low resource consumption, high performance."
[[items]]
desktop = "xfce"
icon = "slides/img/xfce_panel.png"
title = "XFCE Panel"
description = "With the traditional panel layout, taskbar, application menu and system tray are always at your fingertips."
[[items]]
desktop = "xfce"
icon = "slides/img/file_manager.png"
title = "Thunar File Manager"
description = "Easily perform file operations with the fast and lightweight Thunar. Offers tree view, tabbed navigation and customizable features."
[[items]]
desktop = "xfce"
icon = "slides/img/xfce_settings.png"
title = "Settings Center"
description = "A modern alternative to the classic application menu. Find applications easily and organize your favorites with Whisker Menu."
# ── KDE Plasma ────────────────────────────────────────────────────
[[items]]
desktop = "kde"
icon = "slides/img/kde.png"
title = "PisiLinux KDE Plasma"
description = "Meet the modern, customizable and powerful KDE Plasma desktop environment. Everything is at your fingertips."
[[items]]
desktop = "kde"
icon = "slides/img/file_manager.png"
title = "Dolphin File Manager"
description = "Dolphin makes managing your files easy with tabbed browsing, dual panel and powerful search features."
[[items]]
desktop = "kde"
icon = "slides/img/kde_connect.png"
title = "KDE Connect"
description = "Connect your phone to your computer. Notifications, file transfer and clipboard sharing are possible with KDE Connect."
[[items]]
desktop = "kde"
icon = "slides/img/discover.png"
title = "Discover Software Center"
description = "Easily find, install and update applications from the visual interface with Discover."
# ── GNOME ─────────────────────────────────────────────────────────
[[items]]
desktop = "gnome"
icon = "slides/img/gnome.png"
title = "PisiLinux GNOME"
description = "Reduce distractions and focus on your work with the clean and focused GNOME desktop environment."
[[items]]
desktop = "gnome"
icon = "slides/img/gnome_search.png"
title = "GNOME Search"
description = "Press the Super key and start typing. Instantly reach applications, files and settings."
[[items]]
desktop = "gnome"
icon = "slides/img/gnome_extensions.png"
title = "GNOME Extensions"
description = "Shape your desktop to your needs with hundreds of extensions from extensions.gnome.org."
# ── MATE ──────────────────────────────────────────────────────────
[[items]]
desktop = "mate"
icon = "slides/img/mate.png"
title = "PisiLinux MATE"
description = "MATE for those who love the traditional desktop layout. Developed with GTK+ 3 for a modern and smooth experience."
[[items]]
desktop = "mate"
icon = "slides/img/file_manager.png"
title = "Caja File Manager"
description = "Easily perform file operations with MATE's powerful Caja. Offers dual panel, tabbed navigation and rich features."
[[items]]
desktop = "mate"
icon = "slides/img/mate_settings.png"
title = "MATE Settings"
description = "Customize your MATE desktop with comprehensive settings for appearance, display, keyboard and more."
[[items]]
desktop = "mate"
icon = "slides/img/customization.png"
title = "Rich Application Suite"
description = "MATE offers a wide range of well-integrated classic applications from text editor to image viewer."
# ── Budgie ────────────────────────────────────────────────────────
[[items]]
desktop = "budgie"
icon = "slides/img/budgie.png"
title = "PisiLinux Budgie"
description = "Modern, clean and stylish Budgie desktop. Built on GNOME infrastructure, user-friendly."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_applets.png"
title = "Budgie Desktop Applets"
description = "Easily add tools like weather, system monitor and calendar via Budgie's panel applets."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_raven.png"
title = "Raven Notification Center"
description = "Access volume level, media controls, notifications and quick settings from the Raven panel."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_gtk_theme.png"
title = "GTK Theme Support"
description = "Budgie runs GTK themes and GNOME applications seamlessly, ensuring visual consistency."
# ── Cinnamon ──────────────────────────────────────────────────────
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon.png"
title = "PisiLinux Cinnamon"
description = "Cinnamon combines the traditional desktop experience with modern features, offering a powerful and flexible environment."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_panel.png"
title = "Cinnamon Panel"
description = "Customize your panel with applets, launchers and system indicators; experience a classic but powerful desktop."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_settings.png"
title = "Settings Manager"
description = "Fine-tune every aspect of your Cinnamon desktop through the comprehensive Settings Manager."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_desklets.png"
title = "Cinnamon Desklets"
description = "Keep notes, clocks and system info always visible on your desktop with Cinnamon Desklets."
# ── LXQt ──────────────────────────────────────────────────────────
[[items]]
desktop = "lxqt"
icon = "slides/img/lxqt.png"
title = "PisiLinux LXQt"
description = "Lightweight, fast and modern LXQt desktop environment. Low resource consumption, high performance."
[[items]]
desktop = "lxqt"
icon = "slides/img/file_manager.png"
title = "LXQt Panel"
description = "The traditional panel layout puts the taskbar, application launcher and system tray always within reach."
[[items]]
desktop = "lxqt"
icon = "slides/img/file_manager.png"
title = "PCManFM-Qt File Manager"
description = "Manage file operations easily with the lightweight and fast PCManFM-Qt. Offers tabbed navigation and customizable interface."
# ── LXDE ──────────────────────────────────────────────────────────
[[items]]
desktop = "lxde"
icon = "slides/img/lxde.png"
title = "PisiLinux LXDE"
description = "The legendary lightweight LXDE desktop. Known for very low resource consumption and reliable structure."
[[items]]
desktop = "lxde"
icon = "slides/img/lxde.png"
title = "Openbox-Based"
description = "LXDE uses the Openbox window manager. Offers a smooth experience with fine-tuning."
[[items]]
desktop = "lxde"
icon = "slides/img/file_manager.png"
title = "PCManFM File Manager"
description = "PCManFM facilitates your file operations with its fast and lightweight structure, supports tree view and tabs."
# ── Lumina ────────────────────────────────────────────────────────
[[items]]
desktop = "lumina"
icon = "slides/img/lumina.png"
title = "PisiLinux Lumina"
description = "Lightweight and independent Lumina desktop. Qt-based, minimal environment that works properly on BSD and Linux systems."
[[items]]
desktop = "lumina"
icon = "slides/img/customization.png"
title = "Theme Customization"
description = "Easily customize color schemes and interface style with Lumina's theme system."
-240
View File
@@ -1,240 +0,0 @@
# ╔══════════════════════════════════════════════════════════════════╗
# ║ Yali-RS — Slayt İçerikleri (Türkçe) ║
# ╚══════════════════════════════════════════════════════════════════╝
# Slayt geçiş süresi (saniye)
interval_secs = 12
[[items]]
icon = "slides/img/pisilinux-logo.png"
title = "Pisi GNU/Linux'a Hoş Geldiniz"
description = "Pisi GNU/Linux; Türkiye'nin ilk yerli paket yönetim sistemi olan, PiSi tabanlı, özgür yazılım topluluğu tarafından geliştirilen, kolay kurulum, kolay kullanım ve günlük masaüstü gereksinimlerini karşılamaya odaklanan, son kullanıcıya yönelik bir Linux dağıtımıdır."
[[items]]
icon = "slides/img/pisi-software-center.png"
title = "Yerli Yazılım Bileşenleri"
description = "Yerli yazılım bileşenleriyle donatılmış olan Pisi GNU/Linux; kurulum yazılımı YALI, ayar sihirbazı KAPTAN, sistemin hızlı açılmasını sağlayan MÜDÜR ve donanım ayarları için kullanılan ÇOMAR gibi özgün teknolojileriyle öne çıkan yerli bir GNU/Linux dağıtımıdır."
[[items]]
icon = "slides/img/community-102.png"
title = "Geniş Yazılım Deposu"
description = "Pisi GNU/Linux, geniş yazılım deposu sayesinde kullanıcıların ihtiyaç duyduğu binlerce uygulamayı sunar ve bunları düzenli olarak güncel tutar."
[[items]]
icon = "slides/img/pisi_speed.png"
title = "Modern Masaüstü Deneyimi"
description = "Özgür ve açık kaynaklı Pisi GNU/Linux, varsayılan olarak modern ve kapsamlı özellikler sunan KDE Plasma masaüstü ortamıyla gelir. Daha düşük sistem kaynakları tüketen bir masaüstü tercih eden kullanıcılar için ise hafif, hızlı ve özelleştirilebilir XFCE masaüstü ortamı da alternatif olarak sunulmaktadır."
[[items]]
icon = "slides/img/securety.png"
title = "Çok Yönlü Kullanım"
description = "Pisi GNU/Linux; evde, ofiste ve günlük kullanımda rahatlıkla kullanılabilen çok yönlü bir işletim sistemidir. Müzik, film, 3D modelleme, grafik tasarım ve profesyonel uygulamalar için güçlü bir altyapı sunarken, oyun tutkunları için de kapsamlı bir oyun platformu sağlar."
[[items]]
icon = "slides/img/customization-dark.png"
title = "Özelleştirme"
description = "Masaüstü ortamından tema renklerine, panel düzeninden klavye kısayollarına kadar her şeyi zevkinize göre ayarlayın."
# ── XFCE ──────────────────────────────────────────────────────────
[[items]]
desktop = "xfce"
icon = "slides/img/xfce.png"
title = "PisiLinux XFCE"
description = "Hafif, hızlı ve kullanıcı dostu XFCE masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir."
[[items]]
desktop = "xfce"
icon = "slides/img/xfce_panel.png"
title = "XFCE Panel"
description = "Geleneksel panel düzeni ile görev çubuğu, uygulama menüsü ve sistem tepsisi her zaman elinizin altında."
[[items]]
desktop = "xfce"
icon = "slides/img/file_manager.png"
title = "Thunar Dosya Yöneticisi"
description = "Hızlı ve hafif Thunar ile dosya işlemlerinizi kolayca yapın. Ağaç görünümü, sekmeli gezinme ve özelleştirilebilir özellikler sunar."
[[items]]
desktop = "xfce"
icon = "slides/img/xfce_settings.png"
title = "Ayarlar Merkezi"
description = "Klasik uygulama menüsüne modern bir alternatif. Whisker Menu ile uygulamaları kolayca bulun, favorilerinizi düzenleyin."
# ── KDE Plasma ────────────────────────────────────────────────────
[[items]]
desktop = "kde"
icon = "slides/img/kde.png"
title = "PisiLinux KDE Plasma"
description = "Modern, özelleştirilebilir ve güçlü KDE Plasma masaüstü ortamıyla tanışın. Her şey elinizin altında."
[[items]]
desktop = "kde"
icon = "slides/img/file_manager.png"
title = "Dolphin Dosya Yöneticisi"
description = "Sekmeli gezinme, ikili panel ve güçlü arama özelliğiyle Dolphin dosyalarınızı yönetmeyi kolaylaştırır."
[[items]]
desktop = "kde"
icon = "slides/img/kde_connect.png"
title = "KDE Bağlan"
description = "Telefonunuzu bilgisayarınıza bağlayın. Bildirimler, dosya aktarımı ve klipboard paylaşımı KDE Connect ile mümkün."
[[items]]
desktop = "kde"
icon = "slides/img/discover.png"
title = "Discover Yazılım Merkezi"
description = "Discover ile uygulamaları görsel arayüzden kolayca bulun, kurun ve güncelleyin."
# ── GNOME ─────────────────────────────────────────────────────────
[[items]]
desktop = "gnome"
icon = "slides/img/gnome.png"
title = "PisiLinux GNOME"
description = "Sade ve odaklı GNOME masaüstü ortamı ile dikkat dağıtıcı unsurları azaltın, işinize konsantre olun."
[[items]]
desktop = "gnome"
icon = "slides/img/gnome_search.png"
title = "GNOME Arama"
description = "Super tuşuna basıp yazmaya başlayın. Uygulama, dosya ve ayarlara anında ulaşın."
[[items]]
desktop = "gnome"
icon = "slides/img/gnome_extensions.png"
title = "GNOME Eklentileri"
description = "extensions.gnome.org adresinden yüzlerce eklentiyle masaüstünüzü ihtiyaçlarınıza göre şekillendirin."
# ── MATE ──────────────────────────────────────────────────────────
[[items]]
desktop = "mate"
icon = "slides/img/mate.png"
title = "PisiLinux MATE"
description = "Geleneksel masaüstü düzenini sevenler için MATE. GTK+ 3 ile geliştirilmiş, modern ve akıcı bir deneyim sunar."
[[items]]
desktop = "mate"
icon = "slides/img/file_manager.png"
title = "Caja Dosya Yöneticisi"
description = "MATE'in güçlü dosya yöneticisi Caja ile dosya işlemlerinizi kolayca yapın. Çift panel, sekmeli gezinme ve geniş özellikler sunar."
[[items]]
desktop = "mate"
icon = "slides/img/mate_settings.png"
title = "MATE Ayarları"
description = "Görünüm, ekran, klavye ve daha fazlası için kapsamlı ayarlarla MATE masaüstünüzü özelleştirin."
[[items]]
desktop = "mate"
icon = "slides/img/customization.png"
title = "Zengin Uygulama Paketi"
description = "MATE; metin editöründen görüntü görüntüleme yazılımına kadar geniş, iyi entegre edilmiş klasik uygulamalar sunar."
# ── Budgie ────────────────────────────────────────────────────────
[[items]]
desktop = "budgie"
icon = "slides/img/budgie.png"
title = "PisiLinux Budgie"
description = "Modern, sade ve şık Budgie masaüstü. GNOME altyapısı üzerine inşa edilmiş, kullanıcı dostudur."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_applets.png"
title = "Budgie Desktop Applets"
description = "Budgie'nin panel appletleri sayesinde hava durumu, sistem izleyici, takvim gibi araçları kolayca ekleyin."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_raven.png"
title = "Raven Bildirim Merkezi"
description = "Raven panelinden ses seviyesi, medya kontrolleri, bildirimler ve hızlı ayarlara erişin."
[[items]]
desktop = "budgie"
icon = "slides/img/budgie_gtk_theme.png"
title = "GTK Tema Desteği"
description = "Budgie, GTK temalarını ve GNOME uygulamalarını sorunsuz çalıştırır, görsel bütünlük sağlar."
# ── Cinnamon ──────────────────────────────────────────────────────
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon.png"
title = "PisiLinux Cinnamon"
description = "Geleneksel masaüstü deneyimini modern özelliklerle birleştiren Cinnamon, güçlü ve esnek bir ortam sunar."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_panel.png"
title = "Cinnamon Paneli"
description = "Panelinizi applet'ler, başlatıcılar ve sistem göstergeleriyle özelleştirin; klasik ama güçlü bir masaüstü deneyimi yaşayın."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_settings.png"
title = "Ayarlar Yöneticisi"
description = "Cinnamon'un kapsamlı Ayarlar Yöneticisi aracılığıyla masaüstünüzün her yönünü özelleştirin."
[[items]]
desktop = "cinnamon"
icon = "slides/img/cinnamon_desklets.png"
title = "Cinnamon Desklets"
description = "Masaüstü widget'ları ile notlar, saatler ve sistem bilgileri her zaman görünür kalsın."
# ── LXQt ──────────────────────────────────────────────────────────
[[items]]
desktop = "lxqt"
icon = "slides/img/lxqt.png"
title = "PisiLinux LXQt"
description = "Hafif, hızlı ve modern LXQt masaüstü ortamı. Kaynak tüketimi düşük, performansı yüksektir."
[[items]]
desktop = "lxqt"
icon = "slides/img/file_manager.png"
title = "LXQt Panel"
description = "Geleneksel panel düzeni ile görev çubuğu, uygulama başlatıcı ve sistem tepsisi her zaman elinizin altında."
[[items]]
desktop = "lxqt"
icon = "slides/img/file_manager.png"
title = "PCManFM-Qt Dosya Yöneticisi"
description = "Hafif ve hızlı PCManFM-Qt ile dosya işlemlerini kolayca yönetin. Sekmeli gezinme ve özelleştirilebilir arayüz sunar."
# ── LXDE ──────────────────────────────────────────────────────────
[[items]]
desktop = "lxde"
icon = "slides/img/lxde.png"
title = "PisiLinux LXDE"
description = "Efsanevi hafif masaüstü LXDE. Çok düşük kaynak tüketimi ve güvenilir yapısıyla tanınır."
[[items]]
desktop = "lxde"
icon = "slides/img/lxde.png"
title = "Openbox Tabanlı"
description = "LXDE, Openbox pencere yöneticisini kullanır. İnce ayarlar ile akıcı bir deneyim sunar."
[[items]]
desktop = "lxde"
icon = "slides/img/file_manager.png"
title = "PCManFM Dosya Yöneticisi"
description = "PCManFM, hızlı ve hafif yapısıyla dosya işlemlerinizi kolaylaştırır, ağaç görünümü ve sekme destekler."
# ── Lumina ────────────────────────────────────────────────────────
[[items]]
desktop = "lumina"
icon = "slides/img/lumina.png"
title = "PisiLinux Lumina"
description = "Hafif ve bağımsız Lumina masa üstü. Qt tabanlı, BSD ve Linux sistemlerde düzgün çalışan minimal bir ortam."
[[items]]
desktop = "lumina"
icon = "slides/img/customization.png"
title = "Tema Özelleştirme"
description = "Lumina'nın tema sistemiyle renk şemaları ve arayüz stilini kolayca özelleştirin."
+1 -1
View File
@@ -63,7 +63,7 @@ pub fn all_passed(results: &[CheckResult]) -> bool {
fn check_ram() -> CheckResult {
let mut sys = System::new();
sys.refresh_memory();
let mb = sys.total_memory() / 1_048_576;
let mb = sys.total_memory() / 1024 / 1024;
if mb < 1024 {
CheckResult::fail(
+2 -2
View File
@@ -137,12 +137,12 @@ pub fn apply_to_state(af: &AnswerFile, state: &mut crate::installer::GlobalState
if let Some(info) = disk_info {
let is_uefi = std::path::Path::new("/sys/firmware/efi").exists();
let efi_mb = if is_uefi { 512u64 } else { 0 };
let total_mb = info.size_bytes / 1_048_576;
let total_mb = info.size_bytes / 1024 / 1024;
// Swap: RAM * 1.0, min 512 MB, max 2048 MB, ayrıca diskin %10'unu geçmesin
let mut sys = sysinfo::System::new();
sys.refresh_memory();
let ram_mb = sys.total_memory() / 1_048_576;
let ram_mb = sys.total_memory() / 1024 / 1024;
let disk_10pct = total_mb / 10;
let swap_mb = ram_mb.clamp(512, 2048).min(disk_10pct);
+14 -67
View File
@@ -134,11 +134,11 @@ pub struct OemConfig {
// ── Varsayılan değerler ───────────────────────────────────────────────────────
fn default_title() -> String { "PisiLinux Install".to_string() }
fn default_title() -> String { "YALI Installation Tool".to_string() }
fn default_subtitle() -> String { "PiSi GNU/LINUX".to_string() }
fn default_yali_logo() -> String { "assets/icons/YALI_icon_DARK.png".to_string() }
fn default_pisi_logo_dark() -> String { "assets/icons/YALI_icon_DARK.png".to_string() }
fn default_pisi_logo_light() -> String { "assets/icons/YALI_icon_DARK.png".to_string() }
fn default_yali_logo() -> String { "assets/icons/YALI_icon_LIGHT.svg".to_string() }
fn default_pisi_logo_dark() -> String { "assets/pisi-logo-dark.png".to_string() }
fn default_pisi_logo_light() -> String { "assets/pisi-logo-light.png".to_string() }
fn default_interval() -> u64 { 6 }
fn default_primary() -> String { "#1a73e8".to_string() }
fn default_accent() -> String { "#34a853".to_string() }
@@ -161,87 +161,34 @@ impl BrandingConfig {
"/usr/share/yali/branding.toml",
"branding.toml",
];
let mut config = BrandingConfig::default();
for path in &paths {
if let Ok(content) = std::fs::read_to_string(path) {
match toml::from_str(&content) {
Ok(cfg) => { config = cfg; break; }
Ok(cfg) => return cfg,
Err(e) => eprintln!("[branding] {}: {}", path, e),
}
}
}
// slides/slides.toml varsa onu da yükle (öncelikli)
if let Some(content) = std::fs::read_to_string("slides/slides.toml").ok() {
if let Ok(slides) = toml::from_str::<SlidesConfig>(&content) {
if !slides.icons.is_empty() {
config.slides.icons = slides.icons;
}
if !slides.items.is_empty() {
config.slides.items = slides.items;
}
if slides.interval_secs > 0 {
config.slides.interval_secs = slides.interval_secs;
}
}
}
config
BrandingConfig::default()
}
/// Belirtilen dile ait `branding_{lang}.toml` dosyasını arar ve yükler.
/// Bulunamazsa varsayılan `branding.toml` dosyasına geri döner.
/// Yükleme sonrası `slides/slides_{lang}.toml` varsa slides verilerini da buradan alır.
pub fn load_for_lang(lang: &str) -> Self {
let mut config = {
let lang_paths = [
format!("/etc/yali/branding_{}.toml", lang),
format!("/usr/share/yali/branding_{}.toml", lang),
format!("branding_{}.toml", lang),
];
let mut found = None;
for path in &lang_paths {
if let Ok(content) = std::fs::read_to_string(path) {
match toml::from_str(&content) {
Ok(cfg) => { found = Some(cfg); break; }
Err(e) => eprintln!("[branding] {}: {}", path, e),
}
}
}
found.unwrap_or_else(Self::load)
};
// Harici slides dosyasını dene (slides/slides_{lang}.toml)
if let Some(slides) = Self::load_external_slides(lang) {
if !slides.icons.is_empty() {
config.slides.icons = slides.icons;
}
if !slides.items.is_empty() {
config.slides.items = slides.items;
}
if slides.interval_secs > 0 {
config.slides.interval_secs = slides.interval_secs;
}
}
config
}
/// `slides/slides_{lang}.toml` veya `slides/slides.toml` dosyasını yükler.
fn load_external_slides(lang: &str) -> Option<SlidesConfig> {
let paths = [
format!("slides/slides_{}.toml", lang),
"slides/slides.toml".to_string(),
let lang_paths = [
format!("/etc/yali/branding_{}.toml", lang),
format!("/usr/share/yali/branding_{}.toml", lang),
format!("branding_{}.toml", lang),
];
for path in &paths {
for path in &lang_paths {
if let Ok(content) = std::fs::read_to_string(path) {
match toml::from_str(&content) {
Ok(cfg) => return Some(cfg),
Err(e) => eprintln!("[slides] {}: {}", path, e),
Ok(cfg) => return cfg,
Err(e) => eprintln!("[branding] {}: {}", path, e),
}
}
}
None
Self::load()
}
/// İkon adına göre dosya yolunu döner.
+25 -15
View File
@@ -25,7 +25,7 @@ pub fn form_input_text(
ui.spacing_mut().button_padding = eframe::egui::vec2(10.0, 10.0);
egui::Frame::none()
.fill(egui::Color32::from_rgb(255, 255, 255))
.stroke(egui::Stroke::new(1.0_f32, egui::Color32::from_gray(200)))
.stroke(egui::Stroke::new(1.0, egui::Color32::from_gray(200)))
.rounding(4.0)
.inner_margin(eframe::egui::Margin::symmetric(2.0, 2.0))
.show(ui, |ui| {
@@ -45,22 +45,23 @@ pub fn form_input_password(
show_password: &mut bool,
) -> eframe::egui::Response {
let width_percent = 0.90;
let height = 18.0;
let height = 32.0;
let toplam_genislik = ui.available_width() * width_percent;
let buton_genislik = 18.0;
let buton_genislik = 32.0;
let alan_genislik = toplam_genislik - buton_genislik - 4.0;
let mut response: Option<eframe::egui::Response> = None;
ui.visuals_mut().override_text_color = Some(eframe::egui::Color32::from_rgb(30, 30, 30));
// Remixicon karakterlerini kullanıyoruz
let goz_ikonu = if *show_password { "\u{ECB5}" } else { "\u{ECB7}" };
// SVG içeriklerini string olarak tanımlıyoruz
let goz_acik_svg = r#"<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M2.036 12.322a1.012 1.012 0 0 1 0-.639C3.423 7.51 7.36 4.5 12 4.5c4.638 0 8.573 3.007 9.963 7.178.07.207.07.431 0 .639C20.577 16.49 16.64 19.5 12 19.5c-4.638 0-8.573-3.007-9.963-7.178Z" /><path stroke-linecap="round" stroke-linejoin="round" d="M15 12a3 3 0 1 1-6 0 3 3 0 0 1 6 0Z" /></svg>"#;
let goz_kapali_svg = r#"<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor"><path stroke-linecap="round" stroke-linejoin="round" d="M3.98 8.223A10.477 10.477 0 0 0 1.934 12C3.226 16.338 7.244 19.5 12 19.5c.993 0 1.953-.138 2.863-.395M6.228 6.228A10.451 10.451 0 0 1 12 4.5c4.756 0 8.773 3.162 10.065 7.498a10.522 10.522 0 0 1-4.293 5.774M6.228 6.228 3 3m3.228 3.228 3.65 3.65m7.894 7.894L21 21m-3.228-3.228-3.65-3.65m0 0a3 3 0 1 0-4.243-4.243m4.242 4.242L9.88 9.88" /></svg>"#;
eframe::egui::Frame::none()
.fill(eframe::egui::Color32::WHITE)
.stroke(eframe::egui::Stroke::new(1.0_f32, eframe::egui::Color32::from_rgb(200, 200, 200)))
.stroke(eframe::egui::Stroke::new(1.0, eframe::egui::Color32::from_rgb(200, 200, 200)))
.rounding(6.0)
.inner_margin(eframe::egui::Margin::symmetric(2.0, 2.0))
.show(ui, |ui| {
@@ -70,18 +71,27 @@ pub fn form_input_password(
eframe::egui::vec2(alan_genislik, height),
eframe::egui::TextEdit::singleline(text)
.password(!*show_password)
.frame(false)
.frame(false)
);
response = Some(r);
// 2. Remixicon İkonlu Buton
let btn = eframe::egui::Button::new(
eframe::egui::RichText::new(goz_ikonu)
.size(14.0)
.color(eframe::egui::Color32::from_rgb(120, 120, 120))
)
.frame(false)
.min_size(eframe::egui::vec2(buton_genislik, height));
// Duruma göre ilgili SVG string'ini seçip byte dizisine (&[u8]) çeviriyoruz
let (uri, svg_bytes) = if *show_password {
("bytes://goz_acik.svg", goz_acik_svg.as_bytes())
} else {
("bytes://goz_kapali.svg", goz_kapali_svg.as_bytes())
};
// Image::from_bytes kullanarak imajı güvenli bir şekilde yüklüyoruz
let svg_gosterici = eframe::egui::Image::from_bytes(uri, svg_bytes)
.tint(eframe::egui::Color32::from_rgb(120, 120, 120))
.max_width(18.0)
.max_height(18.0);
// 2. SVG İkonlu Buton
let btn = eframe::egui::Button::image(svg_gosterici)
.frame(false)
.min_size(eframe::egui::vec2(buton_genislik, height));
if ui.add(btn).on_hover_cursor(eframe::egui::CursorIcon::PointingHand).clicked() {
*show_password = !*show_password;
+21 -96
View File
@@ -113,7 +113,6 @@ impl std::fmt::Display for PartitionTableType {
/// Manuel bölümleme için desteklenen dosya sistemleri.
#[derive(Clone, Debug, PartialEq)]
#[allow(dead_code)]
pub enum FsType {
Ext4,
Ext3,
@@ -123,6 +122,7 @@ pub enum FsType {
Ntfs,
Swap,
Lvm, // LVM fiziksel bölüm (PV)
#[allow(dead_code)]
LvmLv, // LVM mantıksal bölüm (LV)
}
@@ -150,7 +150,6 @@ impl std::fmt::Display for FsType {
/// LVM Volume Group yapılandırması.
#[derive(Clone, Debug, Default)]
#[allow(dead_code)]
pub struct VolumeGroup {
pub name: String,
pub pv_devices: Vec<String>, // PV olarak kullanılacak bölüm aygıtları
@@ -168,86 +167,11 @@ pub struct LogicalVolume {
pub mountpoint: String,
}
/// Cihaz yolundan ait olduğu diskin yolunu çıkarır.
///
/// - `/dev/sda1` → `/dev/sda`
/// - `/dev/nvme0n1p2` → `/dev/nvme0n1`
/// - `/dev/mmcblk0p1` → `/dev/mmcblk0`
#[allow(dead_code)] // genel yardımcı; testlerde ve gelecekteki işlerde kullanılır
pub fn device_to_disk(device: &str) -> String {
let base = device.trim_start_matches("/dev/");
// NVMe/MMC/loop aygıtlarında bölüm ayracı "p" + rakamdır
let needs_p = base.contains("nvme") || base.contains("mmcblk") || base.contains("loop");
if let Some(pos) = base.rfind('p') {
if needs_p && base[pos + 1..].chars().all(|c| c.is_ascii_digit()) && !base[pos + 1..].is_empty() {
return format!("/dev/{}", &base[..pos]);
}
}
// SATA/virtio: sondaki tüm rakamları at
let trimmed = base.trim_end_matches(|c: char| c.is_ascii_digit());
format!("/dev/{}", trimmed)
}
/// Cihaz adından bölüm numarasını çıkarır ("/dev/sda3" → 3,
/// "/dev/nvme0n1p2" → 2). Sonda rakam yoksa None döner.
pub fn part_number(device: &str) -> Option<u32> {
let base = device.trim_start_matches("/dev/");
let digits: String = base.chars().rev().take_while(|c| c.is_ascii_digit()).collect();
digits.chars().rev().collect::<String>().parse().ok()
}
/// Bölüm tanımlarına parted'ın gerçek slot davranışını izleyerek
/// aygıt numarası atar.
///
/// Eski Yali modeliyle aynı mantık: fiziksel (silinmemiş) bölümler
/// kendi numaralarını korur; silinmek işaretlenenlerin slotları boşalır
/// ve yeni tanımlar en küçük boş slotu sırayla doldurur. Örn. sda2
/// silinip yeni bölüm eklenirse yeni bölüm sda2 olur; sda3 yerinde kalır.
pub fn assign_partition_numbers(parts: &mut [CustomPartition]) {
use std::collections::BTreeSet;
// Disk bazlı kullanılan slot kümeleri
let mut used: std::collections::HashMap<String, BTreeSet<u32>> = std::collections::HashMap::new();
for p in parts.iter() {
if p.delete { continue; } // silinecekler slot açar
if !p.orig_device.is_empty() { // fiziksel: kendi numarası
if let Some(n) = part_number(&p.orig_device) {
used.entry(p.disk.clone()).or_default().insert(n);
}
}
}
for p in parts.iter_mut() {
if p.delete {
// Silinecek bölüm: kurulumda yok olacak; adı olduğu gibi kalır,
// yeni slot tüketmez.
continue;
}
match part_number(&p.orig_device) {
Some(n) => { p.device = part_path(&p.disk, n); }
None => {
let set = used.entry(p.disk.clone()).or_default();
// en küçük boş slot
let mut n = 1u32;
while set.contains(&n) { n += 1; }
set.insert(n);
p.device = part_path(&p.disk, n);
}
}
}
}
/// Kullanıcının manuel olarak tanımladığı tek bir bölüm.
#[derive(Clone, Debug)]
pub struct CustomPartition {
/// Bölümün ait olduğu disk (ör. "/dev/sda").
/// Çoklu disk kurulumunda bölümler diske göre gruplanır.
pub disk: String,
/// Bölüm aygit adı; oluşturulacak (ör. "/dev/sda1")
pub device: String,
/// Taramada bulunan fiziksel cihaz adı (örn. "/dev/sda3").
/// Kullanıcı tarafından yeni eklenen tanımlarda boştur.
/// Silme işleminde satırın listede kalıp yeniden
/// tanımlanabilmesini sağlar.
pub orig_device: String,
/// Boyut MB cinsinden; 0 = "kalan alan"
pub size_mb: u64,
/// Dosya sistemi
@@ -260,12 +184,6 @@ pub struct CustomPartition {
pub luks_password: String,
/// LUKS harici adı (ör. "cryptroot")
pub luks_name: String,
/// false = biçimlendir, true = olduğu gibi bırak (mevcut bölümleri koru)
pub skip_format: bool,
/// true = kurulum sırasında bu bölüm diskten SİLİNECEK
/// (eski Yali'deki OperationDestroyDevice karşılığı).
/// Satır listede görünür kalır; istenirse işaret geri alınır.
pub delete: bool,
}
/// Kurulum boyunca toplanan tüm veriler.
@@ -323,12 +241,17 @@ pub struct GlobalState {
/// true olmadan kurulum ilerleyemez.
pub oem_password_changed: bool,
pub selected_package_groups: Vec<String>,
/// branding.toml [slides.icons] tablosundan yüklenen ikon → dosya URI eşlemesi.
/// Slideshow bu map üzerinden çalışma zamanında görsel yollarını çözümler.
pub branding_icons: std::collections::HashMap<String, String>,
/// Sidebar için koyu tema logo URI'si.
pub branding_yali_dark: String,
pub branding_pisi_dark: String,
/// Sidebar için açık tema logo URI'si.
pub branding_yali_light: String,
pub branding_pisi_light: String,
/// branding.toml dosyasından yüklenen slaytlar.
pub branding_slides: Vec<crate::branding::SlideItem>,
}
impl Default for GlobalState {
@@ -382,10 +305,12 @@ impl Default for GlobalState {
display_manager: "sddm".to_string(),
autologin: false,
desktop_environment: detect_desktop_environment(),
branding_icons: std::collections::HashMap::new(),
branding_pisi_dark: String::new(),
branding_pisi_light: String::new(),
branding_yali_dark: String::new(),
branding_yali_light: String::new(),
branding_slides: Vec::new(),
}
}
}
@@ -530,24 +455,24 @@ impl InstallerStep for WelcomeStep {
for check in &self.checks {
match check.status {
CheckStatus::Pass => {
ui.label(
egui::RichText::new("\u{EB7B}")
.color(theme::c_success())
.size(18.0) // SVG max_width/height yerine font boyutu
ui.add(
egui::Image::new(egui::include_image!("../assets/icons/check.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_success())
);
}
CheckStatus::Warn => {
ui.label(
egui::RichText::new("\u{EA21}")
.color(theme::c_warning())
.size(18.0)
);
ui.add(
egui::Image::new(egui::include_image!("../assets/icons/warning-icon.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_warning())
);
}
CheckStatus::Fail => {
ui.label(
egui::RichText::new("\u{F4C6}")
.color(theme::c_error())
.size(18.0)
ui.add(
egui::Image::new(egui::include_image!("../assets/icons/fail-icon.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_error())
);
}
}
-101
View File
@@ -1,101 +0,0 @@
use super::{Job, UiSender};
/// Otomatik oturum açma ayarını hedef sisteme uygular.
///
/// Ekran yöneticisi (sddm/lightdm/gdm) yapılandırması ISO'da hazır olduğu
/// için burada değiştirilmez; yalnızca kullanıcının seçtiği "otomatik oturum
/// açma" bilgisi mevcut konfigürasyona eklenir. Autologin kapalıysa hiçbir
/// şeye dokunulmaz (ISO varsayılanında otomatik giriş yoktur).
pub struct ConfigureAutologinJob {
pub mount: String,
pub display_manager: String,
pub autologin: bool,
pub username: String,
pub desktop_environment: String,
}
#[async_trait::async_trait]
impl Job for ConfigureAutologinJob {
fn name(&self) -> &str { "job_autologin" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
if !self.autologin {
ui.log("Otomatik oturum açma kapalı, değişiklik yapılmıyor.");
return Ok(());
}
ui.log(format!(
"Otomatik oturum açma uygulanıyor: {} ({})",
self.username, self.display_manager
));
let session = match self.desktop_environment.as_str() {
"plasma" => "plasmax11.desktop",
"xfce" => "xfce.desktop",
"gnome" => "gnome.desktop",
"cinnamon" => "cinnamon.desktop",
"budgie" => "budgie-desktop.desktop",
"lxde" => "LXDE.desktop",
"lxqt" => "lxqt.desktop",
"mate" => "mate.desktop",
"lumina" => "lumina-desktop.desktop",
_ => "plasmax11.desktop",
};
match self.display_manager.as_str() {
"sddm" => {
// ISO'nun /usr/lib/sddm/sddm.conf.d içeriğine dokunmadan
// ayrı bir drop-in dosyası ile autologin eklenir.
let dir = format!("{}/usr/lib/sddm/sddm.conf.d", self.mount);
tokio::fs::create_dir_all(&dir).await
.map_err(|e| format!("sddm.conf.d oluşturulamadı: {}", e))?;
let content = format!(
"[Autologin]\nUser={}\nSession={}\n",
self.username, session
);
tokio::fs::write(format!("{}/autologin.conf", dir), content)
.await
.map_err(|e| format!("autologin.conf yazılamadı: {}", e))?;
}
"lightdm" => {
let dir = format!("{}/etc/lightdm", self.mount);
let _ = tokio::fs::create_dir_all(&dir).await;
let conf = format!("{}/lightdm.conf", dir);
let mut content = tokio::fs::read_to_string(&conf)
.await
.unwrap_or_default();
if !content.contains("[Seat:*]") {
content.push_str("[Seat:*]\n");
}
if !content.contains("autologin-user=") {
content.push_str(&format!("autologin-user={}\n", self.username));
content.push_str(&format!("autologin-session={}\n", self.desktop_environment));
}
tokio::fs::write(&conf, content)
.await
.map_err(|e| format!("lightdm.conf yazılamadı: {}", e))?;
}
"gdm" => {
let dir = format!("{}/etc/gdm", self.mount);
let _ = tokio::fs::create_dir_all(&dir).await;
let conf = format!("{}/custom.conf", dir);
let content = format!(
"[daemon]\nAutomaticLoginEnable=true\nAutomaticLogin={}\nDefaultSession={}\n",
self.username, session
);
tokio::fs::write(&conf, content)
.await
.map_err(|e| format!("gdm/custom.conf yazılamadı: {}", e))?;
}
_ => {
ui.log(format!(
"⚠ Bilinmeyen ekran yöneticisi: {} — autologin uygulanmadı",
self.display_manager
));
}
}
ui.log("✓ Otomatik oturum açma yapılandırması tamamlandı");
Ok(())
}
}
-541
View File
@@ -1,541 +0,0 @@
use std::sync::atomic::Ordering;
use super::{blkid_uuid, DEMO_MODE, get_fs_type, is_mount_point, part_path, run_cmd, run_chroot, Job, UiSender};
/// initramfs yeniler (GRUB öncesi zorunlu).
/// PisiLinux `dracut` kullanıyorsa `dracut -f --regenerate-all`,
/// mkinitcpio kullanıyorsa `mkinitcpio -P`.
pub struct GenerateInitramfsJob {
pub mount: String,
pub encrypt_root: bool,
pub lvm_active: bool,
}
#[async_trait::async_trait]
impl Job for GenerateInitramfsJob {
fn name(&self) -> &str { "job_initramfs" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// LVM için initramfs'e lvm modülü/hook'u ekleyen konfigürasyon
if self.lvm_active && !self.encrypt_root {
ui.log("LVM algılandı: initramfs LVM ayarlanıyor…");
// dracut: add lvm module
let dracut_conf = format!("{}/etc/dracut.conf.d/99-lvm.conf", self.mount);
let _ = tokio::fs::create_dir_all(format!("{}/etc/dracut.conf.d", self.mount)).await;
let _ = tokio::fs::write(&dracut_conf, "add_dracutmodules+=\" lvm \"\n").await;
// mkinitcpio: add lvm2 hook
// Hem HOOKS="..." hem HOOKS=(... ) sözdizimi desteklenir.
let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount);
if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() {
if let Ok(content) = tokio::fs::read_to_string(&mkinitcpio_conf).await {
if content.contains("HOOKS=") && !content.contains("lvm2") {
let new_content = content
.replace("HOOKS=\"", "HOOKS=\"lvm2 ")
.replace("HOOKS=(", "HOOKS=(lvm2 ");
let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await;
}
}
}
}
// LUKS için initramfs'e cryptsetup ekleyen konfigürasyon
if self.encrypt_root {
ui.log("LUKS algılandı: initramfs cryptsetup ayarlanıyor…");
// dracut: add crypt module
let dracut_conf = format!("{}/etc/dracut.conf.d/99-luks.conf", self.mount);
tokio::fs::create_dir_all(
format!("{}/etc/dracut.conf.d", self.mount)
).await.map_err(|e| format!("dracut.conf.d oluşturulamadı: {}", e))?;
tokio::fs::write(&dracut_conf, "add_dracutmodules+=\" crypt lvm \"\n")
.await.map_err(|e| format!("dracut luks conf yazılamadı: {}", e))?;
// mkinitcpio: add encrypt hook
// Hem HOOKS="..." hem HOOKS=(... ) sözdizimi desteklenir.
let mkinitcpio_conf = format!("{}/etc/mkinitcpio.conf", self.mount);
if tokio::fs::metadata(&mkinitcpio_conf).await.is_ok() {
let content = tokio::fs::read_to_string(&mkinitcpio_conf)
.await.unwrap_or_default();
if content.contains("HOOKS=") && !content.contains("encrypt") {
let new_content = content
.replace("HOOKS=\"", "HOOKS=\"encrypt ")
.replace("HOOKS=(", "HOOKS=(encrypt ");
let _ = tokio::fs::write(&mkinitcpio_conf, new_content).await;
}
}
}
// PisiLinux /sbin/mkinitramfs, dracut, mkinitcpio sırasıyla dene
let cmds: &[&[&str]] = &[
&["mkinitramfs", "-f"],
&["dracut", "-f", "--regenerate-all"],
&["mkinitcpio", "-P"],
];
for cmd in cmds {
if run_chroot(&self.mount, cmd).await.is_ok() {
ui.log(format!("initramfs oluşturuldu ({})", cmd[0]));
return Ok(());
}
}
Err("initramfs oluşturulamadı: mkinitramfs, dracut, mkinitcpio denendi".to_string())
}
}
/// GRUB bootloader kurulumu (UEFI veya BIOS).
pub struct InstallGrubJob {
pub disk: String,
pub mount: String,
pub is_uefi: bool,
pub boot_device: String,
pub timeout: u32,
pub password: String,
pub kernel_options: String,
pub encrypt_root: bool,
}
#[async_trait::async_trait]
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) — grub-mkpasswd-pbkdf2 ile hash üretilir,
// düz metin asla diske yazılmaz.
if !self.password.is_empty() {
ui.log("GRUB şifresi PBKDF2 ile hashleniyor…");
// grub-mkpasswd-pbkdf2 şifreyi stdin'den iki kez okur (şifre + doğrulama)
use tokio::io::AsyncWriteExt;
let pw_input = format!("{}\n{}\n", self.password, self.password);
let mut child = tokio::process::Command::new("grub-mkpasswd-pbkdf2")
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::null())
.spawn()
.map_err(|e| format!("grub-mkpasswd-pbkdf2 başlatılamadı: {}", e))?;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(pw_input.as_bytes()).await
.map_err(|e| format!("grub-mkpasswd-pbkdf2 stdin hatası: {}", e))?;
}
let output = child.wait_with_output().await
.map_err(|e| format!("grub-mkpasswd-pbkdf2 bekleme hatası: {}", e))?;
// Çıktıdan "grub.pbkdf2.sha512...." token'ını ayrıştır
let stdout = String::from_utf8_lossy(&output.stdout);
let hash = stdout
.lines()
.find_map(|l| {
let pos = l.find("grub.pbkdf2")?;
Some(l[pos..].trim().to_string())
})
.ok_or("grub-mkpasswd-pbkdf2 çıktısında hash bulunamadı")?;
let custom_grub = format!(
"#!/bin/sh\nset superusers=\"root\"\npassword_pbkdf2 root {}\n",
hash
);
let pw_path = format!("{}/etc/grub.d/40_custom", self.mount);
tokio::fs::write(&pw_path, custom_grub)
.await
.map_err(|e| format!("GRUB şifre dosyası yazılamadı: {}", e))?;
run_cmd("chmod", &["+x", &pw_path]).await?;
ui.log("GRUB şifresi PBKDF2 hash olarak yazıldı.");
}
// GRUB timeout ayarı
ui.log(format!("GRUB timeout ayarlanıyor: {}s", self.timeout));
let default_path = format!("{}/etc/default/grub", self.mount);
let mut grub_default = tokio::fs::read_to_string(&default_path)
.await
.unwrap_or_default();
// timeout satırını bul/değiştir
let timeout_line = format!("GRUB_TIMEOUT={}", self.timeout);
let found_timeout = grub_default.lines()
.any(|l| l.trim_start().starts_with("GRUB_TIMEOUT="));
if found_timeout {
let mut lines: Vec<String> = grub_default.lines()
.map(|l| {
if l.trim_start().starts_with("GRUB_TIMEOUT=") {
timeout_line.clone()
} else {
l.to_string()
}
})
.collect();
lines.push(String::new());
grub_default = lines.join("\n");
} else {
grub_default.push_str(&format!("\n{}\n", timeout_line));
}
// kernel cmdline
let mut cmdline = self.kernel_options.clone();
if self.encrypt_root {
// Kök bölüm UUID'sini blkid'den al (LUKS başlık UUID'si)
let (root_num, _swap_num): (u32, u32) = if self.is_uefi { (3, 2) } else { (2, 1) };
let root_part = part_path(&self.disk, root_num);
let root_uuid = blkid_uuid(&root_part).await.unwrap_or_default();
let crypt_arg = format!("cryptdevice=UUID={}:cryptroot root=/dev/mapper/cryptroot", root_uuid);
if cmdline.is_empty() {
cmdline = crypt_arg;
} else {
cmdline = format!("{} {}", crypt_arg, cmdline);
}
}
if !cmdline.is_empty() {
let cmdline_line = format!("GRUB_CMDLINE_LINUX=\"{}\"", cmdline);
let found_cmdline = grub_default.lines()
.any(|l| l.trim_start().starts_with("GRUB_CMDLINE_LINUX="));
if found_cmdline {
let mut lines: Vec<String> = grub_default.lines()
.map(|l| {
if l.trim_start().starts_with("GRUB_CMDLINE_LINUX=") {
cmdline_line.clone()
} else {
l.to_string()
}
})
.collect();
lines.push(String::new());
grub_default = lines.join("\n");
} else {
grub_default.push_str(&format!("{}\n", cmdline_line));
}
}
tokio::fs::write(&default_path, grub_default)
.await
.map_err(|e| format!("/etc/default/grub yazılamadı: {}", e))?;
// Chroot içinde grub2-install çalıştır (UEFI veya BIOS)
let mut grub_args = vec!["grub2-install"];
let install_dev = if self.boot_device.is_empty() { self.disk.as_str() } else { self.boot_device.as_str() };
if self.is_uefi {
// /boot/efi mevcut değilse veya bağlı değilse bağla
let host_efi_dir = format!("{}/boot/efi", self.mount);
let efi_dir = std::path::Path::new(&host_efi_dir);
if !efi_dir.exists() || !is_mount_point(efi_dir) {
ui.log("/boot/efi bağlanmamış, ESP taranıyor…");
tokio::fs::create_dir_all(&host_efi_dir).await
.map_err(|e| format!("{}/boot/efi oluşturulamadı: {}", self.mount, e))?;
// Önyükleyici aygıtında ESP bölümü ara (önce 1, sonra 2)
for part_num in &[1u32, 2] {
let esp_candidate = part_path(install_dev, *part_num);
if let Ok(fs_type) = get_fs_type(&esp_candidate).await {
if fs_type == "vfat" {
ui.log(format!("{} ESP olarak /boot/efi'ye bağlanıyor…", esp_candidate));
let _ = run_cmd("mount", &[&esp_candidate, &host_efi_dir]).await;
break;
}
}
}
}
let efi_dir_arg = "--efi-directory=/boot/efi";
ui.log(format!("GRUB UEFI modu {} aygıtına kuruluyor (chroot içinde)…", install_dev));
grub_args.extend_from_slice(&[
"--target=x86_64-efi",
efi_dir_arg,
"--bootloader-id=PisiLinux",
"--recheck",
install_dev,
]);
} else {
ui.log(format!("GRUB BIOS modu {} aygıtına kuruluyor (chroot içinde)…", install_dev));
grub_args.extend_from_slice(&[
"--target=i386-pc",
install_dev,
"--recheck",
]);
}
run_chroot(&self.mount, &grub_args).await?;
// device.map: tüm diskleri kapsayan bir mapping oluştur
ui.log("GRUB device.map oluşturuluyor…");
let mut device_map = String::new();
if let Ok(entries) = std::fs::read_dir("/dev") {
let mut devices: Vec<String> = entries
.filter_map(|e| e.ok())
.filter(|e| {
let name = e.file_name();
let s = name.to_string_lossy();
let s = s.as_ref();
match s {
x if x.starts_with("sd") || x.starts_with("vd") => {
// sdX / vdX = disk; sdX1 / vdX1 = partition
x.len() <= 3 || !x[3..].chars().any(|c| c.is_ascii_digit())
}
x if x.starts_with("nvme") => {
// nvme0n1 = disk; nvme0n1p1 = partition
!x.contains('p')
}
x if x.starts_with("mmcblk") => {
// mmcblk0 = disk; mmcblk0p1 = partition
!x.contains('p')
}
_ => false,
}
})
.map(|e| format!("/dev/{}", e.file_name().to_string_lossy()))
.collect();
devices.sort();
for (i, dev) in devices.iter().enumerate() {
device_map.push_str(&format!("(hd{}) {}\n", i, dev));
}
}
if !device_map.is_empty() {
let dm_path = format!("{}/boot/grub2/device.map", self.mount);
let _ = tokio::fs::write(&dm_path, device_map).await;
}
// /etc/grub.d/30_uefi-firmware: EFI firmware menüsü + Windows EFI taraması
let uefi_script = r#"#!/bin/sh
set -e
# GRUB2 Helper Script - Pisi Linux / Custom ISO
prefix="/usr"
exec_prefix="/usr"
datarootdir="/usr/share"
export TEXTDOMAIN=grub
export TEXTDOMAINDIR="${datarootdir}/locale"
. "${datarootdir}/grub/grub-mkconfig_lib"
# 1. EFI Firmware (BIOS Setup) Menü Eklentisi
efi_vars_dir=/sys/firmware/efi/efivars
EFI_GLOBAL_VARIABLE=8be4df61-93ca-11d2-aa0d-00e098032b8c
OsIndications="$efi_vars_dir/OsIndicationsSupported-$EFI_GLOBAL_VARIABLE"
if [ -e "$OsIndications" ] && \
[ "$(( $(printf 0x%x \'"$(cat $OsIndications | cut -b1)") & 1 ))" = 1 ]; then
LABEL="System setup"
cat << EOF
if [ "\$grub_platform" = "efi" ]; then
fwsetup --is-supported
if [ "\$?" = 0 ]; then
menuentry '$LABEL' \$menuentry_id_option 'uefi-firmware' {
fwsetup
}
fi
fi
EOF
fi
# 2. Dinamik Windows EFI Taraması (Disk Bağımsız)
if [ -d /sys/firmware/efi ]; then
TEMP_MOUNT="/tmp/grub_win_detect"
WIN_UUID=""
# Sistemdeki vfat (EFI) bölümlerini al
EFI_PARTS=$(lsblk -list -no NAME,FSTYPE 2>/dev/null | awk '$2=="vfat" {print $1}')
for part in $EFI_PARTS; do
DEV="/dev/$part"
MNT=$(findmnt -n -o TARGET "$DEV" 2>/dev/null || true)
WAS_MOUNTED=1
if [ -z "$MNT" ]; then
WAS_MOUNTED=0
MNT="$TEMP_MOUNT"
mkdir -p "$MNT"
mount -o ro "$DEV" "$MNT" 2>/dev/null || continue
fi
if [ -f "$MNT/EFI/Microsoft/Boot/bootmgfw.efi" ]; then
WIN_UUID=$(blkid -s UUID -o value "$DEV" 2>/dev/null || true)
if [ "$WAS_MOUNTED" -eq 0 ]; then
umount "$MNT" 2>/dev/null || true
fi
break
fi
if [ "$WAS_MOUNTED" -eq 0 ]; then
umount "$MNT" 2>/dev/null || true
fi
done
rm -rf "$TEMP_MOUNT"
if [ -n "$WIN_UUID" ]; then
cat << EOF
menuentry "Windows Boot Manager (UEFI)" --class windows --class os {
insmod part_gpt
insmod fat
insmod search_fs_uuid
insmod chain
search --fs-uuid --set=root ${WIN_UUID}
chainloader /EFI/Microsoft/Boot/bootmgfw.efi
}
EOF
fi
fi
"#;
let uefi_script_path = format!("{}/etc/grub.d/30_uefi-firmware", self.mount);
if let Err(e) = tokio::fs::write(&uefi_script_path, uefi_script).await {
ui.log(format!("/etc/grub.d/30_uefi-firmware yazılamadı: {}", e));
} else {
ui.log("30_uefi-firmware güncellendi (EFI firmware + Windows taraması).");
}
ui.log("grub2-mkconfig çalıştırılıyor…");
// grub2-mkconfig'teki set -e'yi geçici olarak set +e yap;
// böylece grub2-probe hataları script'i durdurmaz, os-prober hatayı atlayıp
// sonraki aygıtlarla devam edebilir. stderr'deki probe uyarıları bastırılır.
let _ = run_chroot(&self.mount, &["sh", "-c",
"cp /usr/sbin/grub2-mkconfig /usr/sbin/grub2-mkconfig.bak 2>/dev/null; \
sed -i 's/^set -e/set +e/' /usr/sbin/grub2-mkconfig 2>/dev/null; \
GRUB_DISABLE_OS_PROBER=false grub2-mkconfig -o /boot/grub2/grub.cfg 2>/dev/null; \
cp /usr/sbin/grub2-mkconfig.bak /usr/sbin/grub2-mkconfig 2>/dev/null; \
rm -f /usr/sbin/grub2-mkconfig.bak 2>/dev/null",
]).await;
let grub_cfg_path = format!("{}/boot/grub2/grub.cfg", self.mount);
let grub_cfg_ok = std::path::Path::new(&grub_cfg_path).exists()
&& std::fs::metadata(&grub_cfg_path).map(|m| m.len() > 0).unwrap_or(false);
if grub_cfg_ok {
ui.log("GRUB yapılandırması başarıyla oluşturuldu.");
Ok(())
} else {
ui.log("grub2-mkconfig başarısız, minimal yapılandırma oluşturuluyor…");
generate_minimal_grub_cfg(ui, &self.mount, &self.disk, &self.boot_device, self.is_uefi, &self.kernel_options).await
}
}
}
/// grub2-mkconfig başarısız olursa elle minimal bir grub.cfg oluşturur.
/// Windows ESP tespit edilirse chainloader girişi de ekler.
async fn generate_minimal_grub_cfg(
ui: &UiSender, mount: &str, disk: &str, boot_device: &str, is_uefi: bool, kernel_options: &str,
) -> Result<(), String> {
let boot_dir = format!("{}/boot", mount);
let grub_dir = format!("{}/boot/grub2", mount);
tokio::fs::create_dir_all(&grub_dir).await
.map_err(|e| format!("{} oluşturulamadı: {}", grub_dir, e))?;
let (root_num, _swap_num) = if is_uefi { (3u32, 2u32) } else { (2u32, 1u32) };
let root_part = part_path(disk, root_num);
let root_uuid = blkid_uuid(&root_part).await
.map_err(|e| format!("Kök UUID okunamadı: {}", e))?;
let mut kernel_file: Option<String> = None;
let mut initrd_files: Vec<String> = Vec::new();
let mut ucode_files: Vec<String> = Vec::new();
if let Ok(mut entries) = tokio::fs::read_dir(&boot_dir).await {
while let Ok(Some(entry)) = entries.next_entry().await {
let name = entry.file_name().to_string_lossy().to_string();
if name.starts_with("kernel-") || name.starts_with("vmlinuz-") {
kernel_file = Some(name);
} else if name.starts_with("initramfs-") || name.starts_with("initrd-") {
initrd_files.push(name);
} else if name.starts_with("intel-ucode") || name.starts_with("amd-ucode") {
ucode_files.push(name);
}
}
}
// Demo modunda gerçek dosyalar kopyalanmadığı için /boot boştur;
// sentetik bir kernel adıyla akışın tamamı denenebilir.
let kernel = match kernel_file {
Some(k) => k,
None if DEMO_MODE.load(Ordering::Relaxed) => "kernel-demo".to_string(),
None => return Err(format!("{}/boot/ içinde kernel bulunamadı", mount)),
};
let kernel_ver = kernel.strip_prefix("kernel-")
.or_else(|| kernel.strip_prefix("vmlinuz-"))
.unwrap_or("");
let initrd = if kernel_ver.is_empty() {
initrd_files.first().cloned()
} else {
let matching: Vec<String> = initrd_files.iter()
.filter(|f| f.contains(kernel_ver))
.cloned()
.collect();
matching.first().cloned()
};
let mut cfg = String::new();
cfg.push_str("set default=0\n");
cfg.push_str("set timeout=5\n\n");
// PisiLinux girişi
if is_uefi {
cfg.push_str("menuentry \"PisiLinux\" {\n");
cfg.push_str(" insmod part_gpt\n insmod ext2\n");
cfg.push_str(&format!(" search --no-floppy --fs-uuid --set=root {}\n", root_uuid));
cfg.push_str(&format!(" linux /boot/{} root=UUID={} rw quiet", kernel, root_uuid));
} else {
cfg.push_str("menuentry \"PisiLinux\" {\n");
cfg.push_str(" insmod ext2\n set root=(hd0,msdos2)\n");
cfg.push_str(&format!(" linux /boot/{} root=UUID={} rw quiet", kernel, root_uuid));
}
if !kernel_options.is_empty() {
cfg.push_str(&format!(" {}", kernel_options));
}
cfg.push('\n');
if !ucode_files.is_empty() || !initrd_files.is_empty() {
let mut initrd_line = String::from(" initrd");
for ucode in &ucode_files {
initrd_line.push_str(&format!(" /boot/{}", ucode));
}
if let Some(ref i) = initrd {
initrd_line.push_str(&format!(" /boot/{}", i));
} else if !initrd_files.is_empty() {
for f in &initrd_files {
initrd_line.push_str(&format!(" /boot/{}", f));
}
}
cfg.push_str(&initrd_line);
cfg.push('\n');
}
cfg.push_str("}\n");
// Windows chainloader girişi (yalnız UEFI)
if is_uefi && !boot_device.is_empty() && boot_device != disk {
let mut win_uuid: Option<String> = None;
for part_num in &[1u32, 2] {
let esp_candidate = part_path(boot_device, *part_num);
if let Ok(fs_type) = get_fs_type(&esp_candidate).await {
if fs_type == "vfat" {
if let Ok(uuid) = blkid_uuid(&esp_candidate).await {
win_uuid = Some(uuid);
break;
}
}
}
}
if let Some(ref uuid) = win_uuid {
cfg.push_str(&format!("\nmenuentry \"Windows Boot Manager\" {{\n"));
cfg.push_str(" insmod part_gpt\n insmod fat\n");
cfg.push_str(&format!(" search --no-floppy --fs-uuid --set=root {}\n", uuid));
cfg.push_str(" chainloader /EFI/Microsoft/Boot/bootmgfw.efi\n");
cfg.push_str("}\n");
ui.log("Windows Boot Manager eklendi.");
}
}
tokio::fs::write(&format!("{}/boot/grub2/grub.cfg", mount), cfg).await
.map_err(|e| format!("grub.cfg yazılamadı: {}", e))?;
ui.log("Minimal GRUB yapılandırması oluşturuldu.");
Ok(())
}
-442
View File
@@ -1,442 +0,0 @@
use std::sync::atomic::Ordering;
use std::time::Instant;
use tokio::io::{AsyncBufReadExt, BufReader};
use tokio::process::Command;
use super::{run_cmd, DEMO_MODE, Job, UiSender};
/// Sistem dosyalarını kopyala (rsync).
pub struct CopyFilesJob {
/// Canlı sistemin kök dizini; ör. "/run/livecd/squashfs-root/"
pub source: String,
/// Hedef bağlama noktası; ör. "/mnt"
pub target: String,
}
#[async_trait::async_trait]
impl Job for CopyFilesJob {
fn name(&self) -> &str { "job_copy_files" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
ui.log(format!("[DEBUG] CopyFilesJob başladı"));
ui.log(format!("[DEBUG] Kaynak: '{}'", self.source));
ui.log(format!("[DEBUG] Hedef: '{}'", self.target));
// Kaynak dizini kontrol et
let source_path = std::path::Path::new(&self.source);
let source_exists = source_path.exists();
let source_readable = source_exists && source_path.is_dir();
ui.log(format!("[DEBUG] Kaynak mevcut: {}, dizin: {}", source_exists, source_readable));
if !source_exists {
ui.log(format!("[DEBUG] Kaynak dizin içeriği aranıyor..."));
// Kaynağın bir üst dizinini dene
let parent = source_path.parent();
if let Some(p) = parent {
if p.exists() {
if let Ok(entries) = std::fs::read_dir(p) {
for entry in entries.flatten().take(20) {
ui.log(format!("[DEBUG] /{:?}", entry.path()));
}
}
}
} else {
ui.log("[DEBUG] Kaynağın parent'ı yok (kök dizin olabilir)");
}
} else if source_readable {
// İlk 10 girdiyi listele
if let Ok(entries) = std::fs::read_dir(source_path) {
let mut count = 0;
for entry in entries.flatten().take(10) {
ui.log(format!("[DEBUG] {:?}", entry.path()));
count += 1;
}
ui.log(format!("[DEBUG] (ilk {} girdi listelendi)", count));
}
}
ui.log(format!("rsync {} → {}", self.source, self.target));
ui.copy_started();
let is_root_source = self.source.trim_end_matches('/') == "";
// ── rsync exclude'leri ──
// Aynı liste hem rsync'e hem de aşağıdaki bilgilendirme amaçlı find
// sayacına uygulanır. İlerleme artık rsync'in kendi bayt-tabanlı
// yüzdesinden (--info=progress2) alındığı için sayım farkları barla
// ilgili değildir; find yalnızca "N dosya kopyalanacak" logu içindir.
let mut excludes: Vec<String> = vec![
"/proc/**".to_string(),
"/sys/**".to_string(),
"/dev/**".to_string(),
"/run/**".to_string(),
"/tmp/**".to_string(),
"/var/tmp/**".to_string(),
"/var/cache/**".to_string(),
"/home/**".to_string(),
"/root/**".to_string(),
"/lost+found".to_string(),
"*.sqfs".to_string(),
];
// Kaynak canlı sistem köküyse ekstra exclude'ler
if is_root_source {
excludes.extend_from_slice(&[
"/bootmnt/**".to_string(),
"/ro_branch/**".to_string(),
"/mnt/**".to_string(),
"/media/**".to_string(),
"/var/log/journal/**".to_string(),
"/var/lib/pisi/cache/**".to_string(),
"/var/lib/pisi/archives/**".to_string(),
"/etc/adjtime".to_string(),
"/etc/mtab".to_string(),
"/etc/resolv.conf".to_string(),
"/etc/hostname".to_string(),
"/etc/machine-id".to_string(),
]);
}
// ── Önce find ile toplam dosya sayısını bul (yalnızca bilgilendirme) ──
let find_path = self.source.trim_end_matches('/');
let find_path = if find_path.is_empty() { "/" } else { find_path };
let _total_files = {
let mut cmd = Command::new("find");
cmd.arg(find_path);
if is_root_source {
cmd.arg("-xdev");
}
for ex in &excludes {
if let Some(dir) = ex.strip_suffix("/**") {
cmd.args(["-path", dir, "-prune", "-o"]);
} else if ex.starts_with('/') {
cmd.args(["-path", ex.as_str(), "-prune", "-o"]);
} else {
cmd.args(["-name", ex.as_str(), "-prune", "-o"]);
}
}
cmd.args(["-type", "f", "-print"]);
cmd.stdout(std::process::Stdio::piped());
cmd.stderr(std::process::Stdio::null());
match cmd.spawn() {
Ok(mut c) => {
let stdout = c.stdout.take()
.ok_or_else(|| "find stdout alınamadı".to_string())?;
let r = BufReader::new(stdout);
let mut lines = r.lines();
let mut n: u64 = 0;
while let Ok(Some(_)) = lines.next_line().await {
n += 1;
}
let _ = c.wait().await;
if n > 0 {
ui.log(format!("{} dosya kopyalanacak", n));
}
n
}
Err(_) => 0
}
};
let mut args: Vec<String> = vec![
"-aAXH".to_string(),
"--out-format=%n".to_string(),
// Bayt-tabanlı ilerleme (stdout'a \r ile karışır). rsync kendisi
// hesapladığı için %100'e ancak kopyalama gerçekten bitince ulaşır.
"--info=progress2".to_string(),
// Incremental recursion (varsayılan) dosya listesini aktarım
// sırasında büyütür; toplam bayt revize olunca yüzde geriye
// düşebilir (%98 → %65 gibi). Liste önden tam kurulunca yüzde
// monoton ilerler.
"--no-inc-recursive".to_string(),
];
for ex in &excludes {
args.push(format!("--exclude={}", ex));
}
if let (Ok(source_path), Ok(target_path)) = (
std::path::Path::new(&self.source).canonicalize(),
std::path::Path::new(&self.target).canonicalize(),
) {
if let Ok(relative_target) = target_path.strip_prefix(&source_path) {
if !relative_target.as_os_str().is_empty() {
// İçerik hariç tutulur ama dizinin kendisi boş olarak kalır
// (ör. /mnt). Çıplak desen kullanılırsa dizinin kendisi de
// budanır ve kurulu sistemde /mnt hiç oluşmaz.
args.push(format!("--exclude={}/**", relative_target.display()));
}
}
}
args.push(self.source.clone());
args.push(self.target.clone());
if DEMO_MODE.load(Ordering::Relaxed) {
ui.log("rsync /run/livecd/squashfs-root/ → /mnt (DEMO)");
let steps = 20;
for i in 0..steps {
let p = (i + 1) as f32 / steps as f32;
ui.current_file(format!("/usr/share/doc/{}/README.md", (i * 137) % 50));
ui.sub_progress(p);
tokio::time::sleep(std::time::Duration::from_millis(150)).await;
}
ui.copy_finished();
return Ok(());
}
let mut child = Command::new("rsync")
.args(&args)
.stdout(std::process::Stdio::piped())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|e| format!("rsync çalıştırılamadı: {}", e))?;
let stdout = child.stdout.take()
.ok_or_else(|| "rsync stdout alınamadı".to_string())?;
let stderr = child.stderr.take()
.ok_or_else(|| "rsync stderr alınamadı".to_string())?;
// Stderr'i arka planda oku (hata birikimi ve tampon taşmasını engelle).
// progress2 satırları stdout'a karıştığı için yalnızca hata metni toplanır.
let stderr_buf = std::sync::Arc::new(tokio::sync::Mutex::new(String::new()));
let stderr_buf_clone = stderr_buf.clone();
tokio::spawn(async move {
let r = BufReader::new(stderr);
let mut lines = r.lines();
while let Ok(Some(l)) = lines.next_line().await {
let mut b = stderr_buf_clone.lock().await;
b.push_str(&l);
b.push('\n');
}
});
// Stdout: --out-format=%n ile dosya adları, --info=progress2 ile
// bayt-tabanlı ilerleme satırları aynı akışa, \r ve \n ayraçlarıyla
// karışık olarak yazılır. Her parçayı içeriğine göre sınıflandırırız:
// "%" içerenler ilerleme (yüzde), diğerleri dosya adıdır.
let reader = BufReader::new(stdout);
let mut lines = reader.lines();
let mut file_count: u64 = 0;
// Son gönderilen yüzde — barın geriye düşmemesi için (--no-inc-recursive
// ile gerekmemesi gerekir ama yine de garanti).
let mut last_sub: f32 = 0.0;
// Son UI güncellemesi — çok sık güncelleme GPU (özellikle Intel UMA) baskısını azaltır
let mut last_update = Instant::now();
while let Some(line) = lines.next_line().await
.map_err(|e| format!("rsync çıktısı okunamadı: {}", e))?
{
for segment in line.split('\r') {
let trimmed = segment.trim();
if trimmed.is_empty() {
continue;
}
if trimmed.contains('%') {
if let Some(pct) = parse_progress_percent(trimmed) {
if pct >= last_sub {
last_sub = pct;
ui.sub_progress(pct);
}
}
continue;
}
file_count += 1;
let now = Instant::now();
if now.duration_since(last_update) >= std::time::Duration::from_millis(50)
|| file_count == 1
|| trimmed.contains("Packages") || trimmed.contains("package")
{
ui.current_file(trimmed.to_string());
last_update = now;
}
}
}
let status = child.wait().await
.map_err(|e| format!("rsync beklenemedi: {}", e))?;
if !status.success() {
let err = stderr_buf.lock().await.clone();
return Err(format!(
"rsync çıkış kodu: {:?}. Hata: {}",
status.code(),
err.trim()
));
}
ui.copy_finished();
Ok(())
}
}
/// /proc, /sys, /dev, /dev/pts dizinlerini chroot hedefine bind-mount yapar.
/// Standart `chroot` için gereklidir; kurulum başında çağrılmalıdır.
pub struct MountBindJob {
pub mount: String, // ör. "/mnt"
}
#[async_trait::async_trait]
impl Job for MountBindJob {
fn name(&self) -> &str { "job_mount_bind" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let mt = &self.mount;
// (kaynak, hedef) — kaynak sistem üzerindeki gerçek path
let bind_dirs = [
("/proc", format!("{}/proc", mt)),
("/sys", format!("{}/sys", mt)),
("/dev", format!("{}/dev", mt)),
("/dev/pts", format!("{}/dev/pts", mt)),
("/run", format!("{}/run", mt)), // COMAR/dbus için gerekli
];
for (src, target) in &bind_dirs {
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?;
}
// 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() {
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?;
}
Ok(())
}
}
/// Chroot ve disk bağlantılarını temizler (kurulum sonunda çağrılır).
///
/// Önce bind-mount'lar, ardından gerçek bölümler (/boot/efi ve kök)
/// ters sırada unmount edilir. FAT32 (ESP) backup boot sektörü yalnızca
/// umount anında güncellendiği için bölümler `-lf` ile hemen lazy
/// unmount edilmez; önce temiz `umount` denenir, sadece "busy" durumunda
/// `umount -lf` ile son çareye düşülür.
pub struct UnmountBindJob {
pub mount: String,
}
#[async_trait::async_trait]
impl Job for UnmountBindJob {
fn name(&self) -> &str { "job_umount_bind" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let mt = &self.mount;
// Ters sırada umount — en derin path önce
let subdirs = [
"sys/firmware/efi/efivars",
"dev/pts",
"dev",
"run",
"sys",
"proc",
"boot/efi",
];
let mut errors: Vec<String> = Vec::new();
// Bind-mount'lar + ESP (/boot/efi)
for subdir in &subdirs {
let target = format!("{}/{}", mt, subdir);
if !umount_clean(&target, ui).await {
let msg = format!("{} temiz unmount edilemedi", target);
errors.push(msg);
}
}
// Kök bölüm: chroot mount'unun kendisi
if !umount_clean(mt, ui).await {
let msg = format!("{} (kök) temiz unmount edilemedi", mt);
errors.push(msg);
}
if !errors.is_empty() {
return Err(format!(
"Mount temizleme hatası — bazı dosya sistemleri bağlı kalmış olabilir:\n{}",
errors.join("\n")
));
}
Ok(())
}
}
/// Tek bir mount noktasını önce temiz `umount` ile, başarısızsa `umount -lf`
/// (lazy) ile çözer. "not mounted" / "no such file" gibi beklenen durumlar
/// kritik sayılmaz; yalnızca komut çalıştırılamaması gerçek hatadır.
async fn umount_clean(target: &str, ui: &UiSender) -> bool {
ui.log(format!("umount {}", target));
match tokio::process::Command::new("umount")
.arg(target).status().await
{
Ok(status) if status.success() => true,
Ok(status) => {
ui.log(format!("⚠ umount temiz deneme başarısız (kod {}): {} → lazy deneniyor", status, target));
match tokio::process::Command::new("umount")
.args(["-lf", target]).status().await
{
Ok(s) if s.success() => true,
Ok(s) => {
// "not mounted" / "no such file" beklenen durumlar
ui.log(format!("⚠ umount uyarısı (kritik değil, kod {}): {}", s, target));
true
}
Err(e) => {
let msg = format!("umount komutu başlatılamadı ({}): {}", target, e);
ui.log(format!("✗ {}", msg));
false
}
}
}
Err(e) => {
let msg = format!("umount komutu başlatılamadı ({}): {}", target, e);
ui.log(format!("✗ {}", msg));
false
}
}
}
/// rsync `--info=progress2` satırından bayt-tabanlı yüzdeyi çıkarır.
/// Örnek satır:
/// " 1,234,567 45% 3.21MB/s 0:00:05 (xfr#1, to-chk=999/1000)"
/// Dosya adı bazlı tahminin aksine bu yüzde gerçek aktarılan veriyi yansıtır
/// ve %100'e ancak kopyalama tamamlanınca ulaşır.
fn parse_progress_percent(line: &str) -> Option<f32> {
let pct_pos = line.rfind('%')?;
let before = &line[..pct_pos];
let token = before
.rsplit(|c: char| c.is_whitespace())
.find(|t| !t.is_empty())?;
let digits: String = token.chars().filter(|c| c.is_ascii_digit()).collect();
let n: f32 = digits.parse().ok()?;
Some((n / 100.0).clamp(0.0, 1.0))
}
#[cfg(test)]
mod tests {
use super::parse_progress_percent;
#[test]
fn parses_progress2_percent() {
// Gerçek rsync --info=progress2 çıktı parçaları
assert_eq!(
parse_progress_percent(" 32.768 0% 0,00kB/s 0:00:00 "),
Some(0.0)
);
assert_eq!(
parse_progress_percent(" 20.971.520 99% 587,32MB/s 0:00:00 (xfr#1, to-chk=3/5)"),
Some(0.99)
);
assert_eq!(
parse_progress_percent(" 21.022.720 100% 571,93MB/s 0:00:00 (xfr#2, to-chk=0/5)"),
Some(1.0)
);
// Dosya adları yüzde içermez → None
assert_eq!(parse_progress_percent("big.bin"), None);
assert_eq!(parse_progress_percent("sub/"), None);
}
}
-116
View File
@@ -1,116 +0,0 @@
use super::{run_chroot, Job, UiSender};
/// COMAR servislerini ve tetikleyicilerini yapılandırır.
pub struct RunComarJob {
#[allow(dead_code)]
pub mount: String,
}
#[async_trait::async_trait]
impl Job for RunComarJob {
fn name(&self) -> &str { "job_comar" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// pisi configure-pending: paket post-install scriptlerini çalıştırır.
// Yapılandırılacak paket yoksa çıkış kodu sıfır dışı olabilir — fatal değil.
// pisi cp işlemi kurulum sonrası atlanıyor (kullanıcı isteği).
let _ = ui;
// ui.log("pisi paketleri yapılandırılıyor (COMAR)…");
// match run_chroot(&self.mount, &["pisi", "cp"]).await {
// Ok(_) => {
// ui.log("✓ pisi paketleri yapılandırıldı");
// }
// Err(e) => {
// // "no packages to configure" gibi durumlar hata değil
// if e.contains("no packages") || e.contains("Nothing to do") {
// ui.log(" pisi: yapılandırılacak paket yok, atlanıyor");
// } else {
// // Gerçek hata — uyar ama kurulumu durdurma
// ui.log(format!("pisi paketleri yapılandırma uyarısı: {}", e));
// ui.log("Bu uyarı kritik değilse kurulum devam edecek.");
// }
// }
// }
Ok(())
}
}
/// ldconfig: paylaşımlı kütüphane önbelleğini yeniden oluşturur.
pub struct RunLdconfigJob {
pub mount: String,
}
#[async_trait::async_trait]
impl Job for RunLdconfigJob {
fn name(&self) -> &str { "job_ldconfig" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
ui.log("ldconfig çalıştırılıyor…");
match run_chroot(&self.mount, &["ldconfig"]).await {
Ok(_) => ui.log("✓ ldconfig tamamlandı"),
Err(e) => ui.log(format!("⚠ ldconfig uyarısı: {}", e)),
}
Ok(())
}
}
/// update-environment: PisiLinux init/betiklerini yeniden yapılandırır.
pub struct UpdateEnvironmentJob {
pub mount: String,
}
#[async_trait::async_trait]
impl Job for UpdateEnvironmentJob {
fn name(&self) -> &str { "job_update_env" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
ui.log("update-environment çalıştırılıyor…");
match run_chroot(&self.mount, &["update-environment"]).await {
Ok(_) => ui.log("✓ update-environment tamamlandı"),
Err(e) => ui.log(format!("⚠ update-environment uyarısı: {}", e)),
}
Ok(())
}
}
/// Live ortam izlerini hedef sistemden temizler.
pub struct CleanupLiveJob {
pub mount: String,
}
#[async_trait::async_trait]
impl Job for CleanupLiveJob {
fn name(&self) -> &str { "job_cleanup_live" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let mt = &self.mount;
ui.log("Live ortam izleri temizleniyor…");
// run/livemedia
let _ = tokio::fs::remove_dir_all(format!("{}/run/livemedia", mt)).await;
// var/cache/pisi/packages
let _ = tokio::fs::remove_dir_all(format!("{}/var/cache/pisi/packages", mt)).await;
// YALI desktop dosyaları
//let _ = tokio::fs::remove_file(format!("{}/usr/share/applications/yali.desktop", mt)).await;
//let _ = tokio::fs::remove_file(format!("{}/usr/share/applications/yali-bin.desktop", mt)).await;
let _ = tokio::fs::remove_dir_all(format!("{}/bootmnt", mt)).await;
//pisi kullanıcısını sil (ev diziniyle birlikte), YALI paketlerini kaldır
let _ = run_chroot(&self.mount, &["userdel", "-r", "pisi"]).await;
let _ = run_chroot(&self.mount, &["pisi", "rm", "yali-rs"]).await;
// live paket deposunu kaldır
let _ = run_chroot(&self.mount, &["pisi", "rr", "live"]).await;
// depolarları yeniden ekle (stable2 + contrib)
ui.log("Depolar Ekleniyor…");
let _ = run_chroot(&self.mount, &["pisi", "ar", "stable2", "https://stable2.pisilinux.org/pisi-index.xml.xz"]).await;
let _ = run_chroot(&self.mount, &["pisi", "ar", "contrib", "https://contrib.pisilinux.org/pisi-index.xml.xz"]).await;
// depoları aktif et
ui.log("Depolar aktif ediliyor...");
let _ = run_chroot(&self.mount, &["pisi", "er", "contrib", "stable2"]).await;
// paket listelerini güncelle
ui.log("Depolar Güncelleniyor...");
let _ = run_chroot(&self.mount, &["pisi", "ur"]).await;
ui.log("✓ Live temizlik tamamlandı");
Ok(())
}
}
-99
View File
@@ -1,99 +0,0 @@
use super::{Job, UiSender};
/// Klavye düzenini /etc/vconsole.conf, /etc/conf.d/mudur ve X11 yapılandırmasına yazar.
pub struct ConfigureKeyboardJob {
pub mount: String,
pub layout: String, // ör. "tr"
pub variant: String, // ör. "f" veya ""
pub language: String, // ör. "tr", "de", "fr"
}
#[async_trait::async_trait]
impl Job for ConfigureKeyboardJob {
fn name(&self) -> &str { "job_keyboard" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// Konsol (TTY) keymap adı: X11 layout'undan farklı olabilir.
// Türkçe'de masaüstü "tr" çalışır ama vconsole keymap'i "trq" (Q) / "trf" (F) olmalı;
// "tr" değeri kbd'de hata verir.
let keymap = match (self.layout.as_str(), self.variant.as_str()) {
("tr", "") => "trq".to_string(),
("tr", "f") => "trf".to_string(),
_ => {
if self.variant.is_empty() {
self.layout.clone()
} else {
format!("{}-{}", self.layout, self.variant)
}
}
};
// /etc/vconsole.conf (systemd-style TTY)
let vconsole_path = format!("{}/etc/vconsole.conf", self.mount);
let vconsole = format!("KEYMAP={}\nFONT=ter-v16n\n", keymap);
ui.log(format!("Yazılıyor: {}", vconsole_path));
let _ = tokio::fs::write(&vconsole_path, vconsole).await;
// /etc/conf.d/mudur (PisiLinux native init)
let mudur_dir = format!("{}/etc/conf.d", self.mount);
let mudur_path = format!("{}/mudur", mudur_dir);
tokio::fs::create_dir_all(&mudur_dir).await
.map_err(|e| format!("{} oluşturulamadı: {}", mudur_dir, e))?;
let content = tokio::fs::read_to_string(&mudur_path).await.unwrap_or_default();
let mut lines: Vec<String> = content.lines().map(String::from).collect();
let mut found_keymap = false;
let mut found_language = false;
for line in lines.iter_mut() {
let trimmed = line.trim();
if trimmed.starts_with("keymap=") || trimmed.starts_with("#keymap=") {
*line = format!("keymap=\"{}\"", keymap);
found_keymap = true;
} else if trimmed.starts_with("language=") || trimmed.starts_with("#language=") {
*line = format!("language=\"{}\"", self.language);
found_language = true;
}
}
if !found_keymap {
lines.push(format!("keymap=\"{}\"", keymap));
}
if !found_language {
lines.push(format!("language=\"{}\"", self.language));
}
let result = lines.join("\n");
ui.log(format!("Yazılıyor: {}", mudur_path));
tokio::fs::write(&mudur_path, result).await
.map_err(|e| format!("{} yazılamadı: {}", mudur_path, e))?;
// /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);
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()
} else {
format!("\n\t\tOption \"XkbVariant\" \"{}\"", self.variant)
};
let x11_conf = format!(
"Section \"InputClass\"\n\
\tIdentifier \"system-keyboard\"\n\
\tMatchIsKeyboard \"on\"\n\
\tOption \"XkbLayout\" \"{layout}\"{options}\n\
EndSection\n",
layout = self.layout,
options = options_line,
);
ui.log(format!("Yazılıyor: {}", x11_path));
tokio::fs::write(&x11_path, x11_conf)
.await
.map_err(|e| format!("xorg keyboard conf yazılamadı: {}", e))?;
Ok(())
}
}
-171
View File
@@ -1,171 +0,0 @@
use super::{run_chroot, Job, UiSender};
/// /etc/locale.conf ve /etc/locale.gen'i yazar, sonra locale-gen çalıştırır.
pub struct ConfigureLocaleJob {
pub mount: String,
pub locale: String, // ör. "tr_TR.UTF-8"
}
#[async_trait::async_trait]
impl Job for ConfigureLocaleJob {
fn name(&self) -> &str { "job_locale" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// Locale değeri doğrulama: yalnızca alfanumerik, '.', '_', '-', '@'
// karakterlerine izin ver. Yol manipülasyonu ve injection önlenir.
let locale_valid = !self.locale.is_empty()
&& !self.locale.contains("..")
&& !self.locale.contains('/')
&& !self.locale.contains('\n')
&& self.locale.chars().all(|c| c.is_alphanumeric() || "._-@".contains(c));
if !locale_valid {
return Err(format!("Geçersiz locale değeri: '{}'", self.locale));
}
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!(
"LANG={locale}\nLC_ALL={locale}\n",
locale = self.locale
);
ui.log(format!("Yazılıyor: {}", conf_path));
let _ = tokio::fs::write(&conf_path, conf).await;
// /etc/env.d/03locale (PisiLinux native)
let pisi_locale_path = format!("{}/etc/env.d/03locale", self.mount);
let pisi_locale = format!(
"LANG={locale}\nLC_ALL={locale}\n",
locale = self.locale
);
ui.log(format!("Yazılıyor: {}", pisi_locale_path));
let _ = tokio::fs::write(&pisi_locale_path, pisi_locale).await;
// /etc/locale.gen — satırın başındaki # kaldırılır
let gen_path = format!("{}/etc/locale.gen", self.mount);
ui.log(format!("Güncelleniyor: {}", gen_path));
let content = tokio::fs::read_to_string(&gen_path).await.unwrap_or_default();
// Her zaman aktif olması gereken yedek yerel ayarlar
let fallback_locales = ["en_US.UTF-8", "C.UTF-8"];
let mut updated: Vec<String> = content
.lines()
.map(|line| {
let stripped = line.trim_start_matches('#').trim_start();
if stripped.starts_with(&self.locale)
|| fallback_locales.iter().any(|l| stripped.starts_with(l))
{
stripped.to_string() // yorumu kaldır
} else {
line.to_string()
}
})
.collect();
// Yedek yerel ayarlar dosyada yoksa sona ekle
for loc in fallback_locales {
let present = updated.iter().any(|line| {
line.trim_start_matches('#').trim_start().starts_with(loc)
});
if !present {
updated.push(format!("{} UTF-8", loc));
}
}
let content_to_write = updated.join("\n") + "\n";
tokio::fs::write(&gen_path, content_to_write)
.await
.map_err(|e| format!("/etc/locale.gen yazılamadı: {}", e))?;
// locale.gen çalıştır — kurulum sonrası locale desteği için zorunlu
ui.log("locale.gen çalıştırılıyor…");
run_chroot(&self.mount, &["locale-gen"]).await?;
Ok(())
}
}
/// Zaman dilimini ayarlar ve hwclock'u senkronize eder.
pub struct ConfigureTimezoneJob {
pub mount: String,
pub timezone: String, // ör. "Europe/Istanbul"
}
#[async_trait::async_trait]
impl Job for ConfigureTimezoneJob {
fn name(&self) -> &str { "job_timezone" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// Path traversal koruması: timezone değeri yalnızca alfanumerik,
// '/', '_', '-', '+' karakterlerine izin verir; ".." asla geçemez.
let tz = &self.timezone;
let tz_valid = !tz.is_empty()
&& !tz.contains("..")
&& tz.chars().all(|c| c.is_alphanumeric() || "/._-+".contains(c));
if !tz_valid {
return Err(format!("Geçersiz timezone değeri: '{}'", tz));
}
// Sembolik link: /etc/localtime → /usr/share/zoneinfo/<tz>
let link_target = format!("/usr/share/zoneinfo/{}", self.timezone);
let link_path = format!("{}/etc/localtime", self.mount);
ui.log(format!("ln -sf {} {}", link_target, link_path));
// Önce varsa eski linki sil
let _ = tokio::fs::remove_file(&link_path).await;
// tokio'da Unix symlink için spawn_blocking kullan
let lt = link_target.clone();
let lp = link_path.clone();
tokio::task::spawn_blocking(move || {
std::os::unix::fs::symlink(&lt, &lp)
}).await
.map_err(|e| format!("symlink spawn hatası: {}", e))?
.map_err(|e| format!("localtime symlink: {}", e))?;
// hwclock --systohc
ui.log("hwclock --systohc çalıştırılıyor…");
// Hata kritik değil, uyar ve devam et
match run_chroot(&self.mount, &["hwclock", "--systohc"]).await {
Ok(_) => ui.log("hwclock senkronize edildi."),
Err(e) => ui.log(format!("⚠ hwclock uyarısı (kritik değil): {}", e)),
}
Ok(())
}
}
/// NTP ve manuel tarih/saat yapılandırması.
pub struct ConfigureDateTimeJob {
pub mount: String,
pub use_ntp: bool,
pub year: i32,
pub month: u32,
pub day: u32,
pub hour: u32,
pub minute: u32,
}
#[async_trait::async_trait]
impl Job for ConfigureDateTimeJob {
fn name(&self) -> &str { "job_datetime" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
if self.use_ntp {
ui.log("NTP etkinleştiriliyor…");
// ntpd servisini başlat
let _ = run_chroot(&self.mount, &["service", "ntpd", "start"]).await;
} else {
ui.log("Manuel tarih/saat ayarlanıyor…");
let date_str = format!(
"{:04}-{:02}-{:02} {:02}:{:02}:00",
self.year, self.month, self.day, self.hour, self.minute
);
let _ = run_chroot(&self.mount, &["date", "-s", &date_str]).await;
let _ = run_chroot(&self.mount, &["hwclock", "--systohc"]).await;
}
Ok(())
}
}
+1994 -186
View File
File diff suppressed because it is too large Load Diff
-39
View File
@@ -1,39 +0,0 @@
use super::{run_chroot, Job, UiSender};
/// Ek paket gruplarını chroot içinde kurar.
pub struct InstallExtraPackagesJob {
pub mount: String,
pub package_groups: Vec<String>,
}
#[async_trait::async_trait]
impl Job for InstallExtraPackagesJob {
fn name(&self) -> &str { "job_netinstall" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
use crate::steps::netinstall::PACKAGE_GROUPS;
let mut packages: Vec<&str> = Vec::new();
for (key, _, pkgs) in PACKAGE_GROUPS {
if self.package_groups.contains(&key.to_string()) {
packages.extend_from_slice(pkgs);
}
}
if packages.is_empty() {
ui.log("Ek paket seçilmedi, atlanıyor.");
return Ok(());
}
let pkg_list = packages.join(" ");
ui.log(format!("Ek paketler kuruluyor: {}", pkg_list));
match run_chroot(&self.mount, &["pisi", "install", &pkg_list]).await {
Ok(_) => ui.log("✓ Ek paketler kuruldu"),
Err(e) => {
ui.log(format!("⚠ pisi install uyarısı: {}", e));
ui.log("⚠ Bu hata kritik değilse kurulum devam edecek.");
}
}
Ok(())
}
}
-39
View File
@@ -1,39 +0,0 @@
use super::{Job, UiSender};
/// /etc/hostname ve /etc/hosts dosyalarını yazar.
pub struct ConfigureHostnameJob {
pub mount: String,
pub hostname: String,
}
#[async_trait::async_trait]
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));
tokio::fs::write(&hostname_path, format!("{}\n", self.hostname))
.await
.map_err(|e| format!("/etc/hostname yazılamadı: {}", e))?;
// /etc/hosts
let hosts_path = format!("{}/etc/hosts", self.mount);
let hosts_content = format!(
"127.0.0.1\tlocalhost\n\
::1\t\tlocalhost\n\
127.0.1.1\t{hostname}\n",
hostname = self.hostname
);
ui.log(format!("Yazılıyor: {}", hosts_path));
tokio::fs::write(&hosts_path, hosts_content)
.await
.map_err(|e| format!("/etc/hosts yazılamadı: {}", e))?;
Ok(())
}
}
-992
View File
@@ -1,992 +0,0 @@
use std::sync::atomic::Ordering;
use tokio::process::Command;
use crate::installer::{CustomPartition, FsType, PartitionPlan, PartitionTableType};
use super::{blkid_uuid, part_path, run_cmd, DEMO_MODE, Job, UiSender};
// ─────────────────────────────────────────────
// YARDIMCI: DISK / BÖLÜMLEME KOMUTLARI
// ─────────────────────────────────────────────
/// `parted` komutlarını çalıştırmak için özel sarmalayıcı.
///
/// `parted -s` canlı sistemde kullanımda olan bir disk üzerinde bölüm
/// tablosunu kernel'e bildiremeyince exit 1 döndürür; ancak değişiklikler
/// diske **yazılmıştır** — kernel güncellemesi `sync_partition_table` ile
/// yapılır. Bu fonksiyon yalnızca gerçekten kritik hataları (disk bulunamadı
/// vb.) yakalar; "kernel haberdar edilemedi" uyarısını yok sayar.
async fn run_parted(args: &[&str]) -> Result<(), String> {
if DEMO_MODE.load(Ordering::Relaxed) {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
return Ok(());
}
let output = Command::new("parted")
.args(args)
.output()
.await
.map_err(|e| format!("parted çalıştırılamadı: {}", e))?;
if output.status.success() {
return Ok(());
}
// Parted, kernel güncelleyemediğinde exit 1 verir ama disk yazımı
// başarılıdır. Bu spesifik uyarıyı hata saymıyoruz.
let stderr = String::from_utf8_lossy(&output.stderr);
let stdout = String::from_utf8_lossy(&output.stdout);
let combined = format!("{}{}", stderr, stdout);
let is_kernel_notify_warning = combined.contains("kernel değişiklikten haberdar edilemedi")
|| combined.contains("kernel was not notified")
|| combined.contains("unable to inform the kernel")
|| combined.contains("still in use");
if is_kernel_notify_warning {
// Uyarıyı yok say; sync_partition_table kernel'i güncelleyecek
return Ok(());
}
// Gerçek bir hata
Err(combined.trim().to_string())
}
/// LUKS bölümünü güvenli şekilde formatlar.
///
/// Şifre, `sh -c` + string interpolasyon yerine doğrudan `stdin` pipe'ı
/// üzerinden iletilir; böylece shell injection saldırıları önlenir.
async fn run_luks_format(device: &str, password: &str) -> Result<(), String> {
if DEMO_MODE.load(Ordering::Relaxed) {
tokio::time::sleep(std::time::Duration::from_millis(1000)).await;
return Ok(());
}
use tokio::io::AsyncWriteExt;
let mut child = tokio::process::Command::new("cryptsetup")
.args([
"luksFormat", "-q", "--type", "luks2",
"--key-file", "-",
device,
])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|e| format!("cryptsetup luksFormat başlatılamadı: {}", e))?;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(password.as_bytes()).await
.map_err(|e| format!("cryptsetup stdin yazma hatası: {}", e))?;
}
let output = child.wait_with_output().await
.map_err(|e| format!("cryptsetup bekleme hatası: {}", e))?;
if output.status.success() {
Ok(())
} else {
Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
}
}
/// LUKS bölümünü güvenli şekilde açar (open).
///
/// Şifre, shell interpolasyon yerine `stdin` pipe'ı üzerinden iletilir.
async fn run_luks_open(device: &str, name: &str, password: &str) -> Result<(), String> {
if DEMO_MODE.load(Ordering::Relaxed) {
tokio::time::sleep(std::time::Duration::from_millis(500)).await;
return Ok(());
}
use tokio::io::AsyncWriteExt;
let mut child = tokio::process::Command::new("cryptsetup")
.args(["open", "--key-file", "-", device, name])
.stdin(std::process::Stdio::piped())
.stdout(std::process::Stdio::null())
.stderr(std::process::Stdio::piped())
.spawn()
.map_err(|e| format!("cryptsetup open başlatılamadı: {}", e))?;
if let Some(mut stdin) = child.stdin.take() {
stdin.write_all(password.as_bytes()).await
.map_err(|e| format!("cryptsetup open stdin yazma hatası: {}", e))?;
}
let output = child.wait_with_output().await
.map_err(|e| format!("cryptsetup open bekleme hatası: {}", e))?;
if output.status.success() {
Ok(())
} else {
Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
}
}
/// Bölümlendirme öncesi diski hazırlar:
/// mevcut bağlamaları ve swap'ı ayırır, ardından kernel'e yeni
/// bölüm tablosunu duyurarak aygıt düğümlerinin hazır olmasını bekler.
async fn prepare_disk(disk: &str, ui: &UiSender) {
if DEMO_MODE.load(Ordering::Relaxed) { return; }
// 1. Diskteki aktif swap'ları kapat
ui.log(format!("Swap ayrılıyor: {}…", disk));
let lsblk = tokio::process::Command::new("lsblk")
.args(["-o", "NAME,TYPE", "-l", "--noheadings", disk])
.output().await;
if let Ok(out) = lsblk {
let text = String::from_utf8_lossy(&out.stdout);
for line in text.lines() {
if line.contains("part") {
if let Some(name) = line.split_whitespace().next() {
let dev = format!("/dev/{}", name);
let _ = tokio::process::Command::new("swapoff")
.arg(&dev).status().await;
}
}
}
}
// 2. Disk bölümlerini umount et (hata görmezden gelinir)
ui.log(format!("Bağlantılar ayrılıyor: {}…", disk));
for n in 1..=32u32 {
let part = crate::installer::part_path(disk, n);
if !std::path::Path::new(&part).exists() { break; }
let _ = tokio::process::Command::new("umount")
.args(["-lf", &part]).status().await;
}
// 3. İlk senkronizasyon
let _ = tokio::process::Command::new("partprobe")
.arg(disk).status().await;
let _ = tokio::process::Command::new("udevadm")
.args(["settle", "--timeout=10"]).status().await;
}
/// Bölümlendirme sonrası kernel'e yeni tabloyu bildirir ve aygıt
/// düğümlerinin hazır olmasını bekler. `parted`'ın uyarı vermesi
/// normaldir; bu fonksiyon hata döndürmez.
async fn sync_partition_table(disk: &str) {
if DEMO_MODE.load(Ordering::Relaxed) { return; }
let _ = tokio::process::Command::new("partprobe")
.arg(disk).status().await;
let _ = tokio::process::Command::new("blockdev")
.args(["--rereadpt", disk]).status().await;
let _ = tokio::process::Command::new("udevadm")
.args(["settle", "--timeout=10"]).status().await;
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
}
// ─────────────────────────────────────────────
// PARTITION JOB (otomatik bölümleme)
// ─────────────────────────────────────────────
/// `parted` ve `mkfs` araçlarını kullanarak diski otomatik bölümlendirir.
/// Plan, PartitionStep'te hesaplanmış ve GlobalState'te saklanmıştır.
pub struct PartitionJob {
pub plan: PartitionPlan,
}
#[async_trait::async_trait]
impl Job for PartitionJob {
fn name(&self) -> &str { "job_partition" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let disk = &self.plan.disk;
let is_gpt = self.plan.table_type == PartitionTableType::Gpt;
// 0. Diski hazırla: bağlamaları ayır, kernel tablosunu temizle
prepare_disk(disk, ui).await;
// 1. Bölüm tablosu oluştur
let table_label = if is_gpt { "gpt" } else { "msdos" };
ui.log(format!("parted {} mktable {}", disk, table_label));
run_parted(&["-s", disk, "mktable", table_label]).await?;
let mut next_mb: u64 = 1; // İlk MiB'ı boot kayıtları için bırak
// 2. EFI bölümü (yalnızca GPT/UEFI)
if self.plan.efi_mb > 0 {
let end_mb = next_mb + self.plan.efi_mb;
ui.log(format!("EFI bölümü oluşturuluyor: {}–{} MiB", next_mb, end_mb));
run_parted(&[
"-s", disk, "mkpart", "fat32",
&format!("{}MiB", next_mb), &format!("{}MiB", end_mb),
]).await?;
run_parted(&["-s", disk, "set", "1", "esp", "on"]).await?;
next_mb = end_mb;
}
// 3. Swap bölümü
let swap_end = next_mb + self.plan.swap_mb;
let swap_num: u32 = if self.plan.efi_mb > 0 { 2 } else { 1 };
ui.log(format!("Swap bölümü oluşturuluyor: {}–{} MiB", next_mb, swap_end));
run_parted(&[
"-s", disk, "mkpart", "linux-swap",
&format!("{}MiB", next_mb), &format!("{}MiB", swap_end),
]).await?;
next_mb = swap_end;
// 4. Kök bölümü (kalan alan)
let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 };
ui.log(format!("Kök bölümü oluşturuluyor: {}MiB – disk sonu", next_mb));
run_parted(&[
"-s", disk, "mkpart", "ext4",
&format!("{}MB", next_mb), "100%",
]).await?;
// 4.5. Tüm mkpart bitti; kernel'e bildir ve aygıt düğümlerini bekle
sync_partition_table(disk).await;
// 5. Biçimlendirme
let efi_part = part_path(disk, 1);
let swap_part = part_path(disk, swap_num);
let root_part = part_path(disk, root_num);
if self.plan.efi_mb > 0 {
ui.log(format!("mkfs.fat -F32 {}", efi_part));
run_cmd("mkfs.fat", &["-F32", &efi_part]).await?;
}
ui.log(format!("mkswap {}", swap_part));
run_cmd("mkswap", &[&swap_part]).await?;
ui.log(format!("swapon {}", swap_part));
run_cmd("swapon", &[&swap_part]).await?;
// LUKS şifreleme (kök bölümü)
// Şifre, shell injection'ı önlemek için doğrudan stdin pipe üzerinden
// iletilir; `sh -c` + string interpolasyon kullanılmaz.
let root_device = if self.plan.encrypt_root && !self.plan.luks_password.is_empty() {
let luks_name = "cryptroot";
ui.log(format!("cryptsetup luksFormat {}…", root_part));
run_luks_format(&root_part, &self.plan.luks_password).await
.map_err(|e| format!("LUKS format hatası: {}", e))?;
ui.log(format!("cryptsetup open {} {}…", root_part, luks_name));
run_luks_open(&root_part, luks_name, &self.plan.luks_password).await
.map_err(|e| format!("LUKS open hatası: {}", e))?;
format!("/dev/mapper/{}", luks_name)
} else {
root_part.clone()
};
ui.log(format!("mkfs.ext4 -F {}", root_device));
run_cmd("mkfs.ext4", &["-F", &root_device]).await?;
Ok(())
}
}
/// Bölüm yolunu bağla.
pub struct MountPartitionsJob {
pub plan: PartitionPlan,
pub mount: String,
}
#[async_trait::async_trait]
impl Job for MountPartitionsJob {
fn name(&self) -> &str { "job_mount" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let disk = &self.plan.disk;
let mount = &self.mount;
let root_num: u32 = if self.plan.efi_mb > 0 { 3 } else { 2 };
let root_part = part_path(disk, root_num);
let root_device = if self.plan.encrypt_root {
"/dev/mapper/cryptroot".to_string()
} else {
root_part.clone()
};
ui.log(format!("mount {} {}", root_device, mount));
run_cmd("mount", &[&root_device, mount]).await?;
if self.plan.efi_mb > 0 {
let efi_dir = format!("{}/boot/efi", mount);
ui.log(format!("mkdir -p {}", efi_dir));
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?;
}
Ok(())
}
}
// ─────────────────────────────────────────────
// FSTAB JOB'LARI
// ─────────────────────────────────────────────
/// blkid ile UUID okuyup fstab'ı doğrudan yazan job.
/// (async closure Rust'ta trait object olarak kullanılamaz;
/// bu nedenle ayrı bir Job struct'ı gerekli.)
pub struct FstabFromBlkidJob {
pub plan: PartitionPlan,
pub mount: String,
}
#[async_trait::async_trait]
impl Job for FstabFromBlkidJob {
fn name(&self) -> &str { "job_fstab" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
ui.log("blkid ile UUID'ler okunuyor…");
let (root_uuid, efi_uuid, swap_uuid) = read_partition_uuids(&self.plan).await?;
ui.log(format!("Kök UUID: {}", root_uuid));
let job = GenerateFstabJob {
mount: self.mount.clone(),
root_uuid,
efi_uuid,
swap_uuid,
encrypt_root: self.plan.encrypt_root,
};
job.run(ui).await
}
}
/// fstab'ı doğrudan dosyaya yazar (genfstab yerine; ISO ortamında daha güvenilir).
pub struct GenerateFstabJob {
pub mount: String,
pub root_uuid: String, // blkid ile alınır
pub efi_uuid: Option<String>,
pub swap_uuid: String,
pub encrypt_root: bool,
}
#[async_trait::async_trait]
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));
let mut fstab = String::from(
"# /etc/fstab — Yali tarafından otomatik oluşturuldu\n\
# <device> <mountpoint> <fs> <options> <dump> <pass>\n\n"
);
// Kök bölümü
if self.encrypt_root {
fstab.push_str(
"/dev/mapper/cryptroot / ext4 defaults,noatime 0 1\n"
);
} else {
fstab.push_str(&format!(
"UUID={} / ext4 defaults,noatime 0 1\n",
self.root_uuid
));
}
// EFI bölümü
if let Some(ref efi) = self.efi_uuid {
fstab.push_str(&format!(
"UUID={} /boot/efi vfat umask=0077 0 2\n",
efi
));
}
// Swap
fstab.push_str(&format!(
"UUID={} none swap sw 0 0\n",
self.swap_uuid
));
tokio::fs::write(&fstab_path, fstab)
.await
.map_err(|e| format!("fstab yazılamadı: {}", e))?;
// crypttab (LUKS için)
if self.encrypt_root {
let crypttab_path = format!("{}/etc/crypttab", self.mount);
let crypttab = format!(
"cryptroot UUID={} none luks\n",
self.root_uuid
);
ui.log(format!("Yazılıyor: {}", crypttab_path));
tokio::fs::write(&crypttab_path, crypttab)
.await
.map_err(|e| format!("crypttab yazılamadı: {}", e))?;
}
Ok(())
}
}
/// Disk bölümlerinin UUID'lerini `blkid` ile okur.
async fn read_partition_uuids(
plan: &PartitionPlan,
) -> Result<(String, Option<String>, String), String> {
let (root_num, swap_num): (u32, u32) = if plan.efi_mb > 0 { (3, 2) } else { (2, 1) };
let root_part = part_path(&plan.disk, root_num);
let swap_part = part_path(&plan.disk, swap_num);
// Şifreli kök: blkid'den LUKS UUID'sini al (crypttab için)
let root_uuid = blkid_uuid(&root_part).await?;
let swap_uuid = blkid_uuid(&swap_part).await?;
let efi_uuid = if plan.efi_mb > 0 {
Some(blkid_uuid(&part_path(&plan.disk, 1)).await?)
} else {
None
};
Ok((root_uuid, efi_uuid, swap_uuid))
}
// ─────────────────────────────────────────────
// MANUEL BÖLÜMLEME İÇİN DESTEKLEYİCİ JOB'LAR
// ─────────────────────────────────────────────
/// Diskteki boş bölgeleri MB cinsinden hesaplar (sysfs tabanlı).
///
/// `exclude_nums`: silinecek işaretli bölümler — boşluk olarak
/// davranılır (kurulum sırasında önce silindikleri için).
/// Dönen değer: (başlangıç_mb, bitiş_mb) aralıkları; disk sonunda
/// GPT yedek başlığı için 1MB pay bırakılır.
fn disk_free_regions_mb(disk: &str, exclude_nums: &[u32]) -> Result<Vec<(u64, u64)>, String> {
let base = disk.trim_start_matches("/dev/");
const SECTOR: u64 = 512;
const MB: u64 = 1024 * 1024;
let disk_sectors: u64 = std::fs::read_to_string(format!("/sys/block/{}/size", base))
.map_err(|e| format!("{} boyutu okunamadı: {}", disk, e))?
.trim().parse().unwrap_or(0);
if disk_sectors == 0 {
return Err(format!("{} disk boyutu okunamadı", disk));
}
let total_mb = disk_sectors * SECTOR / MB;
let usable_total = total_mb.saturating_sub(1); // disk sonu güvenlik payı
// Dolu aralıklar
let mut occupied: Vec<(u64, u64)> = Vec::new();
if let Ok(entries) = std::fs::read_dir(format!("/sys/block/{}", base)) {
for entry in entries.flatten() {
let name = entry.file_name().to_string_lossy().to_string();
if !name.starts_with(base) || name == base { continue; }
let num = match crate::installer::part_number(&format!("/dev/{}", name)) {
Some(n) => n,
None => continue,
};
if exclude_nums.contains(&num) { continue; }
let start_sectors: u64 = std::fs::read_to_string(entry.path().join("start"))
.ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0);
let len_sectors: u64 = std::fs::read_to_string(entry.path().join("size"))
.ok().and_then(|s| s.trim().parse().ok()).unwrap_or(0);
if len_sectors == 0 { continue; }
let start_mb = (start_sectors * SECTOR + MB - 1) / MB;
let end_mb = ((start_sectors + len_sectors) * SECTOR) / MB;
if end_mb > start_mb {
occupied.push((start_mb, end_mb.min(usable_total)));
}
}
}
occupied.sort();
// Boşlukları çıkar (ilk 1MB boot alanı hariç)
const HEADROOM_MB: u64 = 1;
let mut regions: Vec<(u64, u64)> = Vec::new();
let mut cursor = HEADROOM_MB.min(usable_total);
for (s, e) in &occupied {
if *s > cursor { regions.push((cursor, *s)); }
cursor = cursor.max(*e);
}
if cursor < usable_total { regions.push((cursor, usable_total)); }
regions.retain(|(s, e)| e > s);
Ok(regions)
}
/// Manuel bölümleme ayarlarını granular (eski Yali Operation modeli)
/// yaklaşımla uygulayan Job.
///
/// Eski Yali'nin DeviceTree/Operation mimarisiyle aynı felsefe:
/// - Dokunulmayan disklere ve bölümlere HİÇ dokunulmaz (veriler korunur)
/// - Silinmek işaretli bölümler `parted rm` ile kaldırılır
/// (OperationDestroyDevice corresponds)
/// - Yeni tanımlar diskin BOŞ alanlarına yerleştirilir; tüm disk
/// sıfırlanmaz (OperationCreateDevice corresponds)
/// - skip_format=false olanlar biçimlendirilir (OperationCreateFormat)
pub struct CustomPartitionJob {
pub custom_partitions: Vec<CustomPartition>,
}
#[async_trait::async_trait]
impl Job for CustomPartitionJob {
fn name(&self) -> &str { "job_custom_partition" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
let is_uefi = std::path::Path::new("/sys/firmware/efi").exists();
// MBR'de "primary" zorunludur, GPT'de ise bu alan isim (label) alanıdır.
let part_type_or_name = if is_uefi { "" } else { "primary" };
// 0. İşlem yapılan diskleri tanım sırasına göre topla
let mut disks: Vec<String> = Vec::new();
for part in &self.custom_partitions {
if !disks.contains(&part.disk) {
disks.push(part.disk.clone());
}
}
for disk in &disks {
let parts: Vec<&CustomPartition> = self.custom_partitions.iter()
.filter(|p| p.disk == *disk).collect();
// Bu disk için planlanan işlemler - her iki durumda da silinmesi
// istenen bölümlerin numaralarını topla: (1) fiziksel bölümler
// (orig_device varsa) ve (2) kullanıcı tanımlı silinme işaretleri.
let destroys: Vec<u32> = parts.iter()
.filter(|p| p.delete)
.filter_map(|p| crate::installer::part_number(&p.orig_device))
.collect();
let mut creates: Vec<&CustomPartition> = parts.iter()
.filter(|p| !p.delete && p.orig_device.is_empty() && p.fstype != FsType::LvmLv)
.map(|p| *p)
.collect();
// Sabit boyutlular ÖNCE yerleşir; "kalan alan" (size 0)
// tanımları EN SON, geriye kalan boşluğa yerleştirilir.
// (Stable sort → aynı grupta tanım sırası korunur.)
creates.sort_by_key(|p| p.size_mb == 0);
let has_format = parts.iter().any(|p| !p.delete && !p.skip_format);
if destroys.is_empty() && creates.is_empty() && !has_format {
ui.log(format!("⏭️ {}: işlem yok, korunuyor", disk));
continue; // eski Yali gibi dokunulmayan diske girilmez
}
ui.log(format!("── Disk işleniyor: {} ──", disk));
// Bağlamaları ayır, swap kapat
prepare_disk(disk, ui).await;
// ── FAZ A: SİLMEK İŞARETLİ BÖLÜMLER (büyükten küçüğe numara) ──
let mut del_sorted = destroys.clone();
del_sorted.sort_unstable_by(|a, b| b.cmp(a));
for n in &del_sorted {
ui.log(format!("parted {} rm {} (bölüm siliniyor)", disk, n));
run_parted(&["-s", disk, "rm", &n.to_string()]).await?;
}
if !del_sorted.is_empty() {
sync_partition_table(disk).await;
}
// ── FAZ B: YENİ BÖLÜMLER (boş alana yerleştir) ──────────────
if !creates.is_empty() {
let mut regions = disk_free_regions_mb(disk, &destroys)?;
for part in &creates {
let fs_label = match part.fstype {
FsType::Fat32 => "fat32",
FsType::Swap => "linux-swap",
FsType::Ext4 => "ext4",
FsType::Ext3 => "ext3",
FsType::Btrfs => "btrfs",
FsType::Xfs => "xfs",
FsType::Ntfs => "ntfs",
// LVM PV için parted "ext4" ile işaretle, pvcreate sonra
FsType::Lvm => "ext4",
FsType::LvmLv => continue,
};
// Yerleşim: boyut belirtilmişse sığan İLK boş bölge,
// "kalan alan" ise en büyük bölgenin tamamı.
let need = part.size_mb;
let placement = if need == 0 {
let idx = regions.iter().enumerate()
.max_by_key(|(_, (s, e))| e.saturating_sub(*s))
.map(|(i, _)| i);
match idx {
Some(i) => {
let r = regions.remove(i);
Some(r)
}
None => None,
}
} else {
let mut found = None;
for (i, (s, e)) in regions.iter().enumerate() {
if e.saturating_sub(*s) >= need {
found = Some((i, (*s, s + need)));
break;
}
}
match found {
Some((i, seg)) => {
let (_, e) = regions[i];
let start = seg.0;
let new_start = start + need;
if e > new_start {
regions[i] = (new_start, e);
} else {
regions.remove(i);
}
Some(seg)
}
None => None,
}
};
let (start_mb, end_mb) = placement.ok_or_else(|| format!(
"{} üzerinde {} MB için yeterli boş alan yok", disk, need))?;
// DİKKAT: parted "MB" sonekini onalık (10^6) okur;
// hesaplarımız binary (2^20) olduğu için "MiB" zorunlu.
// Tamsayı MiB = 2048 sektörün katı → parted hizalama
// hatası vermez.
let start_str = format!("{}MiB", start_mb);
let end_str = format!("{}MiB", end_mb);
let slot = crate::installer::part_number(&part.device).unwrap_or(0);
ui.log(format!(
"{} üzerinde yeni bölüm oluşturuluyor: {} - {} ({})",
disk, start_str, end_str, fs_label
));
run_parted(&[
"-s", disk, "mkpart", part_type_or_name, fs_label,
&start_str, &end_str
]).await?;
if part.mountpoint == "/boot/efi" || part.fstype == FsType::Fat32 {
run_parted(&["-s", disk, "set", &slot.to_string(), "esp", "on"]).await?;
}
}
sync_partition_table(disk).await;
}
// ── FAZ C: BİÇİMLENDİRME aşağıdaki ortak döngüde ────────────
}
// Biçimlendir (LUKS ile şifreleme desteklenir) — tüm diskler.
// Silinmek işaretli bölümlere hiç dokunulmaz.
for part in &self.custom_partitions {
if part.delete { continue; }
let path = &part.device;
// LUKS şifreleme (kök veya isteğe bağlı bölümler)
// Şifre, shell injection'ı önlemek için doğrudan stdin pipe üzerinden
// iletilir; `sh -c` + string interpolasyon kullanılmaz.
let fs_device = if part.encrypt && !part.luks_password.is_empty() {
let luks_name = if part.luks_name.is_empty() {
format!("crypt_{}", part.mountpoint.replace("/", "_"))
} else {
part.luks_name.clone()
};
let luks_name = luks_name.trim_start_matches('_').to_string();
ui.log(format!("cryptsetup luksFormat {}…", path));
run_luks_format(path, &part.luks_password).await
.map_err(|e| format!("LUKS format hatası ({}): {}", path, e))?;
ui.log(format!("cryptsetup open {} {}…", path, luks_name));
run_luks_open(path, &luks_name, &part.luks_password).await
.map_err(|e| format!("LUKS open hatası ({}): {}", path, e))?;
format!("/dev/mapper/{}", luks_name)
} else {
path.clone()
};
if part.skip_format {
ui.log(format!("⏭️ {} korunuyor (biçimlendirme atlandı)", fs_device));
continue;
}
match part.fstype {
FsType::Fat32 => {
ui.log(format!("mkfs.fat -F32 {}", fs_device));
run_cmd("mkfs.fat", &["-F32", &fs_device]).await?;
}
FsType::Swap => {
ui.log(format!("mkswap {}", path));
run_cmd("mkswap", &[path]).await?;
ui.log(format!("swapon {}", path));
run_cmd("swapon", &[path]).await?;
}
FsType::Ext4 => {
ui.log(format!("mkfs.ext4 -F {}", fs_device));
run_cmd("mkfs.ext4", &["-F", &fs_device]).await?;
}
FsType::Ext3 => {
ui.log(format!("mkfs.ext3 -F {}", fs_device));
run_cmd("mkfs.ext3", &["-F", &fs_device]).await?;
}
FsType::Btrfs => {
ui.log(format!("mkfs.btrfs -f {}", fs_device));
run_cmd("mkfs.btrfs", &["-f", &fs_device]).await?;
}
FsType::Xfs => {
ui.log(format!("mkfs.xfs -f {}", fs_device));
run_cmd("mkfs.xfs", &["-f", &fs_device]).await?;
}
FsType::Ntfs => {
ui.log(format!("mkfs.ntfs -f {}", fs_device));
run_cmd("mkfs.ntfs", &["-f", &fs_device]).await?;
}
FsType::Lvm => {
ui.log(format!("pvcreate {}", path));
run_cmd("pvcreate", &[path]).await?;
}
// LV ayrı bir job'da oluşturulur
FsType::LvmLv => {}
}
}
Ok(())
}
}
/// LVM Volume Group ve Logical Volume'leri oluşturur.
pub struct SetupLvmJob {
pub volume_groups: Vec<crate::installer::VolumeGroup>,
pub logical_volumes: Vec<crate::installer::LogicalVolume>,
}
#[async_trait::async_trait]
impl Job for SetupLvmJob {
fn name(&self) -> &str { "job_setup_lvm" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
for vg in &self.volume_groups {
let pv_str = vg.pv_devices.join(" ");
ui.log(format!("vgcreate {} {}", vg.name, pv_str));
run_cmd("vgcreate", &[&vg.name, &pv_str]).await?;
}
for lv in &self.logical_volumes {
let size_str = if lv.size_mb == 0 {
"100%FREE".to_string()
} else {
format!("{}M", lv.size_mb)
};
let lv_path = format!("/dev/{}/{}", lv.vg_name, lv.name);
ui.log(format!("lvcreate -L {} -n {} {}", size_str, lv.name, lv.vg_name));
run_cmd("lvcreate", &["-L", &size_str, "-n", &lv.name, &lv.vg_name]).await?;
let fs_cmd = match lv.fstype {
crate::installer::FsType::Ext4 => "mkfs.ext4",
crate::installer::FsType::Ext3 => "mkfs.ext3",
crate::installer::FsType::Btrfs => "mkfs.btrfs",
crate::installer::FsType::Xfs => "mkfs.xfs",
_ => continue,
};
ui.log(format!("{} {}", fs_cmd, lv_path));
run_cmd(fs_cmd, &[&lv_path]).await?;
}
Ok(())
}
}
/// Manuel bölümleri ve mantıksal hacimleri kök dizinden başlayarak hiyerarşik olarak hedef yola bağlayan Job.
pub struct MountCustomPartitionsJob {
pub custom_partitions: Vec<CustomPartition>,
pub logical_volumes: Vec<crate::installer::LogicalVolume>,
pub mount: String,
}
impl MountCustomPartitionsJob {
/// Şifreli bölümler için mapper yolunu döndürür.
fn device_path(&self, part: &CustomPartition) -> String {
if part.encrypt {
let luks_name = if part.luks_name.is_empty() {
format!("crypt_{}", part.mountpoint.replace("/", "_").trim_start_matches('_'))
} else {
part.luks_name.clone()
};
format!("/dev/mapper/{}", luks_name)
} else {
part.device.clone()
}
}
}
#[async_trait::async_trait]
impl Job for MountCustomPartitionsJob {
fn name(&self) -> &str { "job_mount_custom" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// 1. Kök (/) bölümünü (fiziksel partition veya LVM LV) bul ve bağla
let root_device = if let Some(root_part) = self.custom_partitions.iter()
.find(|p| p.mountpoint == "/" && !p.delete) {
self.device_path(root_part)
} else if let Some(root_lv) = self.logical_volumes.iter().find(|lv| lv.mountpoint == "/") {
format!("/dev/{}/{}", root_lv.vg_name, root_lv.name)
} else {
return Err("Kök (/) bölümü manuel listede veya mantıksal bölümlerde bulunamadı.".to_string());
};
ui.log(format!("Kök dizin bağlanıyor: {} ➔ {}", root_device, self.mount));
run_cmd("mount", &[&root_device, &self.mount]).await?;
// 2. Diğer bölümleri ve mantıksal hacimleri topla, sırala ve bağla (derinliğe göre artan sırada)
struct MountItem {
device: String,
mountpoint: String,
}
let mut mount_items = Vec::new();
for part in &self.custom_partitions {
if part.mountpoint != "/" && part.fstype != FsType::Swap && !part.mountpoint.is_empty() && !part.delete {
mount_items.push(MountItem {
device: self.device_path(part),
mountpoint: part.mountpoint.clone(),
});
}
}
for lv in &self.logical_volumes {
if lv.mountpoint != "/" && lv.fstype != FsType::Swap && !lv.mountpoint.is_empty() {
mount_items.push(MountItem {
device: format!("/dev/{}/{}", lv.vg_name, lv.name),
mountpoint: lv.mountpoint.clone(),
});
}
}
mount_items.sort_by_key(|item| item.mountpoint.len());
for item in mount_items {
let target_path = format!("{}{}", self.mount, item.mountpoint);
ui.log(format!("Bağlama noktası dizini oluşturuluyor: {}", target_path));
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?;
}
Ok(())
}
}
/// Manuel bölümleme planına göre fstab dosyası üreten Job.
pub struct FstabFromCustomPartitionsJob {
pub custom_partitions: Vec<CustomPartition>,
pub logical_volumes: Vec<crate::installer::LogicalVolume>,
pub mount: String,
}
#[async_trait::async_trait]
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));
let mut fstab = String::from(
"# /etc/fstab — Yali tarafından otomatik (manuel mod) oluşturuldu\n\
# <device> <mountpoint> <fs> <options> <dump> <pass>\n\n"
);
let mut crypttab = String::from(
"# /etc/crypttab — Yali tarafından otomatik oluşturuldu\n"
);
for part in &self.custom_partitions {
if part.mountpoint.is_empty() || part.delete { continue; }
let is_encrypted = part.encrypt && !part.luks_password.is_empty();
let device_field = if is_encrypted {
let luks_uuid = blkid_uuid(&part.device).await?;
let luks_name = if part.luks_name.is_empty() {
format!("crypt_{}", part.mountpoint.replace("/", "_").trim_start_matches('_'))
} else {
part.luks_name.clone()
};
crypttab.push_str(&format!(
"{} UUID={} none luks\n",
luks_name, luks_uuid
));
format!("/dev/mapper/{:<30}", luks_name)
} else {
ui.log(format!("{} için UUID okunuyor…", part.device));
let uuid = blkid_uuid(&part.device).await?;
format!("UUID={:<36}", uuid)
};
let (mp, options, dump, pass) = match part.fstype {
FsType::Swap => ("none", "sw", "0", "0"),
FsType::Fat32 => ("/boot/efi", "umask=0077", "0", "2"),
FsType::Ext4 | FsType::Ext3 if part.mountpoint == "/" => ("/", "defaults,noatime", "0", "1"),
_ => {
let pass_val = if part.mountpoint == "/" { "1" } else { "2" };
(part.mountpoint.as_str(), "defaults,noatime", "0", pass_val)
}
};
let fs_name = match part.fstype {
FsType::Ext4 => "ext4",
FsType::Ext3 => "ext3",
FsType::Btrfs => "btrfs",
FsType::Xfs => "xfs",
FsType::Fat32 => "vfat",
FsType::Ntfs => "ntfs-3g",
FsType::Swap => "swap",
FsType::Lvm | FsType::LvmLv => "ext4",
};
fstab.push_str(&format!(
"{} {:<12} {:<6} {:<18} {} {}\n",
device_field, mp, fs_name, options, dump, pass
));
}
// LVM mantıksal bölümlerini fstab'a ekle
for lv in &self.logical_volumes {
if lv.mountpoint.is_empty() { continue; }
let device_field = format!("/dev/{}/{}", lv.vg_name, lv.name);
let (mp, options, dump, pass) = match lv.fstype {
FsType::Swap => ("none", "sw", "0", "0"),
FsType::Fat32 => ("/boot/efi", "umask=0077", "0", "2"),
FsType::Ext4 | FsType::Ext3 if lv.mountpoint == "/" => ("/", "defaults,noatime", "0", "1"),
_ => {
let pass_val = if lv.mountpoint == "/" { "1" } else { "2" };
(lv.mountpoint.as_str(), "defaults,noatime", "0", pass_val)
}
};
let fs_name = match lv.fstype {
FsType::Ext4 => "ext4",
FsType::Ext3 => "ext3",
FsType::Btrfs => "btrfs",
FsType::Xfs => "xfs",
FsType::Fat32 => "vfat",
FsType::Ntfs => "ntfs-3g",
FsType::Swap => "swap",
_ => "ext4",
};
fstab.push_str(&format!(
"{:<42} {:<12} {:<6} {:<18} {} {}\n",
device_field, mp, fs_name, options, dump, pass
));
}
tokio::fs::write(&fstab_path, fstab)
.await
.map_err(|e| format!("fstab yazılamadı: {}", e))?;
// crypttab (sadece şifreli bölümler için)
let crypttab_device_count = crypttab.lines().count().saturating_sub(1); // header satırını sayma
if crypttab_device_count > 0 {
let crypttab_path = format!("{}/etc/crypttab", self.mount);
ui.log(format!("Yazılıyor: {}", crypttab_path));
let _ = tokio::fs::write(&crypttab_path, crypttab).await;
}
Ok(())
}
}
-134
View File
@@ -1,134 +0,0 @@
use super::{run_chroot, run_cmd, Job, UiSender};
/// Kullanıcı hesabı oluşturur ve şifresini ayarlar.
pub struct CreateUserJob {
pub mount: String,
pub username: String,
pub password: String,
pub root_password: String,
}
#[async_trait::async_trait]
impl Job for CreateUserJob {
fn name(&self) -> &str { "job_user" }
async fn run(&self, ui: &UiSender) -> Result<(), String> {
// wheel grubunun varlığını garantile (PisiLinux'ta olmayabilir)
let group_file = format!("{}/etc/group", self.mount);
let group_content = tokio::fs::read_to_string(&group_file)
.await.unwrap_or_default();
if !group_content.lines().any(|l| l.starts_with("wheel:")) {
ui.log("wheel grubu oluşturuluyor…");
run_chroot(&self.mount, &["groupadd", "-r", "wheel"]).await
.unwrap_or_default(); // hata olsa bile devam et
}
// useradd: kullanıcıyı temel gruplarla oluştur
// PisiLinux'ta "storage" grubu yoksa useradd hata verir; -M flag ile grub yoksa atla
// Güvenli yöntem: önce her grubu tek tek ekle
let groups = ["wheel", "users", "pnp", "disk", "audio", "video", "power", "dialout", "lp", "lpadmin", "cdrom", "floppy"];
for grp in &groups {
if group_content.lines().any(|l| l.starts_with(&format!("{}:", grp))) {
// grup zaten var
} else {
// grup yoksa oluşturma (hata görmezden gel)
let _ = run_chroot(&self.mount, &["groupadd", grp]).await;
}
}
// Kullanıcıyı oluştur
let groups_str = groups.join(",");
ui.log(format!("useradd -m -G {} -s /bin/bash {}", groups_str, self.username));
run_chroot(&self.mount, &[
"useradd",
"-m",
"-G", &groups_str,
"-s", "/bin/bash",
&self.username,
]).await?;
// // chpasswd ile şifre ayarla (stdin üzerinden güvenli)
// ui.log(format!("Şifre ayarlanıyor: {}", self.username));
// let chpasswd_input = format!("{}:{}", self.username, self.password);
// set_password_via_chpasswd(&self.mount, &chpasswd_input).await?;
// // Root şifresi: belirlenmişse ayarla, yoksa kilitle
// if self.root_password.is_empty() {
// ui.log("Root hesabı kilitleniyor…");
// run_chroot(&self.mount, &["passwd", "-l", "root"]).await?;
// } else {
// ui.log("Root şifresi ayarlanıyor…");
// let root_input = format!("root:{}", self.root_password);
// set_password_via_chpasswd(&self.mount, &root_input).await?;
// }
// 2. chpasswd ile şifre ayarla (YENİ BASİTLEŞTİRİLMİŞ VE KESİN YÖNTEM)
ui.log(format!("Şifre ayarlanıyor: {}", self.username));
// sh -c kullanarak echo ile chpasswd'e şifreyi güvenli bir şekilde üflüyoruz
let user_cmd = format!("echo '{}:{}' | chpasswd -c SHA512", self.username, self.password);
run_chroot(&self.mount, &["sh", "-c", &user_cmd]).await?;
// 3. Root şifresi: belirlenmişse ayarla, yoksa kilitle
if self.root_password.is_empty() {
ui.log("Root hesabı kilitleniyor…");
run_chroot(&self.mount, &["passwd", "-l", "root"]).await?;
} else {
ui.log("Root şifresi ayarlanıyor…");
let root_cmd = format!("echo 'root:{}' | chpasswd -c SHA512", self.root_password);
run_chroot(&self.mount, &["sh", "-c", &root_cmd]).await?;
}
// sudoers: wheel grubu sudo yetkisi alsın
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
.map_err(|e| format!("sudoers yazılamadı: {}", e))?;
// Dosya izinleri: sudoers 440 olmalı
run_cmd("chmod", &["440", &sudoers_path]).await?;
Ok(())
}
}
// /// `chpasswd` komutuna stdin üzerinden kullanıcı:şifre gönderir.
// async fn set_password_via_chpasswd(mount: &str, input: &str) -> Result<(), String> {
// if DEMO_MODE.load(std::sync::atomic::Ordering::Relaxed) {
// tokio::time::sleep(std::time::Duration::from_millis(2500)).await;
// return Ok(());
// }
// use tokio::io::AsyncWriteExt;
// use tokio::process::Command;
// let mut child = Command::new("chroot")
// .args([mount, "chpasswd"])
// .stdin(std::process::Stdio::piped())
// .stdout(std::process::Stdio::null())
// .stderr(std::process::Stdio::piped())
// .spawn()
// .map_err(|e| format!("chpasswd başlatılamadı: {}", e))?;
// if let Some(mut stdin) = child.stdin.take() {
// let mut data = input.as_bytes().to_vec();
// if !data.ends_with(b"\n") {
// data.push(b'\n');
// }
// stdin.write_all(&data).await
// .map_err(|e| format!("chpasswd stdin yazma hatası: {}", e))?;
// }
// let output = child.wait_with_output().await
// .map_err(|e| format!("chpasswd bekleme hatası: {}", e))?;
// if output.status.success() {
// Ok(())
// } else {
// Err(String::from_utf8_lossy(&output.stderr).trim().to_string())
// }
// }
-9
View File
@@ -1,9 +0,0 @@
pub mod installer;
pub mod steps;
pub mod jobs;
pub mod ui;
pub mod autoinstall;
pub mod branding;
pub mod funct;
rust_i18n::i18n!("locales");
+18 -27
View File
@@ -34,24 +34,11 @@ fn main() -> eframe::Result<()> {
/// Grafik arayüzü başlatır (normal mod).
fn run_gui(demo_mode: bool) -> eframe::Result<()> {
let icon_data = {
let img = image::load_from_memory(include_bytes!("../assets/icons/YALI_icon_DARK.png"))
.expect("YALI_icon_DARK.png yüklenemedi")
.into_rgba8();
let (w, h) = img.dimensions();
egui::IconData {
width: w,
height: h,
rgba: img.into_raw(),
}
};
let options = eframe::NativeOptions {
viewport: egui::ViewportBuilder::default()
.with_inner_size([1200.0, 900.0])
.with_min_inner_size([1080.0, 720.0])
.with_title(t!("installer"))
.with_icon(icon_data),
.with_title(t!("installer")),
..Default::default()
};
@@ -147,7 +134,6 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> {
let cli_ctx = eframe::egui::Context::default();
let ui_sender = UiSender::new(tx, cli_ctx);
let language_c = if locale.starts_with("tr") { "tr".to_string() } else { "en".to_string() };
let plan_c = plan.clone();
let locale_c = locale.clone();
let timezone_c = timezone.clone();
@@ -166,7 +152,6 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> {
jobs::JobQueueConfig {
mount: mount_c,
source: source_c,
language: language_c,
locale: locale_c,
timezone: timezone_c,
username: username_c,
@@ -203,15 +188,8 @@ fn run_auto_install(path: &str, demo_mode: bool) -> eframe::Result<()> {
print!("\r {}: [{:50}] {:.0}%", t!("progress"), "=".repeat((p * 50.0) as usize), p * 100.0);
let _ = std::io::Write::flush(&mut std::io::stdout());
}
Ok(InstallMessage::SubProgress(p)) => {
print!("\r {}: [{:50}] {:.0}% ", t!("progress"), "=".repeat((p * 50.0) as usize), p * 100.0);
let _ = std::io::Write::flush(&mut std::io::stdout());
}
Ok(InstallMessage::CopyStarted) | Ok(InstallMessage::CopyFinished) => {}
Ok(InstallMessage::CurrentFile(f)) => {
print!("\r {}: {} ", t!("install_current_file"), f);
let _ = std::io::Write::flush(&mut std::io::stdout());
}
Ok(InstallMessage::SubProgress(_)) => {}
Ok(InstallMessage::CurrentFile(_)) => {}
Ok(InstallMessage::Done) => {
println!("\n\n{}: {}", t!("installation_done"), t!("installation_done_description"));
break;
@@ -286,6 +264,11 @@ impl Default for YaliApp {
app.state.branding_yali_light = branding.yali_logo_uri();
app.state.branding_pisi_dark = branding.pisi_logo_dark_uri();
app.state.branding_pisi_light = branding.pisi_logo_light_uri();
app.state.branding_icons = branding.slides.icons
.iter()
.map(|(k, v)| (k.clone(), branding::path_to_file_uri(v)))
.collect();
app.state.branding_slides = branding.slides.items.clone();
app.steps[0].on_enter(&mut app.state);
app.last_step = 0;
@@ -306,6 +289,11 @@ impl eframe::App for YaliApp {
self.state.branding_yali_light = branding.yali_logo_uri();
self.state.branding_pisi_dark = branding.pisi_logo_dark_uri();
self.state.branding_pisi_light = branding.pisi_logo_light_uri();
self.state.branding_icons = branding.slides.icons
.iter()
.map(|(k, v)| (k.clone(), branding::path_to_file_uri(v)))
.collect();
self.state.branding_slides = branding.slides.items.clone();
// Pencere başlığını dinamik olarak güncelle
ctx.send_viewport_cmd(egui::ViewportCommand::Title(self.branding.general.title.clone()));
@@ -322,6 +310,10 @@ impl eframe::App for YaliApp {
egui::ScrollArea::vertical().show(ui, |ui| {
rescue.show(ui, &mut self.state);
});
ui.add_space(12.0);
if ui.button(t!("rescue_return")).clicked() {
self.state.rescue_mode = false;
}
});
}
return;
@@ -440,7 +432,7 @@ impl eframe::App for YaliApp {
egui::TopBottomPanel::bottom("footer")
.frame(egui::Frame::none()
.fill(ui::theme::c_footer_bg())
.stroke(egui::Stroke::new(1.0_f32, ui::theme::c_footer_bg())))
.stroke(egui::Stroke::new(1.0, ui::theme::c_footer_bg())))
.show(ctx, |ui| {
ui.add_space(10.0);
ui.horizontal(|ui| {
@@ -509,7 +501,6 @@ impl eframe::App for YaliApp {
.inner_margin(egui::Margin::same(20.0)))
.show(ctx, |ui| {
egui::ScrollArea::vertical()
.scroll_bar_visibility(egui::scroll_area::ScrollBarVisibility::AlwaysHidden)
.id_source("main_scroll")
.show(ui, |ui| {
if let Some(step) = self.steps.get_mut(cur) {
+122 -124
View File
@@ -7,7 +7,6 @@
use std::sync::mpsc;
use std::thread;
use std::time::{Duration, Instant};
use eframe::egui;
use rust_i18n::t;
@@ -23,10 +22,6 @@ pub struct ExecutionStep {
finished: bool,
error_screen: Option<ErrorScreen>,
slideshow: Option<Slideshow>,
/// rsync kopyalama fazı aktifken tek ilerleme çubuğu gösterilir.
copy_active: bool,
/// Kurulum bitince tamamlandı ekranına otomatik geçiş için zaman damgası.
finished_at: Option<Instant>,
}
impl InstallerStep for ExecutionStep {
@@ -38,19 +33,14 @@ impl InstallerStep for ExecutionStep {
self.tx = Some(tx);
self.finished = false;
self.error_screen = None;
self.finished_at = None;
let desktop = std::env::var("XDG_CURRENT_DESKTOP")
.unwrap_or_else(|_| "default".to_string())
.to_lowercase();
let cwd = std::env::current_dir().map(|d| d.display().to_string()).unwrap_or_default();
let ss = Slideshow::new(&desktop, &state.language);
state.install_log.push(format!("Slideshow: cwd={}, lang={}, desktop={}, slayt={}", cwd, state.language, desktop, ss.slide_count()));
self.slideshow = Some(ss);
self.slideshow = Some(Slideshow::new(&desktop, state.branding_icons.clone(), state.branding_slides.clone()));
state.install_progress = 0.0;
state.install_sub_progress = 0.0;
self.copy_active = false;
state.install_log.clear();
state.install_current_file = None;
state.install_log.push(t!("install_starting").to_string());
@@ -109,7 +99,6 @@ impl InstallerStep for ExecutionStep {
jobs::JobQueueConfig {
mount: "/mnt".to_string(),
source: src,
language,
locale,
timezone,
username,
@@ -150,7 +139,6 @@ impl InstallerStep for ExecutionStep {
jobs::JobQueueConfig {
mount: "/mnt".to_string(),
source: src,
language,
locale,
timezone,
username,
@@ -198,19 +186,12 @@ impl InstallerStep for ExecutionStep {
Ok(InstallMessage::SubProgress(p)) => {
state.install_sub_progress = p;
}
Ok(InstallMessage::CopyStarted) => {
self.copy_active = true;
}
Ok(InstallMessage::CopyFinished) => {
self.copy_active = false;
}
Ok(InstallMessage::CurrentFile(f)) => {
state.install_current_file = Some(f);
}
Ok(InstallMessage::Done) => {
state.install_progress = 1.0;
self.finished = true;
self.finished_at = Some(Instant::now());
}
Ok(InstallMessage::Error(e)) => {
self.error_screen = Some(ErrorScreen::new(
@@ -245,76 +226,76 @@ impl InstallerStep for ExecutionStep {
}
// ── Normal kurulum görünümü ────────────────────────────
// Üst: Slayt gösterisi (kalan alanı doldurur) | Alt: ilerleme durumu
// Üst: Slayt gösterisi | Alt: ilerleme + log
let available_w = ui.available_width();
let available_h = ui.available_height();
ui.vertical(|ui| {
// ── Üst kısım — başlık + slayt gösterisi (kalan alanı doldurur) ──
ui.add_space(8.0);
theme::section_heading(ui, &t!("execution_title"));
ui.label(
egui::RichText::new(t!("execution_description"))
.color(theme::c_text_dim())
.size(13.0),
);
ui.add_space(16.0);
// Alt kısım (ilerleme + durum) için ayrılan yükseklik.
// bottom_up layout en sonda olduğu için slayt alanını kalan
// yükseklikle sınırlandırıyoruz; aksi halde slayt gösterisi
// ekranın dışına itilir.
let bottom_h = 170.0;
let slide_h = (ui.available_height() - bottom_h).max(200.0);
// Üst kısım — slayt gösterisi
let slide_h = (available_h * 0.45).max(280.0);
ui.allocate_ui(egui::vec2(available_w, slide_h), |ui| {
ui.set_min_height(slide_h);
if let Some(ref mut ss) = self.slideshow {
ss.show(ui);
}
ui.vertical(|ui| {
ui.add_space(8.0);
theme::section_heading(ui, &t!("execution_title"));
ui.label(
egui::RichText::new(t!("execution_description"))
.color(theme::c_text_dim())
.size(13.0),
);
ui.add_space(16.0);
if let Some(ref mut ss) = self.slideshow {
ss.show(ui);
ui.add_space(10.0);
// Manuel ileri/geri butonları
ui.horizontal(|ui| {
if ui.small_button("‹").clicked() { ss.previous(); }
if ui.small_button("›").clicked() { ss.advance(); }
});
}
});
});
ui.add_space(16.0);
ui.separator();
ui.add_space(16.0);
// ── Alt kısım — ilerleme + durum (en altta) ──────────
ui.with_layout(egui::Layout::bottom_up(egui::Align::Min), |ui| {
ui.add_space(8.0);
// Alt kısım — ilerleme + log
let log_h = available_h - slide_h - 50.0;
ui.allocate_ui(egui::vec2(available_w, log_h), |ui| {
ui.vertical(|ui| {
ui.add_space(8.0);
// İlerleme çubuğu — yalnızca rsync kopyalama fazında
// tek bar gösterilir (eski YALI düzeni). Diğer adımlar
// (fstab, GRUB, temizlik vb.) metin + spinner olarak
// görünür.
let running = !self.finished
&& self.error_screen.is_none()
&& (self.copy_active || state.install_progress > 0.0);
// İlerleme çubuğu
let running = !self.finished
&& self.error_screen.is_none()
&& state.install_progress > 0.0;
egui::Frame::none()
.fill(theme::c_bg_widget())
.rounding(egui::Rounding::same(8.0))
.inner_margin(egui::Margin::same(12.0))
.show(ui, |ui| {
ui.set_width(available_w - 4.0);
let step_label = state.install_log.last()
.cloned()
.unwrap_or_default();
ui.label(
egui::RichText::new(&step_label)
.size(12.0)
.color(theme::c_text_dim()),
);
egui::Frame::none()
.fill(theme::c_bg_widget())
.rounding(egui::Rounding::same(8.0))
.inner_margin(egui::Margin::same(12.0))
.show(ui, |ui| {
ui.set_width(available_w - 4.0);
if self.copy_active {
let pct = format!("{:.0}%", state.install_sub_progress * 100.0);
let bar = ui.add(
egui::ProgressBar::new(state.install_sub_progress)
.animate(running)
.desired_height(20.0),
);
ui.painter().text(
bar.rect.center(),
egui::Align2::CENTER_CENTER,
pct,
egui::FontId::proportional(14.0),
theme::c_text(),
);
// Yüzde + adım etiketi
let step_label = state.install_log.last()
.cloned()
.unwrap_or_default();
ui.horizontal(|ui| {
ui.label(
egui::RichText::new(
format!("{:.0}%", state.install_progress * 100.0)
)
.strong()
.color(theme::c_accent()),
);
ui.label(
egui::RichText::new(&step_label)
.size(12.0)
.color(theme::c_text_dim()),
);
});
if let Some(ref current_file) = state.install_current_file {
ui.add_space(4.0);
@@ -324,29 +305,58 @@ impl InstallerStep for ExecutionStep {
.color(theme::c_text_dim()),
);
}
} else if running {
ui.add_space(4.0);
ui.horizontal(|ui| {
ui.spinner();
ui.colored_label(theme::c_text_dim(), t!("install_in_progress"));
});
}
});
ui.add(
egui::ProgressBar::new(state.install_progress)
.animate(running)
.desired_height(10.0),
);
// Durum etiketi
ui.add_space(8.0);
if self.finished {
theme::success_box(ui, &t!("install_done"));
} else if running {
ui.horizontal(|ui| {
ui.spinner();
ui.colored_label(theme::c_text_dim(), t!("install_in_progress"));
});
}
if state.install_sub_progress > 0.0 {
ui.add_space(4.0);
ui.add(
egui::ProgressBar::new(state.install_sub_progress)
.animate(running)
.desired_height(6.0)
.fill(theme::c_accent_dim()),
);
}
});
ui.add_space(16.0);
ui.separator();
ui.add_space(16.0);
// Durum etiketi
ui.add_space(8.0);
if self.finished {
theme::success_box(ui, &t!("install_done"));
} else if running {
ui.horizontal(|ui| {
ui.spinner();
ui.colored_label(theme::c_text_dim(), t!("install_in_progress"));
});
}
ui.add_space(8.0);
// Log terminali
let terminal_h = if self.finished { log_h - 60.0 } else { log_h - 80.0 };
egui::ScrollArea::vertical()
.max_height(terminal_h.max(100.0))
.stick_to_bottom(true)
.id_source("exec_log")
.show(ui, |ui| {
egui::Frame::none()
.fill(egui::Color32::from_rgb(12, 12, 20))
.rounding(egui::Rounding::same(6.0))
.inner_margin(egui::Margin::same(10.0))
.show(ui, |ui| {
ui.set_width(available_w - 8.0);
for line in &state.install_log {
let color = log_color(line);
ui.colored_label(color, line);
}
});
});
});
});
});
@@ -354,18 +364,6 @@ impl InstallerStep for ExecutionStep {
if !self.finished && self.error_screen.is_none() {
ui.ctx().request_repaint();
}
// Kurulum tamamlanınca kısa bir bekleyişten sonra otomatik olarak
// tamamlandı ekranına geç
if self.finished && self.error_screen.is_none() {
if let Some(at) = self.finished_at {
if at.elapsed() >= Duration::from_secs(2) {
state.current_step += 1;
} else {
ui.ctx().request_repaint_after(Duration::from_secs(1));
}
}
}
}
fn is_complete(&self, _state: &GlobalState) -> bool {
@@ -373,16 +371,16 @@ impl InstallerStep for ExecutionStep {
}
}
// fn log_color(line: &str) -> egui::Color32 {
// if line.starts_with('✗') {
// theme::c_error()
// } else if line.starts_with('✓') {
// theme::c_success()
// } else if line.starts_with('▶') {
// theme::c_accent()
// } else if line.starts_with('⚠') {
// theme::c_warning()
// } else {
// egui::Color32::from_rgb(185, 185, 205)
// }
// }
fn log_color(line: &str) -> egui::Color32 {
if line.starts_with('✗') {
theme::c_error()
} else if line.starts_with('✓') {
theme::c_success()
} else if line.starts_with('▶') {
theme::c_accent()
} else if line.starts_with('⚠') {
theme::c_warning()
} else {
egui::Color32::from_rgb(185, 185, 205)
}
}
+4 -5
View File
@@ -13,7 +13,7 @@ impl InstallerStep for FinishStep {
ui.vertical_centered(|ui| {
// Büyük onay simgesi
ui.label(
egui::RichText::new("\u{EB7B}")
egui::RichText::new("✔")
.size(72.0)
.color(theme::c_success()),
);
@@ -37,10 +37,9 @@ impl InstallerStep for FinishStep {
// Yeniden Başlat — ana eylem butonu
if theme::primary_button(ui, &t!("finish_restart")).clicked() {
let _ = std::process::Command::new("sync").status();
// Temiz kapanma: init (sysvinit) dosya sistemlerini unmount eder.
// `-f` kullanılmaz — zorla reboot FAT backup boot sektörünü
// güncellemeden sistemi kesip fsck.fat hatası bırakıyordu.
let _ = std::process::Command::new("reboot").spawn();
let _ = std::process::Command::new("reboot")
.arg("-f")
.spawn();
ui.ctx().send_viewport_cmd(egui::ViewportCommand::Close);
}
-32
View File
@@ -183,38 +183,6 @@ impl InstallerStep for LocationStep {
);
ui.add_space(8.0);
// Seçilen zamana göre tarih ve saat gösterimi
ui.horizontal(|ui| {
ui.label(egui::RichText::new(t!("datetime_current")).strong().color(theme::c_text()));
ui.add_space(8.0);
if state.use_ntp {
// NTP modunda seçilen zaman dilimine göre zamanı göster
let now = chrono::Utc::now();
let formatted = if let Ok(tz) = state.timezone.parse::<chrono_tz::Tz>() {
let local_time = now.with_timezone(&tz);
local_time.format("%Y-%m-%d %H:%M:%S").to_string()
} else {
// Zaman dilimi parse edilemezse UTC göster
now.format("%Y-%m-%d %H:%M:%S UTC").to_string()
};
ui.label(egui::RichText::new(formatted).color(theme::c_success()));
} else {
// Manuel modunda seçilen değerleri göster
let formatted = format!(
"{:04}-{:02}-{:02} {:02}:{:02}",
state.manual_year,
state.manual_month,
state.manual_day,
state.manual_hour,
state.manual_minute
);
ui.label(egui::RichText::new(formatted).color(theme::c_text()));
}
});
ui.add_space(12.0);
let (mut cur_region, mut cur_zone) = Self::split_tz(&state.timezone);
if !self.regions.contains(&cur_region) {
+6 -7
View File
@@ -154,25 +154,24 @@ impl InstallerStep for NetworkStep {
.show(ui, |ui| {
for (i, net) in self.scan_results.clone().iter().enumerate() {
ui.horizontal(|ui| {
let lock = if net.secured { "\u{EECE}" } else { "\u{EDCF}" };
let lock = if net.secured { "🔒" } else { "🌐" };
let sig = net.signal.parse::<i32>().unwrap_or(0);
let bars = if sig > -50 { "\u{F133}" } else if sig > -67 { "\u{F12F}" } else if sig > -80 { "\u{F12B}" } else { "\u{F134}" };
let active = if net.active { " \u{EB7B}" } else { "" };
let bars = if sig > -50 { "▂▄▆█" } else if sig > -67 { "▂▄▆" } else if sig > -80 { "▂▄" } else { "▂" };
let active = if net.active { " ✓" } else { "" };
ui.label(format!("{} {}{}", lock, net.ssid, active));
ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| {
ui.label(
egui::RichText::new(bars)
.size(16.0)
.size(10.0)
.color(egui::Color32::from_rgb(120, 120, 140)),
);
if net.secured && !net.active {
ui.add(egui::TextEdit::singleline(&mut state.network_wifi_password)
.password(!self.show_password.get(i).copied().unwrap_or(false))
.hint_text(t!("network_password_hint"))
.desired_width(120.0)
.text_color(egui::Color32::from_rgb(120, 120, 140)));
if ui.add_sized([24.0, 24.0], egui::Button::new(egui::RichText::new("\u{ECB5}").size(14.0))).clicked() {
.desired_width(120.0));
if ui.small_button("👁").clicked() {
if self.show_password.len() <= i {
self.show_password.resize(i + 1, false);
}
+1012 -346
View File
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+15 -15
View File
@@ -84,10 +84,10 @@ impl RescueStep {
if let Ok(s) = r {
if s.success() {
self.mount_done = true;
self.log.push(format!("\u{EB7B} {} → /mnt bağlandı.", dev));
self.log.push(format!("✓ {} → /mnt bağlandı.", dev));
return true;
} else {
self.log.push(format!("\u{EA6B} {} bağlanamadı (zaten bağlı veya hata).", dev));
self.log.push(format!("⚠ {} bağlanamadı (zaten bağlı veya hata).", dev));
self.mount_done = true; // Belki zaten bağlı
return true;
}
@@ -194,13 +194,13 @@ impl RescueStep {
Ok(o) if o.status.success() => {
self.pisi.result_ok = Some(true);
self.pisi.result_msg = Some(t!("rescue_pisi_history_ok").to_string());
self.log.push(format!("\u{EB7B} pisi takeback {} başarılı.", op));
self.log.push(format!("✓ pisi takeback {} başarılı.", op));
}
Ok(o) => {
let err = String::from_utf8_lossy(&o.stderr).trim().to_string();
self.pisi.result_ok = Some(false);
self.pisi.result_msg = Some(t!("rescue_pisi_history_err", err = err).to_string());
self.log.push(format!("\u{EA21} pisi takeback {}: {}", op, err));
self.log.push(format!("❌ pisi takeback {}: {}", op, err));
}
Err(e) => {
self.pisi.result_ok = Some(false);
@@ -249,8 +249,8 @@ impl RescueStep {
if self.mount_done && resp.clicked() {
self.log.push(t!("rescue_chroot_start").to_string());
match Self::run_rescue_cmd("chroot", &["/mnt", "/bin/bash", "-c", "echo 'chroot OK'"]) {
Ok(msg) => self.log.push(format!("\u{EB7B} {}", msg)),
Err(e) => self.log.push(format!("\u{EA21} {}", e)),
Ok(msg) => self.log.push(format!("✓ {}", msg)),
Err(e) => self.log.push(format!("❌ {}", e)),
}
}
@@ -268,7 +268,7 @@ impl RescueStep {
match Self::run_rescue_cmd(cmd, args) {
Ok(m) if !m.is_empty() => self.log.push(m),
Ok(_) => {},
Err(e) => self.log.push(format!("\u{EA6B} {}", e)),
Err(e) => self.log.push(format!("⚠ {}", e)),
}
}
// grub2-install
@@ -278,13 +278,13 @@ impl RescueStep {
a.extend_from_slice(&grub_args);
a
}) {
Ok(m) => self.log.push(format!("\u{EB7B} grub2-install: {}", m)),
Err(e) => self.log.push(format!("\u{EA21} grub2-install: {}", e)),
Ok(m) => self.log.push(format!("✓ grub2-install: {}", m)),
Err(e) => self.log.push(format!("❌ grub2-install: {}", e)),
}
// grub2-mkconfig
match Self::run_rescue_cmd("chroot", &["/mnt", "grub2-mkconfig", "-o", "/boot/grub2/grub.cfg"]) {
Ok(m) => self.log.push(format!("\u{EB7B} grub2-mkconfig: {}", m)),
Err(e) => self.log.push(format!("\u{EA21} grub2-mkconfig: {}", e)),
Ok(m) => self.log.push(format!("✓ grub2-mkconfig: {}", m)),
Err(e) => self.log.push(format!("❌ grub2-mkconfig: {}", e)),
}
}
});
@@ -479,11 +479,11 @@ impl InstallerStep for RescueStep {
.inner_margin(egui::Margin::same(8.0))
.show(ui, |ui| {
for line in &self.log {
let color = if line.starts_with("\u{EB7B}") {
let color = if line.starts_with("✓") {
theme::c_success()
} else if line.starts_with("\u{EA21}") {
} else if line.starts_with("❌") {
theme::c_error()
} else if line.starts_with("\u{EA6B}") {
} else if line.starts_with("⚠") {
theme::c_warning()
} else {
theme::c_text_dim()
@@ -501,7 +501,7 @@ impl InstallerStep for RescueStep {
// Geri dön butonu
ui.add_space(8.0);
if theme::secondary_button(ui, &format!("\u{EB7C} {}", t!("rescue_return"))).clicked() {
if theme::secondary_button(ui, &t!("rescue_return")).clicked() {
state.rescue_mode = false;
}
}
+23 -76
View File
@@ -18,20 +18,12 @@ impl InstallerStep for SummaryStep {
ui.add_space(12.0);
egui::Frame::group(ui.style()).show(ui, |ui| {
// Frame'in tüm genişliği kaplamasını sağlıyoruz
ui.set_width(ui.available_width());
// Grid genişlik ayarları
let available_width = ui.available_width();
let col1_width = 160.0; // Sol sütun (etiketler)
let col2_width = (available_width - col1_width - 20.0).max(100.0); // Sağ sütun kalan alan
egui::Grid::new("summary_grid")
.num_columns(2)
.spacing([20.0, 8.0])
.striped(true)
.spacing([12.0, 8.0])
.min_col_width(col1_width)
.max_col_width(col2_width)
.show(ui, |ui| {
summary_row(ui, t!("summary_language"), &state.language);
summary_row(ui, t!("summary_timezone"), &state.timezone);
@@ -43,25 +35,15 @@ impl InstallerStep for SummaryStep {
};
summary_row(ui, t!("summary_keyboard"), &kb);
let disk_str = if state.erase_disk {
match &state.selected_disk {
Some(d) => format!("{} \u{EA6B} {}", d, t!("summary_erase_mode")),
None => t!("summary_not_selected").to_string(),
}
} else if !state.custom_partitions.is_empty() {
// Manuel modda tanım yapılan TÜM diskleri listele
let mut disks: Vec<&str> = Vec::new();
for part in &state.custom_partitions {
if !disks.contains(&part.disk.as_str()) {
disks.push(&part.disk);
let disk_str = match &state.selected_disk {
Some(d) => {
if state.erase_disk {
format!("{} — {}", d, t!("summary_erase_mode"))
} else {
format!("{} — {}", d, t!("summary_manual_mode"))
}
}
format!("{} \u{EA6B} {}", disks.join(", "), t!("summary_manual_mode"))
} else {
match &state.selected_disk {
Some(d) => format!("{} \u{EA6B} {}", d, t!("summary_manual_mode")),
None => t!("summary_not_selected").to_string(),
}
None => t!("summary_not_selected").to_string(),
};
summary_row(ui, t!("summary_disk"), &disk_str);
@@ -83,45 +65,15 @@ impl InstallerStep for SummaryStep {
}
} else if !state.custom_partitions.is_empty() {
let mut plan_lines = Vec::new();
// Bölümleri ait oldukları diske göre gruplu listele
let mut seen_disks: Vec<&str> = Vec::new();
for part in &state.custom_partitions {
if !seen_disks.contains(&part.disk.as_str()) {
seen_disks.push(&part.disk);
}
}
for d in seen_disks {
plan_lines.push(format!("\u{EDFB} {}", d));
for part in state.custom_partitions.iter().filter(|p| p.disk == d) {
let size_str = if part.size_mb == 0 {
// Sembolik boyut: kalan kapasiteyi göster
let others: u64 = state.custom_partitions.iter()
.filter(|q| q.disk == part.disk && !q.delete && q.size_mb > 0)
.map(|q| q.size_mb)
.sum();
let cap = state.available_disks.iter()
.find(|dk| dk.name == part.disk)
.map(|dk| dk.size_bytes / 1_048_576)
.unwrap_or(0);
let rem = cap.saturating_sub(others);
if rem >= 1024 {
format!("{:.1} GB", rem as f64 / 1024.0)
} else {
format!("{} MB", rem)
}
} else if part.size_mb >= 1024 {
format!("{:.1} GB", part.size_mb as f64 / 1024.0)
} else {
format!("{} MB", part.size_mb)
};
if part.delete {
plan_lines.push(format!("{} \u{EA6B} {} ({}, {}) \u{ECA1} {}",
part.device, "—", "—", size_str, t!("mp_will_delete")));
} else {
plan_lines.push(format!("{} \u{EA6B} {} ({}, {})",
part.device, part.mountpoint, part.fstype, size_str));
}
}
let size_str = if part.size_mb == 0 {
t!("mp_size_remaining").to_string()
} else if part.size_mb >= 1024 {
format!("{:.1} GB", part.size_mb as f64 / 1024.0)
} else {
format!("{} MB", part.size_mb)
};
plan_lines.push(format!("{} ➔ {} ({}, {})", part.device, part.mountpoint, part.fstype, size_str));
}
summary_row(ui, t!("summary_custom_partitions"), &plan_lines.join("\n"));
}
@@ -166,18 +118,16 @@ impl InstallerStep for SummaryStep {
ui.add_space(16.0);
// Uyarı kutusu (\u{EA6B} uyarısı eklenmiş)
// Uyarı kutusu
egui::Frame::none()
.fill(egui::Color32::from_rgba_unmultiplied(200, 60, 40, 30))
.rounding(6.0)
.inner_margin(egui::Margin::same(10.0))
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.colored_label(
egui::Color32::from_rgb(255, 255, 255),
format!("\u{EA21} {}", t!("summary_warning")),
);
});
ui.colored_label(
egui::Color32::from_rgb(180, 60, 40),
t!("summary_warning"),
);
});
}
@@ -189,9 +139,6 @@ impl InstallerStep for SummaryStep {
fn summary_row(ui: &mut egui::Ui, label: impl Into<String>, value: &str) {
ui.label(egui::RichText::new(label.into()).strong().color(crate::ui::theme::c_text()));
// Değer sütununun taşmasını engellemek ve hizalamayı korumak için wrap() kullanıyoruz
ui.add(egui::Label::new(egui::RichText::new(value).color(crate::ui::theme::c_text())).wrap(true));
ui.label(egui::RichText::new(value).color(crate::ui::theme::c_text()));
ui.end_row();
}
}
+42 -44
View File
@@ -211,16 +211,16 @@ impl InstallerStep for UsersStep {
if !state.real_name.is_empty() {
ui.label(
egui::RichText::new("\u{EB7B}")
.size(18.0)
.color(theme::c_success())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_success())
);
}else{
ui.label(
egui::RichText::new("\u{EA21}")
.size(18.0)
.color(theme::c_error())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_error())
);
}
});
@@ -238,16 +238,16 @@ impl InstallerStep for UsersStep {
form_input_text(ui, &mut state.username);
if !state.username.is_empty() {
if let Some(_err) = validate_username(&state.username) {
ui.label(
egui::RichText::new("\u{EA21}")
.size(18.0)
.color(theme::c_error())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_error())
);
} else {
ui.label(
egui::RichText::new("\u{EB7B}")
.size(18.0)
.color(theme::c_success())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_success())
);
}
}
@@ -290,16 +290,16 @@ impl InstallerStep for UsersStep {
form_input_password(ui, &mut state.password_confirm, show_pwd);
if !state.password_confirm.is_empty() {
if state.password == state.password_confirm {
ui.label(
egui::RichText::new("\u{EB7B}")
.size(18.0)
.color(theme::c_success())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_success())
);
} else {
ui.label(
egui::RichText::new("\u{EA21}")
.size(18.0)
.color(theme::c_error())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_error())
);
}
}
@@ -316,16 +316,16 @@ impl InstallerStep for UsersStep {
form_input_text(ui, &mut state.hostname);
if !state.hostname.is_empty() {
if is_valid_hostname(&state.hostname) {
ui.label(
egui::RichText::new("\u{EB7B}")
.size(18.0)
.color(theme::c_success())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_success())
);
} else {
ui.label(
egui::RichText::new("\u{EA21}")
.size(18.0)
.color(theme::c_error())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_error())
);
}
}
@@ -377,16 +377,16 @@ impl InstallerStep for UsersStep {
form_input_password(ui, &mut state.root_password_confirm, show_root_pwd_conf);
if !state.root_password_confirm.is_empty() {
if state.root_password == state.root_password_confirm {
ui.label(
egui::RichText::new("\u{EB7B}")
.size(18.0)
.color(theme::c_success())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/check.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_success())
);
} else {
ui.label(
egui::RichText::new("\u{EA21}")
.size(18.0)
.color(theme::c_error())
ui.add(
egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
.max_width(18.0).max_height(18.0)
.tint(theme::c_error())
);
}
}
@@ -410,17 +410,15 @@ impl InstallerStep for UsersStep {
let user_ok = is_valid_username(&state.username)
&& is_valid_hostname(&state.hostname)
// DÜZELTME: Kullanıcı şifresi en az 3 karakter olmalı
&& state.password.len() >= 3
&& state.password.len() >= 8
&& state.password == state.password_confirm;
if self.use_same_for_admin {
user_ok
} else {
// DÜZELTME: Root şifresi boşsa veya 3 karakterden uzunsa ve eşleşiyorsa geçerli
user_ok && (
state.root_password.is_empty()
|| (state.root_password.len() >= 3 && state.root_password == state.root_password_confirm)
|| (state.root_password.len() >= 8 && state.root_password == state.root_password_confirm)
)
}
}
+1 -1
View File
@@ -56,7 +56,7 @@ fn gradient_button(ui: &mut egui::Ui, text: &str, top_color: Color32, bottom_col
ui.painter().rect(strip_rect, Rounding::ZERO, c, Stroke::NONE);
}
ui.painter().rect(rect, rounding, Color32::TRANSPARENT, Stroke::new(1.0_f32, actual_border));
ui.painter().rect(rect, rounding, Color32::TRANSPARENT, Stroke::new(1.0, actual_border));
ui.painter().galley(
egui::pos2(rect.center().x - galley.size().x / 2.0, rect.center().y - galley.size().y / 2.0),
+6 -6
View File
@@ -7,13 +7,13 @@ pub fn apply_visuals(v: &mut egui::Visuals) {
v.widgets.hovered.weak_bg_fill = Color32::from_rgb(200, 200, 200);
v.widgets.active.weak_bg_fill = Color32::from_rgb(180, 180, 180);
v.widgets.inactive.fg_stroke = Stroke::new(1.0_f32, Color32::from_rgb(50, 50, 50));
v.widgets.hovered.fg_stroke = Stroke::new(1.5_f32, Color32::WHITE);
v.widgets.active.fg_stroke = Stroke::new(2.0_f32, Color32::WHITE);
v.widgets.inactive.fg_stroke = Stroke::new(1.0, Color32::from_rgb(50, 50, 50));
v.widgets.hovered.fg_stroke = Stroke::new(1.5, Color32::WHITE);
v.widgets.active.fg_stroke = Stroke::new(2.0, Color32::WHITE);
v.widgets.inactive.bg_stroke = Stroke::new(1.0_f32, Color32::from_rgb(155, 155, 155));
v.widgets.hovered.bg_stroke = Stroke::new(1.0_f32, Color32::from_rgb(155, 155, 155));
v.widgets.active.bg_stroke = Stroke::new(1.0_f32, Color32::from_rgb(155, 155, 155));
v.widgets.inactive.bg_stroke = Stroke::new(1.0, Color32::from_rgb(155, 155, 155));
v.widgets.hovered.bg_stroke = Stroke::new(1.0, Color32::from_rgb(155, 155, 155));
v.widgets.active.bg_stroke = Stroke::new(1.0, Color32::from_rgb(155, 155, 155));
}
fn is_dark_ui(ui: &egui::Ui) -> bool {
+1 -1
View File
@@ -68,7 +68,7 @@ impl ErrorScreen {
// ── Hata mesajı ───────────────────────────────
egui::Frame::none()
.fill(egui::Color32::from_rgba_unmultiplied(0xF4, 0x43, 0x36, 18))
.stroke(egui::Stroke::new(1.0_f32, theme::c_error()))
.stroke(egui::Stroke::new(1.0, theme::c_error()))
.rounding(egui::Rounding::same(8.0))
.inner_margin(egui::Margin::same(12.0))
.show(ui, |ui| {
+329 -69
View File
@@ -2,91 +2,327 @@
//!
//! Her slayt: başlık, açıklama metni ve isteğe bağlı bir simge karakteri.
//! Slaytlar otomatik olarak 6 saniyede bir değişir.
//! Slaytlar harici `slides/slides_{lang}.toml` dosyasından yüklenir.
//! Masaüstü ortamına göre farklı slayt setleri yüklenebilir.
use eframe::egui;
use std::collections::HashMap;
use std::time::{Duration, Instant};
use rust_i18n::t;
use std::borrow::Cow;
const SLIDE_INTERVAL: Duration = Duration::from_secs(6);
pub struct Slide {
pub icon: String,
pub icon: &'static str,
pub title: Cow<'static, str>,
pub description: Cow<'static, str>,
}
/// Masaüstü ortamına göre slayt seti döner.
/// ISO meta verisi ileride `/etc/livecd-desktop` gibi bir dosyadan okunabilir.
pub fn slides_for_desktop(desktop: &str, branding_slides: &[crate::branding::SlideItem]) -> Vec<Slide> {
if !branding_slides.is_empty() {
branding_slides
.iter()
.filter(|item| item.desktop.is_empty() || item.desktop.to_lowercase() == desktop.to_lowercase())
.map(|item| Slide {
icon: Box::leak(item.icon.clone().into_boxed_str()),
title: Cow::Owned(item.title.clone()),
description: Cow::Owned(item.description.clone()),
})
.collect()
} else {
let mut slides = default_slides();
let specific_slides = match desktop {
"budgie" => budgie_slides(),
"cinnamon" => cinnamon_slides(),
"gnome" => gnome_slides(),
"kde" | "plasma" => kde_slides(),
"lxde" => lxde_slides(),
"lxqt" => lxqt_slides(),
"lumina" => lumina_slides(),
"mate" => mate_slides(),
"xfce" => xfce_slides(),
_ => vec![],
};
slides.extend(specific_slides);
slides
}
}
fn default_slides() -> Vec<Slide> {
vec![
Slide {
icon: "pisilogo",
title: t!("welcome_slide_title"),
description: t!("welcome_slide_description"),
},
Slide {
icon: "pisi",
title: t!("package_manager_title"),
description: t!("package_manager_description"),
},
Slide {
icon: "community",
title: t!("community_title"),
description: t!("community_description"),
},
Slide {
icon: "pisi_speed",
title: t!("speed_title"),
description: t!("speed_description"),
},
Slide {
icon: "securety",
title: t!("security_title"),
description: t!("security_description"),
},
Slide {
icon: "customization",
title: t!("customization_title"),
description: t!("customization_description"),
},
]
}
fn budgie_slides() -> Vec<Slide> {
vec![
Slide {
icon: "budgie",
title: t!("budgie_title"),
description: t!("budgie_description"),
},
Slide {
icon: "budgie_applets",
title: t!("budgie_applets_title"),
description: t!("budgie_applets_description"),
},
Slide {
icon: "budgie_raven",
title: t!("budgie_raven_title"),
description: t!("budgie_raven_description"),
},
Slide {
icon: "budgie_gtk_theme",
title: t!("budgie_gtk_theme_title"),
description: t!("budgie_gtk_theme_description"),
},
]
}
fn cinnamon_slides() -> Vec<Slide> {
vec![
Slide {
icon: "cinnamon",
title: t!("cinnamon_title"),
description: t!("cinnamon_description"),
},
Slide {
icon: "cinnamon_panel",
title: t!("cinnamon_panel_title"),
description: t!("cinnamon_panel_description"),
},
Slide {
icon: "cinnamon_settings",
title: t!("cinnamon_settings_title"),
description: t!("cinnamon_settings_description"),
},
Slide {
icon: "cinnamon_desklets",
title: t!("cinnamon_desklets_title"),
description: t!("cinnamon_desklets_description"),
},
]
}
fn gnome_slides() -> Vec<Slide> {
vec![
Slide {
icon: "gnome",
title: t!("gnome_title"),
description: t!("gnome_description"),
},
Slide {
icon: "gnome_search",
title: t!("gnome_search_title"),
description: t!("gnome_search_description"),
},
Slide {
icon: "gnome_extensions",
title: t!("gnome_extensions_title"),
description: t!("gnome_extensions_description"),
},
]
}
fn kde_slides() -> Vec<Slide> {
vec![
Slide {
icon: "kde",
title: t!("kde_title"),
description: t!("kde_description"),
},
Slide {
icon: "file_manager",
title: t!("dolphin_title"),
description: t!("dolphin_description"),
},
Slide {
icon: "kde_connect",
title: t!("kde_connect_title"),
description: t!("kde_connect_description"),
},
Slide {
icon: "discover",
title: t!("discover_title"),
description: t!("discover_description"),
},
]
}
fn lxde_slides() -> Vec<Slide> {
vec![
Slide {
icon: "lxde",
title: t!("lxde_title"),
description: t!("lxde_description"),
},
Slide {
icon: "lxde_openbox",
title: t!("lxde_openbox_title"),
description: t!("lxde_openbox_description"),
},
Slide {
icon: "file_manager",
title: t!("lxde_pcmanfm_title"),
description: t!("lxde_pcmanfm_description"),
},
Slide {
icon: "lxde_lxpanel",
title: t!("lxde_lxpanel_title"),
description: t!("lxde_lxpanel_description"),
},
]
}
fn lxqt_slides() -> Vec<Slide> {
vec![
Slide {
icon: "lxqt",
title: t!("lxqt_title"),
description: t!("lxqt_description"),
},
Slide {
icon: "lxqt_panel",
title: t!("lxqt_panel_title"),
description: t!("lxqt_panel_description"),
},
Slide {
icon: "file_manager",
title: t!("lxqt_pcmanfm_qt_title"),
description: t!("lxqt_pcmanfm_qt_description"),
},
]
}
fn lumina_slides() -> Vec<Slide> {
vec![
Slide {
icon: "lumina",
title: t!("lumina_title"),
description: t!("lumina_description"),
},
Slide {
icon: "lumina_applications",
title: t!("lumina_applications_title"),
description: t!("lumina_applications_description"),
},
Slide {
icon: "lumina_theme",
title: t!("lumina_theme_title"),
description: t!("lumina_theme_description"),
},
Slide {
icon: "lumina_panel",
title: t!("lumina_panel_title"),
description: t!("lumina_panel_description"),
},
]
}
fn mate_slides() -> Vec<Slide> {
vec![
Slide {
icon: "mate",
title: t!("mate_title"),
description: t!("mate_description"),
},
Slide {
icon: "file_manager",
title: t!("mate_caja_title"),
description: t!("mate_caja_description"),
},
Slide {
icon: "mate_settings",
title: t!("mate_settings_title"),
description: t!("mate_settings_description"),
},
Slide {
icon: "mate_applications",
title: t!("mate_applications_title"),
description: t!("mate_applications_description"),
},
]
}
fn xfce_slides() -> Vec<Slide> {
vec![
Slide {
icon: "xfce",
title: t!("xfce_title"),
description: t!("xfce_description"),
},
Slide {
icon: "xfce_panel",
title: t!("xfce_panel_title"),
description: t!("xfce_panel_description"),
},
Slide {
icon: "file_manager",
title: t!("xfce_thunar_title"),
description: t!("xfce_thunar_description"),
},
Slide {
icon: "xfce_settings",
title: t!("xfce_settings_title"),
description: t!("xfce_settings_description"),
},
]
}
// ─── Slayt gösterisi widget'ı ───────────────────────────────────────────
pub struct Slideshow {
slides: Vec<Slide>,
current: usize,
last_advance: Instant,
/// branding.toml'dan gelen ikon-adı → `file://` URI eşlemesi
icons: HashMap<String, String>,
}
impl Slideshow {
pub fn new(desktop: &str, lang: &str) -> Self {
let slides = Self::load_slides(desktop, lang);
pub fn new(desktop: &str, icons: HashMap<String, String>, branding_slides: Vec<crate::branding::SlideItem>) -> Self {
Self {
slides,
current: 0,
slides: slides_for_desktop(desktop, &branding_slides),
current: 0,
last_advance: Instant::now(),
icons,
}
}
fn load_slides(desktop: &str, lang: &str) -> Vec<Slide> {
let filenames = [
format!("slides_{}.toml", lang),
"slides.toml".to_string(),
];
let exe_dir = std::env::current_exe()
.ok()
.and_then(|p| p.parent().map(|d| d.to_path_buf()))
.unwrap_or_default();
let search_dirs = [
std::env::current_dir().unwrap_or_default(),
exe_dir,
std::path::PathBuf::from("/usr/share/yali"),
std::path::PathBuf::from("/etc/yali"),
];
for dir in &search_dirs {
for fname in &filenames {
let path = dir.join("slides").join(fname);
let content = match std::fs::read_to_string(&path) {
Ok(c) => c,
Err(_) => continue,
};
match toml::from_str::<crate::branding::SlidesConfig>(&content) {
Ok(cfg) => {
let slides: Vec<Slide> = cfg.items
.into_iter()
.filter(|item| item.desktop.is_empty() || item.desktop.to_lowercase() == desktop.to_lowercase())
.map(|item| Slide {
icon: item.icon,
title: Cow::Owned(item.title),
description: Cow::Owned(item.description),
})
.collect();
eprintln!("[slides] {} -> {} slayt yüklendi", path.display(), slides.len());
return slides;
}
Err(e) => {
eprintln!("[slides] {} ayrıştırılamadı: {}", path.display(), e);
}
}
}
}
eprintln!("[slides] hiçbir slayt dosyası yüklenemedi");
Vec::new()
}
/// Her frame'de çağrılır. Otomatik ilerlemeyi ve çizimi yönetir.
pub fn show(&mut self, ui: &mut egui::Ui) {
if self.slides.is_empty() {
ui.label(egui::RichText::new("Slayt yüklenemedi").size(14.0));
return;
}
// Otomatik ilerleme
if self.last_advance.elapsed() >= SLIDE_INTERVAL {
self.current = (self.current + 1) % self.slides.len();
@@ -102,21 +338,15 @@ impl Slideshow {
let slide = &self.slides[self.current];
// İkon: doğrudan dosya yolundan file:// URI'sine çevir
let resolved_uri = if !slide.icon.is_empty() {
Some(crate::branding::path_to_file_uri(&slide.icon))
} else {
None
};
// İkona karşılık gelen URI'yi branding haritasından al
let resolved_uri = self.icons.get(slide.icon).cloned();
// Kart, kendisine ayrılan dikey alanı doldursun.
let fill_h = ui.available_height().max(180.0);
egui::Frame::none()
.fill(crate::ui::theme::c_bg_widget())
.rounding(egui::Rounding::same(10.0))
.inner_margin(egui::Margin::same(24.0))
.show(ui, |ui| {
ui.set_min_height(fill_h);
ui.set_min_height(180.0);
ui.vertical_centered(|ui| {
// Simge veya Görsel
if let Some(uri) = resolved_uri {
@@ -127,7 +357,7 @@ impl Slideshow {
);
} else {
ui.label(
egui::RichText::new("\u{1f5bc}\u{fe0f}")
egui::RichText::new(slide.icon)
.size(48.0),
);
}
@@ -149,11 +379,41 @@ impl Slideshow {
.color(crate::ui::theme::c_text_dim()),
);
ui.add_space(16.0);
// Nokta göstergesi
ui.horizontal(|ui| {
let dot_size = egui::vec2(8.0, 8.0);
for i in 0..self.slides.len() {
let color = if i == self.current {
crate::ui::theme::c_accent()
} else {
crate::ui::theme::c_border()
};
let (rect, response) = ui.allocate_exact_size(dot_size, egui::Sense::click());
ui.painter().circle_filled(rect.center(), 4.0, color);
if response.clicked() {
self.current = i;
self.last_advance = Instant::now();
}
ui.add_space(4.0);
}
});
});
});
}
pub fn slide_count(&self) -> usize {
self.slides.len()
pub fn advance(&mut self) {
self.current = (self.current + 1) % self.slides.len();
self.last_advance = Instant::now();
}
pub fn previous(&mut self) {
if self.current == 0 {
self.current = self.slides.len() - 1;
} else {
self.current -= 1;
}
self.last_advance = Instant::now();
}
}
+25 -156
View File
@@ -52,8 +52,6 @@ pub fn c_sidebar_text() -> Color32 { color_from_hex(SIDEBAR_TEXT.load(Ordering::
pub fn c_footer_bg() -> Color32 { color_from_hex(BG_FOOTER.load(Ordering::Relaxed)) }
pub fn c_root_bg() -> Color32 { color_from_hex(BG_ROOT.load(Ordering::Relaxed)) }
pub fn c_keyboard_bg() -> Color32 { color_from_hex(KEYBOARD_BG.load(Ordering::Relaxed)) }
//pub fn select_disk_label() -> Color32 { color_from_hex(0xFFFFFF) }
pub fn set_theme_mode(is_dark: bool) {
if is_dark {
@@ -110,85 +108,10 @@ pub fn apply(ctx: &egui::Context, is_dark: bool) {
ctx.set_visuals(build_visuals(is_dark));
}
// fn setup_fonts(ctx: &egui::Context) {
// let mut fonts = egui::FontDefinitions::default();
// // 1. ADIM: Sadece Metin (Text) Fontunu Bul ve Yükle
// let text_font_paths = [
// "/usr/share/fonts/TTF/OpenSans-Regular.ttf",
// "/usr/share/fonts/noto/NotoSans-Regular.ttf",
// "/usr/share/fonts/exo-1/Exo-Regular.otf",
// "/usr/share/fonts/exo-1/Exo2-Medium.ttf",
// "/usr/share/fonts/exo-1/Exo-Medium.otf",
// ];
// for path in text_font_paths {
// if let Ok(data) = std::fs::read(path) {
// fonts.font_data.insert(
// "system_text_font".to_owned(),
// egui::FontData::from_owned(data),
// );
// // Metin fontunu en başa (0. indeks) koyuyoruz ki varsayılan olarak bu kullanılsın
// fonts
// .families
// .entry(egui::FontFamily::Proportional)
// .or_default()
// .insert(0, "system_text_font".to_owned());
// break;
// }
// }
// // 2. ADIM: Renkli Emoji Fontunu Ara ve "Fallback" (Yedek) Olarak Sona Ekle
// // ISO ortamlarında Noto Color Emoji genellikle bu yollardan birinde olur
// let emoji_font_paths = [
// "/usr/share/fonts/noto/NotoColorEmoji.ttf",
// ];
// for path in emoji_font_paths {
// if let Ok(data) = std::fs::read(path) {
// fonts.font_data.insert(
// "system_emoji_font".to_owned(),
// egui::FontData::from_owned(data),
// );
// // ÖNEMLİ: Emojiyi listenin SONUNA (push ile) ekliyoruz.
// // Böylece egui normal harfleri sistem fontuyla yazacak,
// // harf sistem fontunda yoksa (yani emojiyse) gidip bu fonttan alacak.
// fonts
// .families
// .entry(egui::FontFamily::Proportional)
// .or_default()
// .push("system_emoji_font".to_owned());
// fonts
// .families
// .entry(egui::FontFamily::Monospace)
// .or_default()
// .insert(0, "system_emoji_font".to_owned());
// break;
// }
// }
// // 3. ADIM: Remixicon ikon fontunu projeye göm (PUA bölgesindeki karakterler için fallback)
// let remixicon_data = include_bytes!("../../assets/remixicon.ttf");
// fonts.font_data.insert(
// "remixicon".to_owned(),
// egui::FontData::from_owned(remixicon_data.to_vec()),
// );
// fonts
// .families
// .entry(egui::FontFamily::Proportional)
// .or_default()
// .push("remixicon".to_owned());
// ctx.set_fonts(fonts);
// }
fn setup_fonts(ctx: &egui::Context) {
let mut fonts = egui::FontDefinitions::default();
// 1. ADIM: Metin ve Monospace için Sistem Fontu Yükleme
// 1. ADIM: Sadece Metin (Text) Fontunu Bul ve Yükle
let text_font_paths = [
"/usr/share/fonts/TTF/OpenSans-Regular.ttf",
"/usr/share/fonts/noto/NotoSans-Regular.ttf",
@@ -197,59 +120,25 @@ fn setup_fonts(ctx: &egui::Context) {
"/usr/share/fonts/exo-1/Exo-Medium.otf",
];
let mut _text_font_loaded = false;
for path in text_font_paths {
if let Ok(data) = std::fs::read(path) {
fonts.font_data.insert(
"system_text_font".to_owned(),
egui::FontData::from_owned(data).into(), // Sürüme göre .into() veya std::sync::Arc::new(...)
egui::FontData::from_owned(data),
);
// Proportional (Orantılı) fontun başına ekle
// Metin fontunu en başa (0. indeks) koyuyoruz ki varsayılan olarak bu kullanılsın
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.insert(0, "system_text_font".to_owned());
// Monospace için de bu fontu varsayılan olarak ekle (özel bir monospace bulunamadıysa)
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.insert(0, "system_text_font".to_owned());
_text_font_loaded = true;
break;
}
}
// Ek Güvence: Özel bir Monospace fontu varsa öncelikli olarak onu ara
let mono_font_paths = [
"/usr/share/fonts/TTF/Hack-Regular.ttf",
"/usr/share/fonts/TTF/DejaVuSansMono.ttf",
"/usr/share/fonts/noto/NotoSansMono-Regular.ttf",
];
for path in mono_font_paths {
if let Ok(data) = std::fs::read(path) {
fonts.font_data.insert(
"system_mono_font".to_owned(),
egui::FontData::from_owned(data).into(),
);
// Monospace ailesinin 0. indeksine yerleştir
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.insert(0, "system_mono_font".to_owned());
break;
}
}
// 2. ADIM: Color Emoji Fontunu Yedek (Fallback) Olarak EKLE
// 2. ADIM: Renkli Emoji Fontunu Ara ve "Fallback" (Yedek) Olarak Sona Ekle
// ISO ortamlarında Noto Color Emoji genellikle bu yollardan birinde olur
let emoji_font_paths = [
"/usr/share/fonts/noto/NotoColorEmoji.ttf",
];
@@ -258,44 +147,27 @@ fn setup_fonts(ctx: &egui::Context) {
if let Ok(data) = std::fs::read(path) {
fonts.font_data.insert(
"system_emoji_font".to_owned(),
egui::FontData::from_owned(data).into(),
egui::FontData::from_owned(data),
);
// Her iki aileye de SONA (push) ekle, böylece metinler bozulmaz
// ÖNEMLİ: Emojiyi listenin SONUNA (push ile) ekliyoruz.
// Böylece egui normal harfleri sistem fontuyla yazacak,
// harf sistem fontunda yoksa (yani emojiyse) gidip bu fonttan alacak.
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.push("system_emoji_font".to_owned());
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.push("system_emoji_font".to_owned()); // insert(0, ...) yerine push yapıldı!
.insert(0, "system_emoji_font".to_owned());
break;
}
}
// 3. ADIM: Remixicon İkon Fontunu Göm (PUA)
let remixicon_data = include_bytes!("../../assets/remixicon.ttf");
fonts.font_data.insert(
"remixicon".to_owned(),
egui::FontData::from_owned(remixicon_data.to_vec()).into(),
);
fonts
.families
.entry(egui::FontFamily::Proportional)
.or_default()
.push("remixicon".to_owned());
fonts
.families
.entry(egui::FontFamily::Monospace)
.or_default()
.push("remixicon".to_owned());
ctx.set_fonts(fonts);
}
@@ -303,7 +175,7 @@ fn setup_fonts(ctx: &egui::Context) {
fn build_visuals(is_dark: bool) -> Visuals {
let mut v = if is_dark { Visuals::dark() } else { Visuals::light() };
// Genel arka planlar
v.window_fill = c_bg_dark();
v.panel_fill = c_bg_panel();
@@ -315,7 +187,7 @@ fn build_visuals(is_dark: bool) -> Visuals {
v.override_text_color = None;
// ─── KENARLIKLAR (Calamares Uyumu için İnce İnce Yumuşatıldı) ───────────────────
v.widgets.noninteractive.bg_stroke = Stroke::new(1.0_f32, c_border());
v.widgets.noninteractive.bg_stroke = Stroke::new(1.0, c_border());
// ─── WIDGET DOLGULARI (Burası ComboBox ve Butonların Kaderini Belirliyor) ───────
v.widgets.noninteractive.bg_fill = c_bg_widget();
@@ -333,7 +205,7 @@ fn build_visuals(is_dark: bool) -> Visuals {
crate::ui::combobox_stil::apply_visuals(&mut v);
// ─── METİN RENKLERİ ────────────────────────────────────────────────────────────
v.widgets.noninteractive.fg_stroke = Stroke::new(1.0_f32, c_text());
v.widgets.noninteractive.fg_stroke = Stroke::new(1.0, c_text());
// ─── YUVARLAK KÖŞELER (Zaten 6.0'dı, Tam İstediğin Calamares Kıvamında) ─────────
v.widgets.noninteractive.rounding = Rounding::same(6.0);
@@ -343,13 +215,13 @@ fn build_visuals(is_dark: bool) -> Visuals {
// Seçim rengi
v.selection.bg_fill = c_accent_dim();
v.selection.stroke = Stroke::new(1.0_f32, c_accent());
v.selection.stroke = Stroke::new(1.0, c_accent());
// Hyperlink
v.hyperlink_color = c_accent();
// Pencere kenarlığı
v.window_stroke = Stroke::new(1.0_f32, c_border());
v.window_stroke = Stroke::new(1.0, c_border());
v.window_rounding = Rounding::same(8.0);
v.interact_cursor = Some(egui::CursorIcon::PointingHand);
@@ -359,14 +231,13 @@ fn build_visuals(is_dark: bool) -> Visuals {
fn build_style() -> egui::Style {
let mut style = egui::Style::default();
// Genel boşluk ve boyutlar
style.spacing.item_spacing = Vec2::new(8.0, 6.0);
style.spacing.button_padding = Vec2::new(12.0, 6.0);
style.spacing.indent = 16.0;
style.spacing.scroll.bar_width = 14.0;
// Metin boyutları
style.text_styles = [
(egui::TextStyle::Small, FontId::proportional(11.0)),
@@ -397,7 +268,7 @@ pub fn section_heading(ui: &mut egui::Ui, text: &str) {
let (rect, _) = ui.allocate_exact_size(Vec2::new(ui.available_width(), 1.0), egui::Sense::hover());
ui.painter().line_segment(
[rect.left_center(), rect.right_center()],
Stroke::new(1.0_f32, c_accent_dim()),
Stroke::new(1.0, c_accent_dim()),
);
ui.add_space(8.0);
}
@@ -406,16 +277,14 @@ pub fn section_heading(ui: &mut egui::Ui, text: &str) {
pub fn error_box(ui: &mut egui::Ui, msg: &str) {
egui::Frame::none()
.fill(Color32::from_rgba_unmultiplied(0xF4, 0x43, 0x36, 20))
.stroke(Stroke::new(1.0_f32, c_error()))
.stroke(Stroke::new(1.0, c_error()))
.rounding(Rounding::same(6.0))
.inner_margin(egui::Margin::same(10.0))
.show(ui, |ui| {
ui.horizontal(|ui| {
ui.label(
egui::RichText::new("\u{F4C6}")
.color(c_error())
.size(18.0) // SVG max_width/height yerine font boyutu
);
ui.add(egui::Image::new(egui::include_image!("../../assets/icons/fail-icon.svg"))
.max_width(18.0).max_height(18.0)
.tint(c_error()));
ui.colored_label(c_error(), msg);
});
});
@@ -425,7 +294,7 @@ pub fn error_box(ui: &mut egui::Ui, msg: &str) {
pub fn warning_box(ui: &mut egui::Ui, msg: &str) {
egui::Frame::none()
.fill(Color32::from_rgba_unmultiplied(0xFF, 0x98, 0x00, 20))
.stroke(Stroke::new(1.0_f32, c_warning()))
.stroke(Stroke::new(1.0, c_warning()))
.rounding(Rounding::same(6.0))
.inner_margin(egui::Margin::same(10.0))
.show(ui, |ui| {
@@ -442,7 +311,7 @@ pub fn warning_box(ui: &mut egui::Ui, msg: &str) {
pub fn success_box(ui: &mut egui::Ui, msg: &str) {
egui::Frame::none()
.fill(Color32::from_rgba_unmultiplied(0x4C, 0xAF, 0x50, 20))
.stroke(Stroke::new(1.0_f32, c_success()))
.stroke(Stroke::new(1.0, c_success()))
.rounding(Rounding::same(6.0))
.inner_margin(egui::Margin::same(10.0))
.show(ui, |ui| {