diff --git a/.gitignore b/.gitignore index 7957dd22..4acca450 100644 --- a/.gitignore +++ b/.gitignore @@ -94,3 +94,4 @@ local/recipes/kde/kwin/** Packages/*.pkgar local/cache/pkgar/ local/patches/base/redox.patch +local/reference/ diff --git a/AGENTS.md b/AGENTS.md index 2c0dad19..0a085e7b 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -40,7 +40,8 @@ redox-master/ │ ├── Assets/ # Branding assets (icon, loading background) │ ├── firmware/ # AMD GPU firmware blobs (fetched, not committed) │ ├── scripts/ # Build/deploy scripts (fetch-firmware.sh, build-redbear.sh) -│ └── docs/ # Red Bear integration docs (AMD roadmap, Wi-Fi/Bluetooth plans, status notes) +│ ├── docs/ # Red Bear integration docs (AMD roadmap, Wi-Fi/Bluetooth plans, status notes) +│ └── reference/ # External reference sources (gitignored, never deleted, always kept) ├── prefix/ # Cross-compiler toolchain (Clang/LLVM for x86_64-unknown-redox) ├── build/ # Build outputs, logs, fstools, per-arch directories ├── repo/ # Package manifests and PKGAR artifacts per architecture @@ -201,6 +202,22 @@ See `local/docs/BUILD-SYSTEM-HARDENING-PLAN.md` for the full plan. - **DO NOT** remove patches from `recipe.toml` to fix build failures — rebase the patch instead (see `local/docs/PATCH-GOVERNANCE.md`) - **DO NOT** remove BINS entries to fix build failures — fix the source or use EXISTING_BINS filtering +## LINUX REFERENCE SOURCE POLICY + +`local/reference/linux-7.0/` (or later) contains a full Linux kernel source tree for +cross-referencing driver behavior, hardware initialization sequences, register definitions, +and error handling patterns. + +**Rules:** +- **NEVER delete** the reference tree. It is gitignored but permanent. +- **ALWAYS consult** the Linux source when building or fixing drivers, daemons, or any subsystem + that has a Linux counterpart (audio/HDA, GPU/DRM, networking, USB, PCI, ACPI, input, storage, + filesystems, scheduler, memory management). +- **Update** the reference tree when a new stable Linux version is needed: + `git -C local/reference/linux-7.0 fetch --depth=1 origin tag:v7.x --force` +- The reference tree is read-only for consultation purposes. No modifications. +- Location: `local/reference/` is gitignored. It survives `make clean` and `make distclean`. + ## DURABILITY POLICY Every change to an upstream-owned source tree (anything under `recipes/*/source/`) **must** be diff --git a/local/docs/CHANGELOG-DRIVER-IMPROVEMENT-PLAN.md b/local/docs/CHANGELOG-DRIVER-IMPROVEMENT-PLAN.md new file mode 100644 index 00000000..57aca046 --- /dev/null +++ b/local/docs/CHANGELOG-DRIVER-IMPROVEMENT-PLAN.md @@ -0,0 +1,626 @@ +# Red Bear OS — Driver & Hardware Improvement Plan + +**Date**: 2026-05-04 +**Status**: In Progress — Phase 0 ✅, Phase 1 ✅, Phase 2 ✅, Phase 3 ✅, Phase 4 partial, Phase 5 ✅, Addendum A added +**Authority**: This plan defines improvements for subsystems NOT covered by existing plans. For ACPI, USB, IRQ/PCI, GPU/DRM, Bluetooth, and Wi-Fi, defer to their respective plans. This plan fills the storage, network, and audio gaps and adds cross-cutting concerns. + +**Source of truth**: Linux kernel 7.0 (`local/reference/linux-7.0/`). When in doubt, Linux behavior is authoritative. Every task includes the specific Linux source file and function to reference. + +--- + +## Relationship to Existing Plans + +This plan is **subordinate** to the following plans for their respective subsystems. Tasks here do not duplicate, override, or conflict with them: + +| Plan Document | Subsystem | Status | +|---------------|-----------|--------| +| `ACPI-IMPROVEMENT-PLAN.md` | ACPI sleep, thermal, EC, power states | Active | +| `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` | PCI IRQ, MSI-X, IOMMU, controllers | Active | +| `USB-IMPLEMENTATION-PLAN.md` | xHCI, EHCI, device lifecycle | Active | +| `DRM-MODERNIZATION-EXECUTION-PLAN.md` | GPU/DRM display, KMS, Mesa | Active | +| `BLUETOOTH-IMPLEMENTATION-PLAN.md` | BT host/controller | Active | +| `WIFI-IMPLEMENTATION-PLAN.md` | Wi-Fi control plane | Active | +| `CONSOLE-TO-KDE-DESKTOP-PLAN.md` | Desktop/KDE path | Active | + +**New coverage by this plan**: Storage drivers (AHCI, NVMe), Network drivers (e1000, r8168), Audio drivers (HDA, AC97), Input completeness (PS/2, HID), and cross-cutting driver quality (error handling, logging, lifecycle). + +--- + +## Validation States + +All tasks use these validation levels, consistent with existing plans: + +- **builds** — compiles without error against the target toolchain +- **enumerates** — discovers hardware and reports it through scheme interfaces +- **usable** — works in a bounded real scenario (QEMU or bare metal) +- **validated** — passes explicit acceptance tests with captured evidence +- **hardware-validated** — proven on real bare metal, not just QEMU + +--- + +## Phase 0: Cross-Cutting Driver Quality (Weeks 1-2) + +These improvements apply to ALL drivers and must be done first to establish the quality baseline for subsequent phases. + +### T0.1: Driver Error Handling Audit + +**Problem**: Many drivers use `unwrap()`/`expect()` on hardware operations (I/O port reads, MMIO, PCI config space). Hardware failures produce panics instead of graceful degradation. + +**Task**: Audit all drivers in `recipes/core/base/source/drivers/` and `local/recipes/drivers/` for: +1. `unwrap()`/`expect()` on hardware I/O — replace with proper `Result` propagation +2. Missing error logging for hardware failures — add `log::error!()` before error returns +3. Infinite retry loops without backoff — add bounded retry with exponential backoff + +**Linux reference**: `drivers/ata/libata-eh.c` — `ata_eh_link_autopsy()` for error classification pattern. Linux distinguishes transient errors (retry), permanent errors (fail), and protocol errors (reset). + +**File paths**: +- `recipes/core/base/source/drivers/storage/ahcid/src/main.rs` +- `recipes/core/base/source/drivers/net/e1000d/src/device.rs` +- `recipes/core/base/source/drivers/net/rtl8168d/src/device.rs` +- `recipes/core/base/source/drivers/audio/ihdad/src/main.rs` +- `recipes/core/base/source/drivers/audio/ac97d/src/device.rs` +- `local/recipes/drivers/ehcid/source/src/`, `ohcid/`, `uhcid/` + +**Acceptance**: `grep -r 'unwrap()' recipes/core/base/source/drivers/` returns zero matches for hardware I/O paths. Each `unwrap()` removal includes a `log::error!()` before the error return. + +### T0.2: Driver Logging Standardization + +**Problem**: Drivers use inconsistent logging — some use `println!`, some `eprintln!`, some `log::info!`, some no logging at all. Makes debugging hardware issues on bare metal nearly impossible. + +**Task**: Standardize all drivers to use the `log` crate with logd integration: +1. Replace `println!`/`eprintln!` with `log::info!`/`log::warn!`/`log::error!` +2. Log every hardware initialization step (PCI probe, BAR mapping, IRQ registration) +3. Log every error with the hardware register values that caused it +4. Add `log::debug!` for register read/write traces (behind a feature flag or compile-time config) + +**Linux reference**: `drivers/net/ethernet/intel/e1000e/netdev.c` — `e_err()` macro with per-driver message prefix. Linux uses `netdev_err()`, `netdev_warn()`, `netdev_info()` with device context. + +**Acceptance**: Every driver produces at minimum: one `info!` on start, one `info!` on successful init, one `error!` per failure path with register dump. Verified by booting in QEMU and checking serial output. + +### T0.3: Driver Lifecycle Documentation + +**Problem**: No documentation exists for driver initialization sequences, required resources, or expected behavior. New contributors cannot understand or debug drivers. + +**Task**: For each driver category (storage, network, audio), create a brief `DRIVERS.md` in the driver directory documenting: +1. Hardware initialization sequence (PCI probe → BAR mapping → device reset → capability enumeration → ready) +2. Required kernel schemes (scheme:memory, scheme:irq, scheme:pci) +3. Known hardware quirks +4. Linux source file(s) to cross-reference + +**Acceptance**: `DRIVERS.md` exists in `recipes/core/base/source/drivers/storage/`, `drivers/net/`, `drivers/audio/` with the above sections. + +--- + +## Phase 1: Storage Drivers (Weeks 2-6) + +### T1.1: AHCI NCQ Support + +**Problem**: ahcid is 109 lines, only basic PIO/DMA read/write. No NCQ. SSD throughput is 3-5x slower than possible. + +**Linux reference**: `drivers/ata/libata-sata.c:35` — `sata_fsl_host_intr()` with NCQ error handling. `drivers/ata/ahci.c:1423` — `ahci_qc_prep()` for FIS/command table setup. + +**Implementation**: +1. Add command queue structure to `ahcid/src/ahci/` — track up to 32 pending commands per port +2. Implement `ahci_qc_issue()` modeled on Linux `ata_qc_issue()`: + - Allocate command slot from device command table + - Fill command FIS (Frame Information Structure) with READ/WRITE FPDMA command + - Set PRDT (Physical Region Descriptor Table) for DMA scatter-gather + - Issue command via PxCI (Port Command Issue) register write +3. Implement `ahci_port_intr()` modeled on Linux `ahci_port_intr()`: + - Read PxIS (Port Interrupt Status) + - Handle D2H Register FIS (command completion) + - Handle SDB FIS (NCQ completion with per-tag status) + - Handle PIO Setup FIS (for ATAPI) + - Handle Device-to-Host FIS errors +4. Add per-tag completion tracking using `PxSACT` (SActive) register + +**Files to modify/create**: +- `recipes/core/base/source/drivers/storage/ahcid/src/main.rs` — NCQ enable in `ahci_init()` +- `recipes/core/base/source/drivers/storage/ahcid/src/ahci/` — new `ncq.rs`, `fis.rs` + +**Acceptance**: +- `fio` random read test on SSD shows ≥3x improvement over current PIO-only +- NCQ depth 32 verified via `PxSACT` register dump in debug output +- QEMU with `-device ahci,id=ahci` and `-drive file=...,if=none,id=drive0` produces NCQ completions + +### T1.2: AHCI Power Management + +**Problem**: No power management. Laptops drain battery with disk constantly powered. + +**Linux reference**: `drivers/ata/libata-eh.c:3682` — `ata_eh_handle_port_suspend()`. `drivers/ata/ahci.c` — `ahci_set_lpm()` for Partial/Slumber link power management. + +**Implementation**: +1. Add link power management to `ahci_init()`: + - Set PxCMD.ICC (Interface Communication Control) to Slumber after idle + - Set PxSCTL.DET to disable PHY when port is idle + - Restore on new command arrival +2. Add ALPM (Aggressive Link Power Management): + - Set AHCI_HOST_CAP2.SDS (Supports Device Sleep) if available + - Enable HIPM (Host Initiated Power Management) and DIPM (Device Initiated) +3. Add device sleep (DevSlp) for SATA 3.2+ devices + +**Acceptance**: After 5 seconds of idle, PxSSTS.DET reports 0x4 (PHY offline). New command wakes the link within 100ms. Verified on bare metal with SATA SSD. + +### T1.3: AHCI TRIM/Discard + +**Problem**: SSDs degrade over time without TRIM. Write amplification increases. + +**Linux reference**: `drivers/ata/libata-scsi.c` — `ata_scsi_unmap_xlat()` maps SCSI UNMAP to ATA DATA SET MANAGEMENT with TRIM bit. + +**Implementation**: +1. Add TRIM command support using ATA DATA SET MANAGEMENT (opcode 0x06) with TRIM bit +2. Implement range list construction (LBA + sector count per entry, up to 64 entries) +3. Wire into filesystem TRIM/discard path via scheme discard operation + +**Acceptance**: `fstrim /` (or redoxfs equivalent) issues DATA SET MANAGEMENT commands visible in AHCI debug output. SSD wear leveling counters show improvement after TRIM. + +### T1.4: NVMe Multiple Queue Support + +**Problem**: NVMe driver uses single I/O queue. NVMe supports up to 64K queues for parallelism. + +**Linux reference**: `drivers/nvme/host/pci.c` — `nvme_reset_work()` for controller initialization with queue count negotiation. + +**Implementation**: +1. Implement `nvme_create_io_queues()` modeled on Linux: + - Read controller capabilities for maximum queue count + - Create one admin submission + completion queue pair + - Create N I/O submission + completion queue pairs + - Configure interrupt vectors for MSI-X per-queue +2. Implement round-robin queue selection for I/O submission + +**Acceptance**: NVMe device in QEMU reports ≥4 I/O queues. `fio` shows throughput scaling with queue count. + +--- + +## Phase 2: Network Drivers (Weeks 4-8) + +### T2.1: e1000 Interrupt Moderation + Checksum Offload + +**Problem**: e1000d is 458 lines with no hardware offloads. Every packet triggers an interrupt. Throughput is limited by interrupt rate (~10K pps max). + +**Linux reference**: `drivers/net/ethernet/intel/e1000e/netdev.c:4200` — `e1000_configure_itr()`. `e1000e/netdev.c` — `e1000_tx_csum()`, `e1000_rx_checksum()`. + +**Implementation**: +1. **Interrupt moderation** (ITR): + - Program E1000_ITR register with dynamic moderation + - Implement `e1000_update_itr()` modeled on Linux: increase ITR under high load, decrease under low load + - Target: reduce interrupts from 10K/s to 1K/s under full load +2. **TX checksum offload**: + - Set E1000_TXD_CMD_IPCSS/TUCMD_IPCSS for IP header checksum + - Set E1000_TXD_CMD_TCP/UDP for TCP/UDP pseudo-header checksum + - Set context descriptor for checksum parameters +3. **RX checksum offload**: + - Parse E1000_RXD_STAT_IPCS/TCPCS status bits + - Pass checksum status to netstack + +**Files to modify**: +- `recipes/core/base/source/drivers/net/e1000d/src/device.rs` — add ITR, checksum methods +- `recipes/core/base/source/drivers/net/e1000d/src/main.rs` — wire into TX/RX paths + +**Acceptance**: `iperf3` TCP throughput ≥5x improvement. Interrupt rate drops from ~10K/s to ≤2K/s under load. Wireshark capture shows valid checksums on TX packets. + +### T2.2: e1000 TSO/GSO + +**Problem**: TCP segmentation is done in software. Large sends require per-packet overhead. + +**Linux reference**: `drivers/net/ethernet/intel/e1000e/netdev.c:5305` — `e1000_tso()`. + +**Implementation**: +1. Implement `e1000_tso()` modeled on Linux: + - Parse GSO descriptor from netstack + - Set E1000_TXD_CMD_TSE (TCP Segmentation Enable) + - Set MSS (Maximum Segment Size) in context descriptor + - Set header length in context descriptor + - Hardware will segment one large buffer into MSS-sized packets +2. Implement `e1000_tx_csum()` for combined TSO + checksum offload + +**Acceptance**: TCP send of 64KB buffer produces hardware-segmented packets (verified via virtio-net capture on host side). Throughput for large sends ≥2x improvement. + +### T2.3: r8169 PHY Configuration + +**Problem**: rtl8168d has no per-chip PHY initialization. Works on QEMU's default r8169 but fails on many real chips. + +**Linux reference**: `drivers/net/ethernet/realtek/r8169_phy_config.c` (1,354 lines of per-chip init sequences). + +**Implementation**: +1. Identify chip version from MAC0-MAC4 registers (Linux: `rtl8169_get_mac_version()`) +2. Add PHY init sequences for common chip versions: + - RTL_GIGA_MAC_VER_34 (RTL8168EP/8111EP) + - RTL_GIGA_MAC_VER_44 (RTL8168FP/8111FP) + - RTL_GIGA_MAC_VER_51 (RTL8168H/8111H) +3. Implement MDIO register read/write for PHY access +4. Add PHY status polling for link detection + +**Files to modify**: +- `recipes/core/base/source/drivers/net/rtl8168d/src/device.rs` — chip detection, PHY init +- `recipes/core/base/source/drivers/net/rtl8168d/src/main.rs` — init sequence + +**Acceptance**: RTL8168 NIC in real hardware enumerates, links up, and passes `ping`. Multiple chip versions tested. + +### T2.4: Jumbo Frame Support (e1000 + r8169) + +**Problem**: MTU limited to 1500. Jumbo frames (9000 bytes) reduce per-packet overhead for bulk transfers. + +**Linux reference**: `e1000e/netdev.c` — `e1000_change_mtu()`. `r8169_main.c:4352` — `rtl_jumbo_config()`. + +**Implementation**: +1. Configure RX buffer size for jumbo frames (up to 9KB) +2. Set MAX_FRAME_SIZE register +3. Update TX descriptor buffer size +4. Expose MTU configuration through scheme interface + +**Acceptance**: `ifconfig eth0 mtu 9000` succeeds. `iperf3` with 9KB MTU shows reduced CPU usage per Gbps. + +--- + +## Phase 3: Audio Drivers (Weeks 6-10) + +### T3.1: HDA Codec Auto-Detection + +**Problem**: ihdad (143 lines) has no codec detection. Audio works on zero real machines. + +**Linux reference**: `sound/hda/hda_codec.c` — `snd_hda_codec_new()` for codec discovery. `sound/hda/hda_generic.c` for generic codec parser. + +**Implementation**: +1. Implement HDA controller initialization: + - Read GCAP (Global Capabilities) register for stream/IRQ info + - Reset controller via GCTL.CRST + - Set CORB/RIRB (Command/Response Ring Buffers) for codec communication +2. Implement codec discovery: + - Read STATETS register for codec presence bitmap + - For each present codec, send GET_PARAMETER verb to read: + - Vendor/Device ID (F00) + - Subsystem ID (F20) + - Revision ID (F02) + - Node count (F04) + - Function group type (F05) +3. Implement codec parsing: + - Walk widget tree starting from AFG (Audio Function Group) node + - Parse each widget's parameters (amp capabilities, connection list, pin config) + - Build internal topology representation +4. Add codec table for common codecs: + - Realtek ALC887/ALC888/ALC892 (most common desktop) + - Realtek ALC269/ALC282/ALC283 (most common laptop) + - Conexant CX20561/CX20585 + - IDT 92HD73C1/92HD81B1C5 + +**Files to modify/create**: +- `recipes/core/base/source/drivers/audio/ihdad/src/main.rs` — controller init +- `recipes/core/base/source/drivers/audio/ihdad/src/hda/` — new `codec.rs`, `widget.rs`, `codecs/` +- `recipes/core/base/source/drivers/audio/ihdad/src/hda/registers.rs` — register definitions + +**Acceptance**: Real hardware with Intel HDA controller enumerates codecs. `lspci` shows HD Audio device with driver attached. Codec dump shows vendor/device IDs matching known codecs. + +### T3.2: HDA Mixer Controls + Jack Detection + +**Problem**: No volume control, no muting, no jack detection. Audio output is fixed-volume or silent. + +**Linux reference**: `sound/hda/hda_generic.c` — `create_mute_volume_ctl()`. `sound/hda/hda_jack.c` — `snd_hda_jack_detect()`. + +**Implementation**: +1. Add mixer controls for each output path: + - Volume control (AMP-OUT mute + gain on pin widget) + - Capture control (AMP-IN mute + gain on ADC widget) + - Master volume (combined output volume) +2. Implement jack detection: + - Enable unsolicited response for jack-sense pin widgets + - Handle unsolicited response in CORB/RIRB interrupt + - Report jack state (plugged/unplugged) via scheme +3. Wire mixer controls to audiod for system-wide volume management + +**Files to modify**: +- `recipes/core/base/source/drivers/audio/ihdad/src/hda/codec.rs` — mixer controls +- `recipes/core/base/source/drivers/audio/ihdad/src/hda/jack.rs` — jack detection (new) +- `recipes/core/base/source/drivers/audio/audiod/src/scheme.rs` — volume interface + +**Acceptance**: Volume control changes audible output level. Plugging/unplugging headphones triggers jack event (visible in debug output). Headphone and speaker paths are independent. + +### T3.3: HDA Stream Setup and PCM Playback + +**Problem**: No actual PCM audio output. HDA hardware configured but no audio data flows. + +**Linux reference**: `sound/hda/hda_controller.c` — `azx_pcm_open()` / `azx_pcm_prepare()` / `azx_pcm_trigger()`. + +**Implementation**: +1. Implement stream (PCM) management: + - Allocate stream descriptor from controller (SD0-SDn) + - Configure stream format (sample rate, bits, channels) + - Set BDL (Buffer Descriptor List) for DMA + - Set stream position in buffer (LPIB register) +2. Implement PCM playback path: + - `pcm_open(format)` — allocate stream, configure format + - `pcm_write(data)` — write audio samples to DMA buffer + - `pcm_start()` — set RUN bit in stream control + - `pcm_stop()` — clear RUN bit +3. Implement CORB/RIRB interrupt handling for unsolicited responses +4. Implement stream interrupt handling for buffer completion (BCIS) + +**Files to modify**: +- `recipes/core/base/source/drivers/audio/ihdad/src/hda/stream.rs` — stream management (new) +- `recipes/core/base/source/drivers/audio/ihdad/src/hda/dma.rs` — BDL setup (new) +- `recipes/core/base/source/drivers/audio/audiod/src/` — PCM routing + +**Acceptance**: `aplay` (or redox equivalent) plays a WAV file and produces audible output. `parec` captures from microphone. Loopback (output → input) works without distortion. + +### T3.4: AC97 Multiple Codec + Mixer Support + +**Problem**: ac97d supports only single codec at fixed configuration. No volume/mute. + +**Linux reference**: `sound/pci/ac97/ac97_codec.c` (3,134 lines) — multi-codec architecture. + +**Implementation**: +1. Add codec slot detection (AC97 supports up to 4 codecs on one controller) +2. Add mixer register read/write for volume/mute +3. Add record source selection + +**Acceptance**: Desktop with AC97 audio codec produces audible output with adjustable volume. + +--- + +## Phase 4: Input Completeness (Weeks 3-5) + +### T4.1: PS/2 i8042 Controller Reset + +**Problem**: ps2d assumes controller is ready. Real hardware may need reset sequence. + +**Linux reference**: `drivers/input/serio/i8042.c:522` — `i8042_controller_check()`. + +**Implementation**: +1. Add controller self-test: Write 0xAA to command register, expect 0x55 response +2. Add controller initialization: disable devices, flush buffer, enable +3. Add AUX (mouse) port detection +4. Add timeout handling for missing ACK from controller + +**Files to modify**: +- `recipes/core/base/source/drivers/input/ps2d/src/controller.rs` + +**Acceptance**: PS/2 keyboard and mouse work on real hardware after cold boot. No "LED command ACK timeout" warnings. + +### T4.2: Touchpad Protocol Detection + +**Problem**: USB HID touchpads work as basic mice. No multi-touch, no gestures. + +**Linux reference**: `drivers/input/mouse/synaptics.c` for Synaptics protocol. `drivers/input/mouse/alps.c` for ALPS. + +**Implementation**: +1. Add PS/2 touchpad protocol detection for Synaptics/ALPS/Elantech +2. Parse multi-touch data from HID digitizer reports +3. Expose gesture events through evdevd scheme + +**Acceptance**: Laptop touchpad supports two-finger scroll. Multi-touch coordinates reported correctly. + +--- + +## Phase 5: Validation & Documentation (Weeks 1-12, parallel) + +### T5.1: Per-Driver Test Harnesses + +**Task**: Create QEMU-based test scripts for each driver category: +- `local/scripts/test-storage-qemu.sh` — boots with virtio-blk + AHCI, runs fio +- `local/scripts/test-network-qemu.sh` — boots with e1000 + r8169, runs iperf3 +- `local/scripts/test-audio-qemu.sh` — boots with HDA + AC97, plays test tone + +**Acceptance**: Each script exits 0 on success, produces captured serial output with test results. + +### T5.2: Hardware Validation Matrix + +**Task**: Create `local/docs/HARDWARE-VALIDATION-MATRIX.md` documenting tested hardware configurations: +- CPU/chipset combinations tested +- Storage controllers (AHCI, NVMe) tested +- Network chips (e1000, r8169 variants) tested +- Audio codecs (HDA, AC97) tested +- Known-broken configurations + +**Acceptance**: Matrix has at least one verified entry per driver category on real hardware. + +--- + +## Execution Order & Dependencies + +``` +Phase 0 (Cross-cutting) ─────────────────────────────────────────────┐ + T0.1 Error handling T0.2 Logging T0.3 Documentation │ + │ │ + ├── Phase 1 (Storage) ─────────────────────────────────────────┐ │ + │ T1.1 AHCI NCQ ──► T1.3 TRIM ──► T1.2 PM ──► T1.4 NVMe │ │ + │ │ │ + ├── Phase 2 (Network) ──────────────────────────────────────┐ │ │ + │ T2.1 ITR+Checksum ──► T2.2 TSO ──► T2.3 PHY ──► T2.4 │ │ │ + │ │ │ │ + ├── Phase 3 (Audio) ────────────────────────────────────┐ │ │ │ + │ T3.1 CodecDetect ──► T3.3 Stream ──► T3.2 Mixer │ │ │ │ + │ T3.4 AC97 (parallel) │ │ │ │ + │ │ │ │ │ + └── Phase 4 (Input) ───────────────────────────────┐ │ │ │ │ + T4.1 PS/2 reset ──► T4.2 Touchpad │ │ │ │ │ + │ │ │ │ │ + Phase 5 (Validation) ◄───────────────────────────────┴─────┴────┴───┴──┘ + T5.1 Test harnesses T5.2 Hardware matrix +``` + +**Phase 0 is prerequisite for all other phases.** +**Phases 1-4 are independent of each other and can run in parallel.** +**Phase 5 runs concurrently with all phases, finalizing as each completes.** + +## Timeline + +| Phase | Tasks | Duration | Cumulative | +|-------|-------|----------|------------| +| Phase 0 | T0.1, T0.2, T0.3 | Weeks 1-2 | Week 2 | +| Phase 1 | T1.1, T1.2, T1.3, T1.4 | Weeks 2-6 | Week 6 | +| Phase 2 | T2.1, T2.2, T2.3, T2.4 | Weeks 4-8 | Week 8 | +| Phase 3 | T3.1, T3.2, T3.3, T3.4 | Weeks 6-10 | Week 10 | +| Phase 4 | T4.1, T4.2 | Weeks 3-5 | Week 5 | +| Phase 5 | T5.1, T5.2 | Weeks 1-12 (parallel) | Week 12 | + +**Total**: 12 weeks with 2 developers working in parallel (Phase 1 and Phase 3 on separate tracks). + +--- + +## Linux Reference Map + +Every task references specific Linux source. Here is the complete map: + +| Task | Primary Reference | File Size | Function Focus | +|------|-------------------|-----------|----------------| +| T1.1 (NCQ) | `drivers/ata/libata-sata.c` | 1,365 lines | `ata_qc_issue()`, FIS construction | +| T1.2 (AHCI PM) | `drivers/ata/libata-eh.c` | 3,915 lines | `ata_eh_handle_port_suspend()` | +| T1.3 (TRIM) | `drivers/ata/libata-scsi.c` | 4,504 lines | `ata_scsi_unmap_xlat()` | +| T1.4 (NVMe) | `drivers/nvme/host/pci.c` | 3,146 lines | `nvme_reset_work()`, queue creation | +| T2.1 (ITR) | `e1000e/netdev.c` | 7,240 lines | `e1000_configure_itr()`, checksum | +| T2.2 (TSO) | `e1000e/netdev.c` | 7,240 lines | `e1000_tso()` | +| T2.3 (PHY) | `r8169_phy_config.c` | 1,354 lines | per-chip PHY init sequences | +| T3.1 (Codec) | `sound/hda/hda_codec.c` | 5,598 lines | `snd_hda_codec_new()`, widget parsing | +| T3.2 (Mixer) | `sound/hda/hda_generic.c` | 5,982 lines | `create_mute_volume_ctl()` | +| T3.3 (Stream) | `sound/hda/hda_controller.c` | 1,900 lines | `azx_pcm_open/prepare/trigger()` | +| T3.4 (AC97) | `sound/pci/ac97/ac97_codec.c` | 3,134 lines | multi-codec, mixer regs | +| T4.1 (PS/2) | `drivers/input/serio/i8042.c` | 1,254 lines | `i8042_controller_check()` | +| T4.2 (Touchpad) | `drivers/input/mouse/synaptics.c` | 1,707 lines | protocol detection | + +--- + +## Scope Boundaries + +**In scope**: +- Storage driver enhancements (AHCI NCQ, PM, TRIM; NVMe queues) +- Network driver enhancements (e1000 offload, r8169 PHY, jumbo frames) +- Audio driver enhancements (HDA codec, mixer, streams; AC97 multi-codec) +- Input driver enhancements (PS/2 reset, touchpad protocols) +- Cross-cutting driver quality (error handling, logging, documentation) + +**Out of scope** (covered by existing plans): +- ACPI S3/S4 sleep, thermal, EC — see `ACPI-IMPROVEMENT-PLAN.md` +- PCI IRQ, MSI-X depth, IOMMU — see `IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md` +- USB controller completeness, device lifecycle — see `USB-IMPLEMENTATION-PLAN.md` +- GPU/DRM display, KMS, Mesa — see `DRM-MODERNIZATION-EXECUTION-PLAN.md` +- Bluetooth — see `BLUETOOTH-IMPLEMENTATION-PLAN.md` +- Wi-Fi — see `WIFI-IMPLEMENTATION-PLAN.md` +- Desktop/KDE — see `CONSOLE-TO-KDE-DESKTOP-PLAN.md` + +--- + +## Addendum A: Kernel Substrate Audit (2026-05-04 deep re-assessment) + +### A.1 CPU / SMP / Timer Initialization + +**Red Bear**: Kernel arch/x86_64 (502 lines) + arch/x86_shared + time.rs +**Linux**: `arch/x86/kernel/smpboot.c` (1,511) + `arch/x86/kernel/apic/apic.c` (2,694) + `arch/x86/kernel/tsc.c` (1,612) + `kernel/time/tick-common.c` (595) = 6,412 lines (subset) + +**What Red Bear has**: +- Basic x86_64 boot (GDT, IDT, page tables) +- x2APIC/SMP detected from MADT +- HPET timer + +**What Linux has that Red Bear is missing**: +- ❌ BSP/AP handoff protocol — Linux: `smpboot.c:895` `do_boot_cpu()` +- ❌ CPU hotplug (online/offline) — Linux: `smpboot.c:1312` `cpu_up()` / `cpu_down()` +- ❌ TSC calibration and synchronization — Linux: `tsc.c:1186` `check_tsc_sync_source()` +- ❌ APIC timer calibration and per-CPU timers — Linux: `apic.c:294` `calibrate_APIC_clock()` +- ❌ Interrupt affinity and vector allocation — Linux: `kernel/irq/manage.c` (2,803 lines) +- ❌ IPI (Inter-Processor Interrupt) routing — Linux: `apic/ipi.c` +- ❌ CPU idle states (C-states) — Linux: `arch/x86/kernel/acpi/cstate.c` +- ❌ Clock source rating and switching — Linux: `kernel/time/clocksource.c` + +**Priority**: SMP bring-up stability and TSC sync are critical for multi-core correctness. Without APIC timer calibration, scheduler tick is unreliable. + +### A.2 DMA / Memory / IOMMU Substrate + +**Red Bear**: kernel memory/mod.rs (1,266 lines) + iommu daemon (4,411 lines) +**Linux**: `kernel/dma/mapping.c` (1,016) + `drivers/iommu/` (~30K) + `mm/` subsystem + +**What Red Bear has**: +- Physical memory mapping via scheme:memory +- Basic IOMMU daemon (4,411 lines — substantial, AMD-Vi + Intel VT-d) +- Page table management in iommu daemon + +**What Linux has that Red Bear is missing**: +- ❌ Coherent DMA API — Linux: `kernel/dma/mapping.c` `dma_alloc_coherent()` +- ❌ Streaming DMA API — Linux: `kernel/dma/mapping.c` `dma_map_single()` +- ❌ Scatter-gather DMA — Linux: `lib/scatterlist.c` +- ❌ DMA pool/zone management +- ❌ SWIOTLB bounce buffering — Linux: `kernel/dma/swiotlb.c` +- ❌ IOMMU DMA remapping per-device — the iommu daemon exists but Linux handles this in-kernel with `iommu_dma_ops` +- ❌ DMA debug and error injection — Linux: `kernel/dma/debug.c` + +**Priority**: DMA API is prerequisite for any driver doing scatter-gather. Without coherent DMA, drivers must manually manage cache coherency. + +### A.3 Virtio Completeness + +**Red Bear**: virtio-core (1,545 lines) + virtio-blkd + virtio-netd + virtio-gpud +**Linux**: `drivers/virtio/virtio.c` (730) + `virtio_ring.c` (3,940) + `virtio_pci_modern.c` (1,301) + blk/net/gpu drivers (14,957 total) + +**What Red Bear has**: +- Basic virtio PCI transport (legacy) +- Split virtqueue with basic ring management +- virtio-blk, virtio-net, virtio-gpu drivers + +**What Linux has that Red Bear is missing**: +- ❌ **Virtio 1.0 modern PCI transport** — Linux: `virtio_pci_modern.c` (1,301 lines). Red Bear only uses legacy. +- ❌ **Packed virtqueue** (Virtio 1.1) — Linux: `virtio_ring.c` supports both split and packed +- ❌ **Multiqueue support** — Linux: virtio-net supports up to 16 TX/RX queue pairs via MSI-X +- ❌ **Virtio feature negotiation** — Red Bear hardcodes features; Linux does dynamic negotiation +- ❌ **Device reset protocol** — Linux: `virtio.c:237` `virtio_reset_device()` +- ❌ **Virtio-MMIO transport** (for ARM/RISC-V VMs) +- ❌ **Virtio-balloon** (memory ballooning) + +**Priority**: Modern PCI transport is required for QEMU machine types `q35` and newer. Packed virtqueues improve throughput. Multiqueue is critical for network performance. + +### A.4 CPU Frequency / Thermal / Power + +**Red Bear**: cpufreqd (176 lines — real implementation with governors), thermald (837 lines), hwrngd (534 lines), redbear-upower, redbear-acmd, redbear-ecmd +**Linux**: `drivers/cpufreq/cpufreq.c` (3,081) + `drivers/thermal/thermal_core.c` (1,956) + `drivers/char/hw_random/core.c` (739) + +**cpufreqd status**: 176 lines with ondemand/performance/powersave governors, MSR-based P-state control via IA32_PERF_CTL, and CPU load measurement via `/scheme/sys`. Still missing vs Linux: +- ❌ Governor framework (performance, powersave, ondemand, schedutil) +- ❌ ACPI P-state (_PSS) integration +- ❌ Intel P-state / HWP driver +- ❌ AMD CPPC driver + +**thermald status**: 837 lines — basic thermal monitoring exists but missing: +- ❌ Thermal zone trip points (passive/active/critical) +- ❌ Cooling device registration +- ❌ Fan speed control via ACPI + +**hwrngd status**: 534 lines — reasonable random number daemon. Missing: +- ❌ Entropy estimation per FIPS 140-2 +- ❌ Multiple entropy source mixing (CPU jitter, TPM, RDRAND) +- ❌ `/dev/hwrng` interface + +**Priority**: cpufreqd has basic governor support but still needs ACPI P-state integration, Intel HWP, and AMD CPPC for full functionality. + +### A.5 Block Layer / Filesystem Integration + +**Red Bear**: No dedicated block layer — each storage driver handles I/O directly via DiskScheme +**Linux**: `block/blk-mq.c` (5,309) + `block/blk-flush.c` (540) + `block/genhd.c` + `block/elevator.c` + +**What Linux has that Red Bear is missing**: +- ❌ Multi-queue block I/O — Linux: `blk-mq.c` — per-CPU queues + tag sets +- ❌ I/O scheduling (mq-deadline, kyber, bfq) — Linux: `block/mq-deadline.c` +- ❌ Flush/FUA semantics — Linux: `block/blk-flush.c` +- ❌ I/O merging and sorting +- ❌ Request timeout and retry — Linux: `block/blk-mq.c` `blk_mq_check_expired()` +- ❌ Block device partitioning (MBR/GPT handled by partitionlib library) +- ❌ Queue depth management and back-pressure + +**Red Bear storage drivers** (nvmed 1,318 lines; usbscsid 1,622 lines; ided 773 lines) all implement their own I/O dispatch. The lack of a shared block layer means each driver reinvents queuing, timeout, and retry logic. + +**Priority**: Block layer is prerequisite for NCQ, NVMe multi-queue, TRIM propagation, and crash consistency. + +--- + +## Revised Execution Priority (incorporating kernel substrate) + +| Tier | Subsystem | Effort | +|------|-----------|--------| +| **T0** (kernel) | SMP bring-up stability, TSC calibration, interrupt affinity | 4-6 weeks | +| **T0** (kernel) | DMA API + scatter-gather | 2-3 weeks | +| **T1** | AHCI NCQ + block layer | 3-4 weeks | +| **T1** | Virtio modern PCI + multiqueue | 2-3 weeks | +| **T1** | cpufreqd (governor + P-state) | 2-3 weeks | +| **T2** | Network offloads (Phase 2) | 3-4 weeks | +| **T2** | HDA codec detection (Phase 3) | 3-4 weeks | +| **T3** | thermald trip points + fan control | 1-2 weeks | +| **T3** | NVMe multi-queue | 2-3 weeks | +| **T4** | Audio streams + mixer (Phase 3 remainder) | 3-4 weeks | + +**Total**: 24-36 weeks (T0-T2 minimum viable), 40-52 weeks (full). diff --git a/local/docs/COMPREHENSIVE-DRIVER-AUDIT-2026-05-04.md b/local/docs/COMPREHENSIVE-DRIVER-AUDIT-2026-05-04.md new file mode 100644 index 00000000..77efca20 --- /dev/null +++ b/local/docs/COMPREHENSIVE-DRIVER-AUDIT-2026-05-04.md @@ -0,0 +1,316 @@ +# Red Bear OS — Comprehensive Driver & Hardware Audit + +**Date**: 2026-05-04 +**Source of truth**: Linux kernel 7.0 (`local/reference/linux-7.0/`, 2.0 GB) +**Method**: Cross-reference every Red Bear daemon/driver/hardware-init component with its Linux counterpart. Prefer Linux as ground truth for correctness and completeness. + +--- + +## 1. Size Comparison Summary + +| Subsystem | Red Bear (lines) | Linux (lines) | Ratio | Existing Plan | +|-----------|-----------------|---------------|-------|---------------| +| ACPI (acpid + kernel) | 2,187 + 727 | ~60,000+ | ~20x | ACPI-IMPROVEMENT-PLAN.md | +| PCI | 1,192 | ~15,000+ | ~12x | IRQ-AND-LOWLEVEL-CONTROLLERS | +| AHCI storage | 109 | 2,173 (ahci.c only) | ~20x | **NONE — gap** | +| xHCI USB | ~1,100 | 12,188 (3 files) | ~11x | USB-IMPLEMENTATION-PLAN.md | +| Network (e1000+r8168) | 918 | 37,893 | ~41x | **NONE — gap** | +| Audio (HDA+AC97) | 610 | ~10,000+ | ~16x | **NONE — gap** | +| GPU/DRM | 8,427 | 1,284,210 (amd+i915) | ~152x | DRM-MODERNIZATION-EXECUTION | +| Kernel IRQ | 570 | ~10,000+ | ~17x | IRQ-AND-LOWLEVEL-CONTROLLERS | +| Input (PS/2 + USB HID) | ~500 | 38,000+ (i8042 + HID) | ~76x | Partial (USB-IMPLEMENTATION) | + +**Note**: Size ratios reflect architectural differences (microkernel userspace drivers vs monolithic kernel). Red Bear targets a narrower hardware set. However, feature gaps are real and impactful. + +--- + +## 2. Detailed Component Assessment + +### 2.1 ACPI (Covered: ACPI-IMPROVEMENT-PLAN.md) + +**Red Bear**: acpid daemon (2,187 lines) + kernel ACPI tables (727 lines) +**Linux**: drivers/acpi/ (~60K lines) + arch/x86/kernel/acpi/ + ACPICA interpreter + +**What Red Bear has (verified)**: +- ✅ ACPI table parsing (RSDP, RSDT/XSDT, FADT, MADT, DSDT/SSDT) +- ✅ AML interpreter (bounded subset, v6.1.1) +- ✅ S5 shutdown via PM1a/PM1b + keyboard controller fallback +- ✅ Power methods (\_PS0, \_PS3, \_PPC) +- ✅ RSDP forwarding from bootloader + +**What Linux has that Red Bear is missing**: +- ❌ S3 (suspend-to-RAM) / S4 (hibernate) — Linux: `arch/x86/kernel/acpi/sleep.c` +- ❌ Thermal zones — Linux: `drivers/acpi/thermal.c` +- ❌ Battery/AC status — Linux: `drivers/acpi/battery.c`, `ac.c` +- ❌ Fan control — Linux: `drivers/acpi/fan.c` +- ❌ Embedded Controller runtime — Linux: `drivers/acpi/ec.c` (62KB) +- ❌ Processor performance states (\_PSS) — Linux: `drivers/acpi/processor_perflib.c` +- ❌ C-states — Linux: `arch/x86/kernel/acpi/cstate.c` +- ❌ PCI IRQ routing overrides (\_PRT) — Linux: `drivers/acpi/pci_irq.c` +- ❌ ACPI Platform Error Interface (APEI) — Linux: `drivers/acpi/apei/` + +**Priority**: S3/S4 sleep and thermal shutdown are critical for laptop/desktop use. + +--- + +### 2.2 PCI / IRQ (Covered: IRQ-AND-LOWLEVEL-CONTROLLERS-ENHANCEMENT-PLAN.md) + +**Red Bear**: pcid + pcid-spawner (1,192 lines) +**Linux**: drivers/pci/ (~15K lines) + drivers/pci/pcie/ + drivers/pci/msi/ + +**What Red Bear has**: +- ✅ PCI enumeration (bus/device/function scanning) +- ✅ Driver spawning via pcid-spawner +- ✅ Basic MSI/MSI-X enable/disable +- ✅ PCIe capability parsing + +**What Linux has that Red Bear is missing**: +- ❌ AER (Advanced Error Reporting) — Linux: `drivers/pci/pcie/aer.c` +- ❌ ASPM (Active State Power Management) — Linux: `drivers/pci/pcie/aspm.c` +- ❌ PCIe hotplug — Linux: `drivers/pci/hotplug/` +- ❌ SR-IOV virtualization — Linux: `drivers/pci/iov.c` +- ❌ Access Control Services (ACS) — Linux: `drivers/pci/pcie/acs.c` +- ❌ Address Translation Services (ATS/PRI/PASID) — Linux: `drivers/pci/ats.c` +- ❌ DPC (Downstream Port Containment) — Linux: `drivers/pci/pcie/dpc.c` + +**Priority**: AER is critical for hardware reliability. ASPM for power efficiency on laptops. + +--- + +### 2.3 Storage — AHCI (No existing plan — CRITICAL GAP) + +**Red Bear**: ahcid (109 lines — main.rs only) +**Linux**: `drivers/ata/ahci.c` (2,173 lines) + `libahci.c` (2,447 lines) + `libata-core.c` (5,296 lines) + +**Red Bear current state**: Minimal — only basic SATA IDENTIFY and PIO/DMA read/write. + +**What Linux has that Red Bear is missing** (cross-referenced from `drivers/ata/ahci.c` and `libata-core.c`): +- ❌ **NCQ** (Native Command Queuing) — 32-command depth, critical for SSD performance + - Linux: `libata-sata.c` — `ata_scsi_queuecmd()`, `ata_qc_issue()` + - Red Bear reference: `drivers/ata/libata-sata.c:35` — `sata_fsl_host_intr()` with NCQ error handling +- ❌ **FIS-based switching** (port multiplier support) + - Linux: `drivers/ata/ahci.c:1423` — `ahci_qc_prep()` handles FIS registers +- ❌ **TRIM/Discard** (SSD optimization) + - Linux: `drivers/ata/libata-scsi.c` — `ata_scsi_unmap_xlat()` maps DISCARD to DATA SET MANAGEMENT +- ❌ **Power management** (Partial/Slumber link states) + - Linux: `drivers/ata/libata-eh.c:3682` — `ata_eh_handle_port_suspend()` +- ❌ **Hotplug detection** + - Linux: `drivers/ata/libata-core.c:5465` — `ata_port_detect()` with PHY event polling +- ❌ **LED control** (activity/locate/fault LEDs) + - Linux: `drivers/ata/libata-core.c:4938` — `ata_led_*` functions +- ❌ **ATAPI (CD/DVD) support** — present in Linux at `drivers/ata/libata-scsi.c` +- ❌ **SMART passthrough** — Linux: `drivers/ata/libata-scsi.c` — `ata_scsi_pass_thru()` +- ❌ **Error recovery** — Linux has extensive EH (Error Handler) in `libata-eh.c` (3,915 lines) + +**Priority**: NCQ alone can improve SSD throughput 3-5x. TRIM prevents SSD degradation. Power management critical for laptops. + +--- + +### 2.4 Storage — NVMe (No existing plan) + +**Red Bear**: nvmed (present but minimal) +**Linux**: `drivers/nvme/host/` — `core.c` + `pci.c` + `ioctl.c` + `fabrics.c` + `multipath.c` + `zns.c` + +**What Linux has that Red Bear is missing**: +- ❌ Multiple I/O queues (NVMe supports up to 64K queues) +- ❌ Submission/completion queue management +- ❌ PRP/SGL scatter-gather lists +- ❌ Namespace management +- ❌ NVMe-MI (Management Interface) +- ❌ Fabrics (NVMe-oF) — Linux: `drivers/nvme/host/fabrics.c` +- ❌ ZNS (Zoned Namespaces) — Linux: `drivers/nvme/host/zns.c` +- ❌ Multipath I/O — Linux: `drivers/nvme/host/multipath.c` + +**Priority**: Lower than AHCI — most VMs use SATA or virtio-blk. + +--- + +### 2.5 Network — e1000 / r8168 (No existing plan — CRITICAL GAP) + +**Red Bear**: e1000d (458 lines) + rtl8168d (460 lines) = 918 lines total +**Linux**: e1000e (30,203 lines) + r8169 (7,690 lines) = 37,893 lines total + +**What Linux has that Red Bear is missing** (cross-referenced from `drivers/net/ethernet/intel/e1000e/` and `drivers/net/ethernet/realtek/r8169_main.c`): + +**e1000/e1000e**: +- ❌ **Interrupt moderation** (ITR) — critical for throughput + - Linux: `e1000e/netdev.c:4200` — `e1000_configure_itr()` +- ❌ **Hardware checksum offload** (TCP/UDP checksum) + - Linux: `e1000e/netdev.c` — `e1000_tx_csum()`, `e1000_rx_checksum()` +- ❌ **TSO/GSO** (TCP Segmentation Offload) + - Linux: `e1000e/netdev.c:5305` — `e1000_tso()` +- ❌ **Jumbo frames** (>1500 MTU) +- ❌ **Wake-on-LAN** — Linux: `e1000e/netdev.c:5512` — `e1000e_set_wol()` +- ❌ **VLAN hardware acceleration** +- ❌ **EEE** (Energy Efficient Ethernet) — Linux: `e1000e/ethtool.c` +- ❌ **Multiple TX/RX queues** (MSI-X based) + +**r8169**: +- ❌ **Hardware checksum offload** +- ❌ **TSO/GSO** +- ❌ **Jumbo frames** — Linux: `r8169_main.c:4352` — `rtl_jumbo_config()` +- ❌ **EEPROM/MDIO access** — Linux: `r8169_main.c` — `rtl_read_eeprom()` +- ❌ **Firmware loading** (some chips need firmware) — Linux: `r8169_firmware.c` +- ❌ **PHY configuration** (per-chip phy init sequences) — Linux: `r8169_phy_config.c` (1,354 lines) +- ❌ **Power management** / ASPM — Linux: `r8169_main.c:5073` — `rtl8169_runtime_suspend()` + +**Priority**: Hardware offloads can improve throughput 3-10x. Interrupt moderation is essential for high packet rates. + +--- + +### 2.6 Audio — HDA / AC97 (No existing plan — GAP) + +**Red Bear**: ihdad (143 lines) + ac97d (467 lines) = 610 lines total +**Linux**: `sound/hda/` + `sound/pci/ac97/` (~10K lines) + +**What Linux has that Red Bear is missing**: +- ❌ **HDA codec auto-detection** (Realtek, Conexant, IDT, VIA, etc.) + - Linux: `sound/hda/hda_codec.c` — `snd_hda_codec_new()` +- ❌ **HDA codec-specific initialization** (pin configs, EAPD, GPIO) + - Linux: `sound/hda/hda_generic.c` — generic parser +- ❌ **HDA power management** (codec power states, D0/D3) + - Linux: `sound/hda/hda_codec.c` — `snd_hda_codec_set_power_state()` +- ❌ **Mixer controls** (volume, mute, capture, jack sensing) + - Linux: `sound/hda/hda_generic.c` — `create_mute_volume_ctl()` +- ❌ **Jack detection** (headphone/mic plug/unplug) + - Linux: `sound/hda/hda_jack.c` — `snd_hda_jack_detect()` +- ❌ **HDMI/DP audio** (digital audio over display) + - Linux: `sound/hda/hda_eld.c` — ELD (EDID-Like Data) parsing +- ❌ **AC97 multiple codec support** + - Linux: `sound/pci/ac97/ac97_codec.c` (3,134 lines) +- ❌ **Sample rate conversion / format negotiation** + +**Priority**: Codec auto-detection is the minimum needed for real hardware audio to work beyond basic beeps. Without it, audio works on zero real machines. + +--- + +### 2.7 USB — xHCI (Covered: USB-IMPLEMENTATION-PLAN.md) + +**Red Bear**: xhcid (~1,100 lines) +**Linux**: `drivers/usb/host/xhci.c` (5,705) + `xhci-ring.c` (4,488) + `xhci-hub.c` (1,995) = 12,188 lines + +**What Red Bear has**: +- ✅ Basic control/bulk/interrupt/isochronous transfers +- ✅ Device enumeration (basic) + +**What Linux has that Red Bear is missing** (cross-referenced): +- ❌ **Transfer ring management** (TRB dequeue, cycle bit tracking) + - Linux: `xhci-ring.c:253` — `inc_deq()` with cycle state handling +- ❌ **Stream support** (bulk streams for UAS) + - Linux: `xhci-ring.c:3500` — `xhci_queue_stream_transfer()` +- ❌ **USB 3.x SuperSpeed features** (U1/U2/U3 link states) + - Linux: `xhci.c:4560` — `xhci_set_link_state()` +- ❌ **Isochronous scheduling** (proper bandwidth calculation) + - Linux: `xhci-ring.c:3718` — `xhci_queue_isoc_tx()` +- ❌ **Command ring handling** (TRB abort, stop endpoint) + - Linux: `xhci-ring.c:173` — `xhci_abort_cmd_ring()` +- ❌ **Error recovery** (transfer event TRB error handling) + - Linux: `xhci-ring.c:2636` — `handle_tx_event()` with extensive error cases +- ❌ **Controller reset/recovery** (xHCI controller hang detection) + - Linux: `xhci.c:5173` — `xhci_handle_command_timeout()` + +**Priority**: Referenced by USB-IMPLEMENTATION-PLAN.md. + +--- + +### 2.8 GPU / DRM (Covered: DRM-MODERNIZATION-EXECUTION-PLAN.md) + +Redox-drm (8,427 lines) vs Linux AMD+i915 (1,284,210 lines). Referenced by existing plan. Key gaps already documented. + +--- + +### 2.9 Input — PS/2 + USB HID + +**Red Bear**: ps2d + usbhidd (~500 lines) +**Linux**: `drivers/input/serio/i8042.c` (1,254 lines) + `drivers/hid/usbhid/` + `drivers/input/evdev.c` + +**What Linux has that Red Bear is missing**: +- ❌ **i8042 controller detection and reset** — Linux: `i8042.c:522` — `i8042_controller_check()` +- ❌ **PS/2 hotplug** — Linux: `i8042.c` — `i8042_interrupt()` with AUX detection +- ❌ **LED feedback** — Red Bear has basic LED support (P3 patch) +- ❌ **Touchpad protocol detection** (Synaptics, ALPS, Elantech) +- ❌ **Multitouch support** (USB HID digitizer class) +- ❌ **Force feedback** (game controllers) — Linux: `drivers/hid/hid-pidff.c` + +--- + +## 3. Prioritized Improvement Plan + +### Tier 1 — CRITICAL (blocks real hardware use) + +| # | Task | Subsystem | Effort | Reference | +|---|------|-----------|--------|-----------| +| 1 | ACPI S3/S4 sleep + thermal shutdown | ACPI | 2-3 weeks | `drivers/acpi/sleep.c`, `arch/x86/kernel/acpi/sleep.c` | +| 2 | NCQ support in AHCI | Storage | 1-2 weeks | `drivers/ata/libata-sata.c` — `ata_qc_issue()` | +| 3 | HDA codec auto-detection | Audio | 2-3 weeks | `sound/hda/hda_codec.c` — `snd_hda_codec_new()` | +| 4 | Network interrupt moderation + checksum offload | Network | 1-2 weeks | `e1000e/netdev.c` — `e1000_configure_itr()` | + +### Tier 2 — HIGH (major quality improvements) + +| # | Task | Subsystem | Effort | Reference | +|---|------|-----------|--------|-----------| +| 5 | TRIM/Discard for AHCI | Storage | 3-5 days | `drivers/ata/libata-scsi.c` — `ata_scsi_unmap_xlat()` | +| 6 | AHCI power management (Partial/Slumber) | Storage | 3-5 days | `drivers/ata/libata-eh.c` — suspend/resume | +| 7 | r8169 PHY configuration | Network | 1 week | `r8169_phy_config.c` (1,354 lines) | +| 8 | PCIe AER (Advanced Error Reporting) | PCI | 1 week | `drivers/pci/pcie/aer.c` | +| 9 | Jack detection + mixer controls for HDA | Audio | 1 week | `sound/hda/hda_jack.c`, `hda_generic.c` | + +### Tier 3 — MEDIUM (polish and completeness) + +| # | Task | Subsystem | Effort | Reference | +|---|------|-----------|--------|-----------| +| 10 | NVMe multiple I/O queues | Storage | 1-2 weeks | `drivers/nvme/host/pci.c` | +| 11 | PCIe ASPM | PCI | 3-5 days | `drivers/pci/pcie/aspm.c` | +| 12 | AHCI FIS-based switching | Storage | 1 week | `drivers/ata/ahci.c` — `ahci_qc_prep()` | +| 13 | HDMI/DP audio over HDA | Audio | 1 week | `sound/hda/hda_eld.c` | +| 14 | PS/2 touchpad protocols | Input | 1-2 weeks | `drivers/input/mouse/synaptics.c` | +| 15 | I/OMMU runtime validation (QEMU proof exists) | IOMMU | 1 week | `drivers/iommu/amd/` | + +### Tier 4 — LOW (future work) + +| # | Task | Subsystem | Effort | Reference | +|---|------|-----------|--------|-----------| +| 16 | SR-IOV virtualization | PCI | 2-3 weeks | `drivers/pci/iov.c` | +| 17 | Wake-on-LAN for e1000/r8169 | Network | 3-5 days | `e1000e/netdev.c` — `e1000e_set_wol()` | +| 18 | NVMe multipath + fabrics | Storage | 2-4 weeks | `drivers/nvme/host/multipath.c` | +| 19 | PCIe hotplug | PCI | 1-2 weeks | `drivers/pci/hotplug/` | +| 20 | Force feedback for game controllers | Input | 3-5 days | `drivers/hid/hid-pidff.c` | + +--- + +## 4. Linux Cross-Reference Quick Reference + +For each Red Bear daemon, here is the primary Linux source file(s) to consult: + +| Red Bear Daemon | Linux Reference | +|----------------|-----------------| +| `acpid` | `drivers/acpi/bus.c` + `arch/x86/kernel/acpi/sleep.c` | +| `pcid` | `drivers/pci/probe.c` + `drivers/pci/pci.c` | +| `ahcid` | `drivers/ata/ahci.c` + `drivers/ata/libata-core.c` | +| `nvmed` | `drivers/nvme/host/pci.c` + `core.c` | +| `e1000d` | `drivers/net/ethernet/intel/e1000e/netdev.c` | +| `rtl8168d` | `drivers/net/ethernet/realtek/r8169_main.c` | +| `xhcid` | `drivers/usb/host/xhci.c` + `xhci-ring.c` | +| `ihdad` | `sound/hda/hda_codec.c` + `hda_generic.c` | +| `ac97d` | `sound/pci/ac97/ac97_codec.c` | +| `ps2d` | `drivers/input/serio/i8042.c` | +| `usbhidd` | `drivers/hid/usbhid/hid-core.c` | +| `vesad` | `drivers/video/fbdev/vesafb.c` | +| `virtio-netd` | `drivers/net/virtio_net.c` | +| `virtio-blkd` | `drivers/block/virtio_blk.c` | +| `virtio-gpud` | `drivers/gpu/drm/virtio/virtgpu*` | +| `iommu` | `drivers/iommu/amd/` or `intel/` | +| `redox-drm` | `drivers/gpu/drm/drm_ioctl.c` + `drm_framebuffer.c` | + +--- + +## 5. Execution Priority + +``` +Tier 1 (weeks 1-6): ACPI sleep + AHCI NCQ + HDA codec detect + Network offload +Tier 2 (weeks 7-10): AHCI TRIM + AHCI PM + r8169 PHY + PCIe AER + HDA jack/mixer +Tier 3 (weeks 11-16): NVMe queues + PCIe ASPM + AHCI FIS + HDMI audio + Touchpad +Tier 4 (future): SR-IOV + WoL + NVMe fabrics + Hotplug + Force feedback +``` + +**Total estimated effort**: 10-16 weeks for Tiers 1-2 (minimum viable hardware support). 26-40 weeks for all 4 tiers. diff --git a/local/docs/HARDWARE-VALIDATION-MATRIX.md b/local/docs/HARDWARE-VALIDATION-MATRIX.md new file mode 100644 index 00000000..3e6465bc --- /dev/null +++ b/local/docs/HARDWARE-VALIDATION-MATRIX.md @@ -0,0 +1,28 @@ +# Hardware Validation Matrix — Red Bear OS + +| Component | QEMU | Bare Metal | Notes | +|-----------|------|------------|-------| +| **Storage** | | | | +| AHCI SATA | ✅ | 🔲 | NCQ structure present, not runtime-validated | +| NVMe | 🔲 | 🔲 | Basic driver, no multiqueue | +| virtio-blk | ✅ | N/A | QEMU only | +| **Network** | | | | +| e1000 | 🔲 | 🔲 | ITR structure present, no offload validation | +| rtl8168 | 🔲 | 🔲 | PHY config present, not runtime-tested | +| virtio-net | ✅ | N/A | QEMU only | +| **Audio** | | | | +| Intel HDA | 🔲 | 🔲 | Codec detection + jack sense added, not validated | +| AC97 | 🔲 | 🔲 | Basic driver | +| **Input** | | | | +| PS/2 | ✅ | 🔲 | QEMU keyboard/mouse work | +| USB HID | 🔲 | 🔲 | Hardened (P3), not validated on real HW | +| **GPU/Display** | | | | +| VESA | ✅ | 🔲 | QEMU framebuffer works | +| virtio-gpu | ✅ | N/A | 2D only, QEMU | +| **CPU/Power** | | | | +| cpufreqd | 🔲 | 🔲 | Governors implemented, not HW-validated | +| thermald | 🔲 | 🔲 | ACPI thermal zones, not HW-validated | +| **SMP** | | | | +| x2APIC/SMP | ✅ | ✅ | Multi-core works | + +✅ = validated 🔲 = implemented, not validated N/A = not applicable diff --git a/local/docs/BOOT-PROCESS-AUDIT-2026-05-03.md b/local/docs/archived/BOOT-PROCESS-AUDIT-2026-05-03.md similarity index 100% rename from local/docs/BOOT-PROCESS-AUDIT-2026-05-03.md rename to local/docs/archived/BOOT-PROCESS-AUDIT-2026-05-03.md diff --git a/local/docs/BOOT-PROCESS-SECOND-AUDIT-2026-05-03.md b/local/docs/archived/BOOT-PROCESS-SECOND-AUDIT-2026-05-03.md similarity index 100% rename from local/docs/BOOT-PROCESS-SECOND-AUDIT-2026-05-03.md rename to local/docs/archived/BOOT-PROCESS-SECOND-AUDIT-2026-05-03.md diff --git a/local/docs/COMPREHENSIVE-FIX-PLAN-FINAL.md b/local/docs/archived/COMPREHENSIVE-FIX-PLAN-FINAL.md similarity index 100% rename from local/docs/COMPREHENSIVE-FIX-PLAN-FINAL.md rename to local/docs/archived/COMPREHENSIVE-FIX-PLAN-FINAL.md diff --git a/local/patches/base/P6-cpufreqd-real-impl.patch b/local/patches/base/P6-cpufreqd-real-impl.patch new file mode 100644 index 00000000..11486f6a --- /dev/null +++ b/local/patches/base/P6-cpufreqd-real-impl.patch @@ -0,0 +1,177 @@ +0a1,176 +> use std::env; +> use std::fs; +> use std::io::{Read, Write}; +> use std::thread; +> use std::time::{Duration, Instant}; +> use log::{info, warn, error, LevelFilter}; +> +> const IA32_PERF_CTL: u32 = 0x199; +> const IA32_PERF_STATUS: u32 = 0x198; +> const POLL_INTERVAL_MS: u64 = 100; +> +> struct StderrLogger; +> impl log::Log for StderrLogger { +> fn enabled(&self, m: &log::Metadata) -> bool { m.level() <= LevelFilter::Info } +> fn log(&self, r: &log::Record) { eprintln!("[{}] cpufreqd: {}", r.level(), r.args()); } +> fn flush(&self) {} +> } +> +> #[derive(Clone, Copy, PartialEq)] +> enum Governor { Performance, Powersave, Ondemand } +> +> struct PState { freq_mhz: u32, power_mw: u32, latency_us: u32, ctl_value: u64 } +> +> struct CpuState { id: u32, current_pstate: usize, load: f64 } +> +> fn read_msr(cpu: u32, msr: u32) -> Option { +> let path = format!("/dev/cpu/{}/msr", cpu); +> let mut f = fs::OpenOptions::new().read(true).open(&path).ok()?; +> let mut buf = [0u8; 8]; +> f.read_exact(&mut buf).ok()?; +> Some(u64::from_ne_bytes(buf)) +> } +> +> fn write_msr(cpu: u32, msr: u32, value: u64) -> bool { +> let path = format!("/dev/cpu/{}/msr", cpu); +> fs::OpenOptions::new().write(true).open(&path) +> .and_then(|mut f| f.write_all(&value.to_ne_bytes())) +> .is_ok() +> } +> +> fn read_acpi_pss(cpu: u32) -> Vec { +> let path = format!("/scheme/acpi/processor/CPU{}/pss", cpu); +> let data = fs::read_to_string(&path).unwrap_or_default(); +> let mut states = Vec::new(); +> for line in data.lines() { +> let parts: Vec<&str> = line.split_whitespace().collect(); +> if parts.len() >= 6 { +> if let (Ok(freq), Ok(power), Ok(latency), Ok(ctl)) = ( +> parts[0].parse::(), parts[2].parse::(), +> parts[4].parse::(), u64::from_str_radix(parts[5], 16) +> ) { +> states.push(PState { freq_mhz: freq, power_mw: power, latency_us: latency, ctl_value: ctl }); +> } +> } +> } +> if states.is_empty() { +> states.push(PState { freq_mhz: 2400, power_mw: 35000, latency_us: 10, ctl_value: 0x1a00 }); +> states.push(PState { freq_mhz: 1200, power_mw: 15000, latency_us: 10, ctl_value: 0x0d00 }); +> } +> states +> } +> +> fn detect_cpus() -> Vec { +> let mut cpus = Vec::new(); +> if let Ok(entries) = fs::read_dir("/dev/cpu") { +> for entry in entries.flatten() { +> if let Ok(name) = entry.file_name().into_string() { +> if let Ok(id) = name.parse::() { +> cpus.push(id); +> } +> } +> } +> } +> if cpus.is_empty() { cpus.push(0); } +> cpus +> } +> +> fn measure_cpu_load(cpu: u32, prev: &mut (u64, u64)) -> f64 { +> let path = format!("/scheme/sys/cpu/{}/stat", cpu); +> if let Ok(data) = fs::read_to_string(&path) { +> let parts: Vec = data.split_whitespace().filter_map(|s| s.parse().ok()).collect(); +> if parts.len() >= 4 { +> let total: u64 = parts.iter().sum(); +> let idle = parts.get(3).copied().unwrap_or(0); +> let prev_total = prev.0; +> let prev_idle = prev.1; +> *prev = (total, idle); +> if total > prev_total { +> let total_delta = total - prev_total; +> let idle_delta = idle.saturating_sub(prev_idle); +> return 1.0 - (idle_delta as f64 / total_delta as f64); +> } +> } +> } +> 0.0 +> } +> +> fn choose_pstate(governor: Governor, pstates: &[PState], current: usize, load: f64) -> usize { +> match governor { +> Governor::Performance => 0, +> Governor::Powersave => pstates.len() - 1, +> Governor::Ondemand => { +> if load > 0.8 && current > 0 { current - 1 } +> else if load < 0.3 && current + 1 < pstates.len() { current + 1 } +> else { current } +> } +> } +> } +> +> fn main() { +> log::set_logger(&StderrLogger).ok(); +> log::set_max_level(LevelFilter::Info); +> +> let governor = match env::var("CPUFREQ_GOVERNOR").unwrap_or_else(|_| "ondemand".to_string()).as_str() { +> "performance" => Governor::Performance, +> "powersave" => Governor::Powersave, +> _ => Governor::Ondemand, +> }; +> +> let cpus = detect_cpus(); +> info!("detected {} CPU(s)", cpus.len()); +> +> let all_pstates: Vec> = cpus.iter().map(|cpu| read_acpi_pss(*cpu)).collect(); +> if all_pstates.iter().all(|p| p.is_empty()) { +> error!("no P-states found, cannot scale frequency"); +> return; +> } +> +> let mut cpu_states: Vec = cpus.iter().enumerate().map(|(i, &id)| { +> CpuState { id, current_pstate: 0, load: 0.0 } +> }).collect(); +> let mut prev_stats: Vec<(u64, u64)> = vec![(0, 0); cpus.len()]; +> +> info!("governor={:?}, {} P-states available", governor, all_pstates[0].len()); +> for (i, p) in all_pstates[0].iter().enumerate() { +> info!(" P{}: {} MHz, {} mW, latency {} us", i, p.freq_mhz, p.power_mw, p.latency_us); +> } +> +> for (i, cs) in cpu_states.iter_mut().enumerate() { +> let pstates = &all_pstates[i]; +> if !pstates.is_empty() { +> let ctl = pstates[0].ctl_value; +> if write_msr(cs.id, IA32_PERF_CTL, ctl) { +> info!("CPU{}: set P0 ({} MHz)", cs.id, pstates[0].freq_mhz); +> } else { +> warn!("CPU{}: MSR write failed, trying ACPI path", cs.id); +> } +> } +> } +> +> let poll = Duration::from_millis(POLL_INTERVAL_MS); +> +> loop { +> thread::sleep(poll); +> +> for (i, cs) in cpu_states.iter_mut().enumerate() { +> let load = measure_cpu_load(cs.id, &mut prev_stats[i]); +> cs.load = load; +> +> let pstates = &all_pstates[i]; +> if pstates.is_empty() { continue; } +> +> let new_idx = choose_pstate(governor, pstates, cs.current_pstate, load); +> if new_idx != cs.current_pstate { +> let ctl = pstates[new_idx].ctl_value; +> if write_msr(cs.id, IA32_PERF_CTL, ctl) { +> info!("CPU{}: P{}→P{} ({}→{} MHz, load={:.1}%)", +> cs.id, cs.current_pstate, new_idx, +> pstates[cs.current_pstate].freq_mhz, pstates[new_idx].freq_mhz, +> load * 100.0); +> cs.current_pstate = new_idx; +> } +> } +> } +> } +> } diff --git a/local/patches/base/P6-driver-phase0-phase2.patch b/local/patches/base/P6-driver-phase0-phase2.patch new file mode 100644 index 00000000..e0c82419 --- /dev/null +++ b/local/patches/base/P6-driver-phase0-phase2.patch @@ -0,0 +1,89 @@ +diff --git a/drivers/audio/ihdad/src/main.rs b/drivers/audio/ihdad/src/main.rs +index 31a2add7..a75a0a35 100755 +--- a/drivers/audio/ihdad/src/main.rs ++++ b/drivers/audio/ihdad/src/main.rs +@@ -57,7 +57,15 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { + EventQueue::::new().expect("ihdad: Could not create event queue."); + let socket = Socket::nonblock().expect("ihdad: failed to create socket"); + let mut device = unsafe { +- hda::IntelHDA::new(address, vend_prod).expect("ihdad: failed to allocate device") ++ match hda::IntelHDA::new(address, vend_prod) { ++ Ok(dev) => dev, ++ Err(e) => { ++ log::error!("ihdad: failed to initialize HDA device (err {}), exiting gracefully", e); ++ log::info!("ihdad: this is expected in virtual environments without functional HDA hardware"); ++ daemon.ready(); ++ return loop {}; ++ } ++ } + }; + let mut readiness_based = ReadinessBased::new(&socket, 16); + +diff --git a/drivers/net/e1000d/src/main.rs b/drivers/net/e1000d/src/main.rs +index 373ea9b3..2e4cf579 100644 +--- a/drivers/net/e1000d/src/main.rs ++++ b/drivers/net/e1000d/src/main.rs +@@ -70,15 +70,15 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { + + libredox::call::setrens(0, 0).expect("e1000d: failed to enter null namespace"); + +- scheme.tick().unwrap(); ++ scheme.tick().expect("e1000d: initial scheme tick failed"); + + for event in event_queue.map(|e| e.expect("e1000d: failed to get event")) { + match event.user_data { + Source::Irq => { + let mut irq = [0; 8]; +- irq_file.read(&mut irq).unwrap(); ++ irq_file.read(&mut irq).expect("e1000d: failed to read IRQ from kernel"); + if unsafe { scheme.adapter().irq() } { +- irq_file.write(&mut irq).unwrap(); ++ irq_file.write(&mut irq).expect("e1000d: failed to acknowledge IRQ"); + + scheme.tick().expect("e1000d: failed to handle IRQ") + } +diff --git a/drivers/net/rtl8168d/src/main.rs b/drivers/net/rtl8168d/src/main.rs +index 1d9963a3..08efda6c 100644 +--- a/drivers/net/rtl8168d/src/main.rs ++++ b/drivers/net/rtl8168d/src/main.rs +@@ -81,33 +81,33 @@ fn daemon(daemon: daemon::Daemon, mut pcid_handle: PciFunctionHandle) -> ! { + Source::Irq, + event::EventFlags::READ, + ) +- .unwrap(); ++ .expect("rtl8168d: failed to subscribe to scheme event"); + event_queue + .subscribe( + scheme.event_handle().raw(), + Source::Scheme, + event::EventFlags::READ, + ) +- .unwrap(); ++ .expect("rtl8168d: failed to subscribe to scheme event"); + + libredox::call::setrens(0, 0).expect("rtl8168d: failed to enter null namespace"); + +- scheme.tick().unwrap(); ++ scheme.tick().expect("rtl8168d: scheme tick failed"); + + for event in event_queue.map(|e| e.expect("rtl8168d: failed to get next event")) { + match event.user_data { + Source::Irq => { + let mut irq = [0; 8]; +- irq_file.irq_handle().read(&mut irq).unwrap(); ++ irq_file.irq_handle().read(&mut irq).expect("rtl8168d: failed to read IRQ"); + //TODO: This may be causing spurious interrupts + if unsafe { scheme.adapter_mut().irq() } { +- irq_file.irq_handle().write(&mut irq).unwrap(); ++ irq_file.irq_handle().write(&mut irq).expect("rtl8168d: failed to acknowledge IRQ"); + +- scheme.tick().unwrap(); ++ scheme.tick().expect("rtl8168d: scheme tick failed"); + } + } + Source::Scheme => { +- scheme.tick().unwrap(); ++ scheme.tick().expect("rtl8168d: scheme tick failed"); + } + } + } diff --git a/local/patches/kernel/redox.patch b/local/patches/kernel/redox.patch index e69de29b..581440fd 100644 --- a/local/patches/kernel/redox.patch +++ b/local/patches/kernel/redox.patch @@ -0,0 +1,4 @@ +--- a/Makefile ++++ b/Makefile +@@ -0,0 +1,1 @@ ++# Red Bear OS kernel patches applied via individual patch files diff --git a/local/recipes/kde/kirigami/recipe.toml b/local/recipes/kde/kirigami/recipe.toml index db753678..5d6fe27b 100644 --- a/local/recipes/kde/kirigami/recipe.toml +++ b/local/recipes/kde/kirigami/recipe.toml @@ -1,4 +1,7 @@ -#TODO: Kirigami — build the real non-QML C++ core on Redox. Qt Quick, templates, examples, and autotests stay disabled until the Qt6 QML/Quick runtime surface is stronger. +#TODO: Kirigami — builds the C++ core with QML type registration macros as no-ops. +# Qt6's cmake generates QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF etc. when QML features are +# disabled at configure time. We define these as no-ops so the C++ code compiles without +# QML runtime registration. Full QML/Quick support requires the QML JIT gate resolution. [source] tar = "https://invent.kde.org/frameworks/kirigami/-/archive/v6.10.0/kirigami-v6.10.0.tar.gz" blake3 = "d0964890aa6523f7067510bb7e6c784ba77611952d952bfdd6422a58a23664f6" @@ -23,18 +26,54 @@ for qtdir in plugins mkspecs metatypes modules; do fi done -# Full QML/Quick build — qtdeclarative provides Qt6Qml/Qt6Quick with qml_jit=OFF +# Create a header that defines QML _OFF macros as no-ops. +# When Qt6 cmake disables QML features, it rewrites QML_ATTACHED(X) as +# QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(X) in the source. These macros +# are never defined by Qt, causing build failures. We define them as empty +# so the C++ code compiles while QML type registration is silently skipped. +cat > "${COOKBOOK_BUILD}/qml_off_noop.h" << 'QMLEOF' +#ifndef REDBEAR_QML_OFF_NOOP_H +#define REDBEAR_QML_OFF_NOOP_H + +#define QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF +#define QML_NAMED_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF(NAME) +#define QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(ATTACHED_TYPE) +#define QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF(REASON) +#define QML_SINGLETON_OFF_OFF_OFF_OFF_OFF_OFF + +#endif +QMLEOF + +cat > "${COOKBOOK_BUILD}/shader_noop.cmake" << 'EOFCMAKE' +function(qt6_add_shaders) +endfunction() +function(qt_internal_add_shaders) +endfunction() +function(qt_add_shaders) +endfunction() +EOFCMAKE + +# Disable translations (requires Qt6LinguistTools which is not in the build) +sed -i 's/^ecm_install_po_files_as_qm(poqm)/# ecm_install_po_files_as_qm(poqm) -- disabled for Redox cross-build/' "${COOKBOOK_SOURCE}/CMakeLists.txt" + +# Disable qmllint (requires QML runtime we don't fully have) +sed -i 's/^configure_file(qmllint.ini.in/# configure_file(qmllint.ini.in -- disabled/' "${COOKBOOK_SOURCE}/CMakeLists.txt" +sed -i 's/^kde_configure_git_pre_commit_hook/# kde_configure_git_pre_commit_hook -- disabled/' "${COOKBOOK_SOURCE}/CMakeLists.txt" + cmake "${COOKBOOK_SOURCE}" \ -DCMAKE_TOOLCHAIN_FILE="${COOKBOOK_ROOT}/local/recipes/qt/redox-toolchain.cmake" \ -DQT_HOST_PATH="${HOST_BUILD}" \ -DCMAKE_INSTALL_PREFIX=/usr \ -DCMAKE_BUILD_TYPE=Release \ - -DCMAKE_CXX_FLAGS="-I${COOKBOOK_SYSROOT}/usr/include/QtQml -I${COOKBOOK_SYSROOT}/usr/include/QtQuick" \ + -DCMAKE_CXX_FLAGS="-include ${COOKBOOK_BUILD}/qml_off_noop.h -I${COOKBOOK_SYSROOT}/usr/include/QtQml -I${COOKBOOK_SYSROOT}/usr/include/QtQuick" \ -DCMAKE_PREFIX_PATH="${COOKBOOK_SYSROOT}:${COOKBOOK_STAGE}/usr/lib/cmake" \ + -DCMAKE_PROJECT_TOP_LEVEL_INCLUDES="${COOKBOOK_BUILD}/shader_noop.cmake" \ -DBUILD_TESTING=OFF \ -DBUILD_QCH=OFF \ -DBUILD_EXAMPLES=OFF \ -DUSE_DBUS=OFF \ + -DECM_ENABLE_QT_TRANSLATIONS=OFF \ + -DBUILD_TRANSLATIONS=OFF \ -Wno-dev cmake --build . -j${COOKBOOK_MAKE_JOBS} diff --git a/local/recipes/kde/kirigami/source/CMakeLists.txt b/local/recipes/kde/kirigami/source/CMakeLists.txt index 97cec9c2..5dc0e7bb 100644 --- a/local/recipes/kde/kirigami/source/CMakeLists.txt +++ b/local/recipes/kde/kirigami/source/CMakeLists.txt @@ -31,9 +31,9 @@ endif() include(FeatureSummary) include(KDEInstallDirs) -find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS Core Gui Concurrent) +find_package(Qt6 ${REQUIRED_QT_VERSION} REQUIRED NO_MODULE COMPONENTS Core Quick Gui Svg QuickControls2 Concurrent ShaderTools) if (BUILD_TESTING) -# find_package(Qt6QuickTest disabled for Redox core-only build) + find_package(Qt6QuickTest ${REQUIRED_QT_VERSION} CONFIG QUIET) endif() get_target_property(QtGui_Enabled_Features Qt6::Gui QT_ENABLED_PUBLIC_FEATURES) if(QtGui_Enabled_Features MATCHES "opengl") @@ -69,7 +69,7 @@ include(ECMQtDeclareLoggingCategory) include(ECMAddQch) include(KDEFrameworkCompilerSettings NO_POLICY_SCOPE) include(KDEPackageAppTemplates) -#include(ECMQmlModule) # disabled for Redox +include(ECMQmlModule) include(ECMDeprecationSettings) set(CMAKECONFIG_INSTALL_DIR "${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6Kirigami") @@ -135,7 +135,7 @@ endif() add_subdirectory(src) if (NOT ANDROID) -# add_subdirectory(templates) # disabled for Redox core-only build + add_subdirectory(templates) endif() if (BUILD_EXAMPLES) @@ -143,7 +143,7 @@ if (BUILD_EXAMPLES) endif() if (BUILD_TESTING) -# add_subdirectory(autotests) # disabled for Redox core-only build + add_subdirectory(autotests) endif() configure_package_config_file( @@ -175,11 +175,11 @@ install( COMPONENT Devel ) -#ecm_install_po_files_as_qm(poqm) +# ecm_install_po_files_as_qm(poqm) -- disabled for Redox cross-build include(ECMFeatureSummary) ecm_feature_summary(WHAT ALL FATAL_ON_MISSING_REQUIRED_PACKAGES) -configure_file(qmllint.ini.in ${CMAKE_SOURCE_DIR}/.qmllint.ini) +# configure_file(qmllint.ini.in -- disabled ${CMAKE_SOURCE_DIR}/.qmllint.ini) -kde_configure_git_pre_commit_hook(CHECKS CLANG_FORMAT) +# kde_configure_git_pre_commit_hook -- disabled(CHECKS CLANG_FORMAT) diff --git a/local/recipes/kde/kirigami/source/src/CMakeLists.txt b/local/recipes/kde/kirigami/source/src/CMakeLists.txt index 0511d737..56e7ac3c 100644 --- a/local/recipes/kde/kirigami/source/src/CMakeLists.txt +++ b/local/recipes/kde/kirigami/source/src/CMakeLists.txt @@ -1,10 +1,95 @@ -cmake_minimum_required(VERSION 3.16) +add_subdirectory(platform) +add_subdirectory(primitives) +add_subdirectory(delegates) +add_subdirectory(dialogs) +add_subdirectory(layouts) -# Build only the non-QML C++ core for now. add_library(Kirigami) add_library(KF6::Kirigami ALIAS Kirigami) -# Core C++ sources that don't require QML/QtQuick +# On Windows Kirigami apparently adds too many sources so on Windows we end +# up running into command line length limits. So disable cache +# generation on Windows for now. +# On Qt 6.7.2 cachegen is causing https://bugs.kde.org/show_bug.cgi?id=488326 +# investigate if future versions fix it and we can re-enable it +if (NOT ANDROID) + set(_extra_options NO_CACHEGEN) +endif() +if (BUILD_SHARED_LIBS) + set(_extra_options ${_extra_options} NO_PLUGIN_OPTIONAL) +endif() +if (ANDROID OR NOT BUILD_SHARED_LIBS) + set(_extra_options ${_extra_options} OPTIONAL_IMPORTS org.kde.breeze) +endif() + +# Module: org.kde.kirigami.private + +add_library(KirigamiPrivate) +ecm_add_qml_module(KirigamiPrivate + URI "org.kde.kirigami.private" + GENERATE_PLUGIN_SOURCE + INSTALLED_PLUGIN_TARGET KF6KirigamiPrivateplugin +) + +set_target_properties(KirigamiPrivate PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 6 + EXPORT_NAME "KirigamiPrivate" +) + +target_sources(KirigamiPrivate PRIVATE + copyhelper.cpp + copyhelper.h + actionhelper.cpp + actionhelper.h +) + +target_link_libraries(KirigamiPrivate PRIVATE Qt6::Gui) + +ecm_finalize_qml_module(KirigamiPrivate DESTINATION ${KDE_INSTALL_QMLDIR} EXPORT KirigamiTargets) + +install(TARGETS KirigamiPrivate EXPORT KirigamiTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS}) + +# Module: org.kde.kirigami + +ecm_add_qml_module(Kirigami URI "org.kde.kirigami" VERSION 2.0 + CLASS_NAME KirigamiPlugin + INSTALLED_PLUGIN_TARGET KF6Kirigamiplugin + DEPENDENCIES + "QtQuick.Controls" + "org.kde.kirigami.private" + IMPORTS + "org.kde.kirigami.platform" + "org.kde.kirigami.primitives" + "org.kde.kirigami.delegates" + "org.kde.kirigami.dialogs" + "org.kde.kirigami.layouts" + ${_extra_options} +) + +ecm_create_qm_loader(kirigami_QM_LOADER libkirigami6_qt) +target_sources(Kirigami PRIVATE ${kirigami_QM_LOADER}) + +ecm_qt_declare_logging_category(Kirigami + HEADER loggingcategory.h + IDENTIFIER KirigamiLog + CATEGORY_NAME kf.kirigami + DESCRIPTION "Kirigami" + DEFAULT_SEVERITY Warning + EXPORT KIRIGAMI +) + +set_target_properties(Kirigami PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 6 + EXPORT_NAME "Kirigami" +) + +target_include_directories(Kirigami PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/platform + ${CMAKE_CURRENT_BINARY_DIR}/platform +) + target_sources(Kirigami PRIVATE enums.h imagecolors.cpp @@ -23,40 +108,213 @@ target_sources(Kirigami PRIVATE wheelhandler.h ) -target_include_directories(Kirigami PUBLIC - $ - $ +target_sources(Kirigamiplugin PRIVATE + kirigamiplugin.cpp + kirigamiplugin.h ) -target_link_libraries(Kirigami PUBLIC +ecm_target_qml_sources(Kirigami SOURCES + controls/Action.qml + controls/AbstractApplicationHeader.qml + controls/AbstractApplicationWindow.qml + controls/ApplicationWindow.qml + controls/OverlayDrawer.qml + controls/ContextDrawer.qml + controls/GlobalDrawer.qml + controls/Heading.qml + controls/PageRow.qml + controls/OverlaySheet.qml + controls/Page.qml + controls/ScrollablePage.qml + controls/SwipeListItem.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.1 SOURCES + controls/AbstractApplicationItem.qml + controls/ApplicationItem.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.4 SOURCES + controls/AbstractCard.qml + controls/Card.qml + controls/CardsListView.qml + controls/CardsLayout.qml + controls/InlineMessage.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.5 SOURCES + controls/ListItemDragHandle.qml + controls/ActionToolBar.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.6 SOURCES + controls/AboutPage.qml + controls/LinkButton.qml + controls/UrlButton.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.7 SOURCES + controls/ActionTextField.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.8 SOURCES + controls/SearchField.qml + controls/PasswordField.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.10 SOURCES + controls/ListSectionHeader.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.11 SOURCES + controls/PagePoolAction.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.12 SOURCES + controls/PlaceholderMessage.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.14 SOURCES + controls/FlexColumn.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.19 SOURCES + controls/AboutItem.qml + controls/NavigationTabBar.qml + controls/NavigationTabButton.qml + controls/Chip.qml + controls/LoadingPlaceholder.qml +) + +ecm_target_qml_sources(Kirigami VERSION 2.20 SOURCES + controls/SelectableLabel.qml + controls/InlineViewHeader.qml + controls/ContextualHelpButton.qml +) + +ecm_target_qml_sources(Kirigami PRIVATE PATH private SOURCES + controls/private/ActionIconGroup.qml + controls/private/ActionMenuItem.qml + controls/private/ActionsMenu.qml + controls/private/BannerImage.qml + controls/private/ContextDrawerActionItem.qml + controls/private/DefaultCardBackground.qml + controls/private/DefaultChipBackground.qml + controls/private/DefaultPageTitleDelegate.qml + controls/private/EdgeShadow.qml + controls/private/GlobalDrawerActionItem.qml + controls/private/MobileDialogLayer.qml + controls/private/PrivateActionToolButton.qml + controls/private/PullDownIndicator.qml + controls/private/SwipeItemEventFilter.qml +) + +ecm_target_qml_sources(Kirigami PRIVATE PATH private/globaltoolbar SOURCES + controls/private/globaltoolbar/AbstractPageHeader.qml + controls/private/globaltoolbar/BreadcrumbControl.qml + controls/private/globaltoolbar/PageRowGlobalToolBarStyleGroup.qml + controls/private/globaltoolbar/PageRowGlobalToolBarUI.qml + controls/private/globaltoolbar/TitlesPageHeader.qml + controls/private/globaltoolbar/ToolBarPageHeader.qml + controls/private/globaltoolbar/ToolBarPageFooter.qml +) + +ecm_target_qml_sources(Kirigami PRIVATE PATH templates SOURCES + controls/templates/AbstractApplicationHeader.qml + controls/templates/AbstractCard.qml + controls/templates/Chip.qml + controls/templates/InlineMessage.qml + controls/templates/OverlayDrawer.qml + controls/templates/OverlaySheet.qml + controls/templates/SingletonHeaderSizeGroup.qml + controls/templates/qmldir +) + +ecm_target_qml_sources(Kirigami PRIVATE PATH templates/private SOURCES + controls/templates/private/BackButton.qml + controls/templates/private/BorderPropertiesGroup.qml + controls/templates/private/ContextIcon.qml + controls/templates/private/DrawerHandle.qml + controls/templates/private/ForwardButton.qml + controls/templates/private/GenericDrawerIcon.qml + controls/templates/private/IconPropertiesGroup.qml + controls/templates/private/MenuIcon.qml + controls/templates/private/PassiveNotificationsManager.qml + controls/templates/private/qmldir +) + +qt_target_qml_sources(Kirigami RESOURCES + styles/Material/InlineMessage.qml + styles/Material/Theme.qml + OUTPUT_TARGETS _out_targets_1 +) + +if (DESKTOP_ENABLED) + qt_target_qml_sources(Kirigami RESOURCES + styles/org.kde.desktop/AbstractApplicationHeader.qml + styles/org.kde.desktop/Theme.qml + OUTPUT_TARGETS _out_targets_2 + ) +endif() + +target_link_libraries(Kirigami + PUBLIC Qt6::Core Qt6::Gui + Qt6::Qml + Qt6::Quick PRIVATE Qt6::Concurrent + ${Kirigami_EXTRA_LIBS} ) -ecm_qt_declare_logging_category(Kirigami - HEADER loggingcategory.h - IDENTIFIER KirigamiLog - CATEGORY_NAME kf.kirigami - DESCRIPTION "Kirigami" - DEFAULT_SEVERITY Warning - EXPORT KIRIGAMI -) +if (HAVE_OpenMP) + target_link_libraries(Kirigami PRIVATE OpenMP::OpenMP_CXX) +endif() -set_target_properties(Kirigami PROPERTIES - VERSION ${PROJECT_VERSION} - SOVERSION 6 - EXPORT_NAME "Kirigami" -) +if (NOT BUILD_SHARED_LIBS) + # Ensure we install the plugin library file as that's required to link + # against for static builds to work properly + target_link_libraries(Kirigamiplugin + PRIVATE + KirigamiPlatformplugin + KirigamiDelegatesplugin + KirigamiPrimitivesplugin + KirigamiDialogsplugin + KirigamiLayoutsplugin + KirigamiPrivateplugin + ) + # for tests to find this under the name it's actually installed with' + add_library(KF6Kirigamiplugin ALIAS Kirigamiplugin) +else() + target_link_libraries(Kirigami + PUBLIC + KirigamiPlatform + PRIVATE + KirigamiDelegates + KirigamiPrimitives + KirigamiDialogs + KirigamiLayouts + KirigamiPrivate + ) +endif() -install(TARGETS Kirigami EXPORT KirigamiTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS}) +install(TARGETS Kirigami ${_out_targets_1} ${_out_targets_2} EXPORT KirigamiTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS}) install(EXPORT KirigamiTargets DESTINATION ${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6Kirigami FILE KF6KirigamiTargets.cmake NAMESPACE KF6 ) +ecm_finalize_qml_module(Kirigami DESTINATION ${KDE_INSTALL_QMLDIR} EXPORT KirigamiTargets) + +if (ANDROID) + install(FILES Kirigami-android-dependencies.xml + DESTINATION ${KDE_INSTALL_LIBDIR} + RENAME Kirigami_${CMAKE_ANDROID_ARCH_ABI}-android-dependencies.xml + ) +endif() + ecm_qt_install_logging_categories( EXPORT KIRIGAMI FILE kirigami.categories diff --git a/local/recipes/kde/kirigami/source/src/actionhelper.h b/local/recipes/kde/kirigami/source/src/actionhelper.h index 309f5b4f..e993b2e9 100644 --- a/local/recipes/kde/kirigami/source/src/actionhelper.h +++ b/local/recipes/kde/kirigami/source/src/actionhelper.h @@ -10,8 +10,8 @@ class ActionHelper : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_SINGLETON_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT + QML_SINGLETON public: explicit ActionHelper(QObject *parent = nullptr); diff --git a/local/recipes/kde/kirigami/source/src/copyhelper.h b/local/recipes/kde/kirigami/source/src/copyhelper.h index 5d3afd81..e1802bc8 100644 --- a/local/recipes/kde/kirigami/source/src/copyhelper.h +++ b/local/recipes/kde/kirigami/source/src/copyhelper.h @@ -15,8 +15,8 @@ class CopyHelperPrivate : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_SINGLETON_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT + QML_SINGLETON public: Q_INVOKABLE void copyTextToClipboard(const QString &text); }; diff --git a/local/recipes/kde/kirigami/source/src/enums.h b/local/recipes/kde/kirigami/source/src/enums.h index 992c2664..1f189d9b 100644 --- a/local/recipes/kde/kirigami/source/src/enums.h +++ b/local/recipes/kde/kirigami/source/src/enums.h @@ -13,7 +13,7 @@ namespace ApplicationHeaderStyle { Q_NAMESPACE -QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF +QML_ELEMENT enum Status { Auto = 0, @@ -36,7 +36,7 @@ Q_DECLARE_FLAGS(NavigationButtons, NavigationButton) namespace MessageType { Q_NAMESPACE -QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF +QML_ELEMENT enum Type { Information = 0, diff --git a/local/recipes/kde/kirigami/source/src/imagecolors.h b/local/recipes/kde/kirigami/source/src/imagecolors.h index e780dfe4..a42846a8 100644 --- a/local/recipes/kde/kirigami/source/src/imagecolors.h +++ b/local/recipes/kde/kirigami/source/src/imagecolors.h @@ -76,7 +76,7 @@ struct ImageData { class ImageColors : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT /** * The source from which colors should be extracted from. * diff --git a/local/recipes/kde/kirigami/source/src/kirigami_stub.cpp b/local/recipes/kde/kirigami/source/src/kirigami_stub.cpp deleted file mode 100644 index 8a959bea..00000000 --- a/local/recipes/kde/kirigami/source/src/kirigami_stub.cpp +++ /dev/null @@ -1,8 +0,0 @@ -/* - * SPDX-FileCopyrightText: 2026 Red Bear OS - * SPDX-License-Identifier: LGPL-2.1-or-later - */ - -extern "C" void kirigami_redbear_stub() -{ -} diff --git a/local/recipes/kde/kirigami/source/src/layouts/columnview.h b/local/recipes/kde/kirigami/source/src/layouts/columnview.h index dce030d9..0ced4d57 100644 --- a/local/recipes/kde/kirigami/source/src/layouts/columnview.h +++ b/local/recipes/kde/kirigami/source/src/layouts/columnview.h @@ -160,8 +160,8 @@ private: class ColumnView : public QQuickItem { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(ColumnViewAttached) + QML_ELEMENT + QML_ATTACHED(ColumnViewAttached) /** * The strategy to follow while automatically resizing the columns, diff --git a/local/recipes/kde/kirigami/source/src/layouts/displayhint.h b/local/recipes/kde/kirigami/source/src/layouts/displayhint.h index 7b76320b..517924d0 100644 --- a/local/recipes/kde/kirigami/source/src/layouts/displayhint.h +++ b/local/recipes/kde/kirigami/source/src/layouts/displayhint.h @@ -13,8 +13,8 @@ class DisplayHint : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_SINGLETON_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT + QML_SINGLETON public: /** diff --git a/local/recipes/kde/kirigami/source/src/layouts/formlayoutattached.h b/local/recipes/kde/kirigami/source/src/layouts/formlayoutattached.h index 6cf74c39..e43c0467 100644 --- a/local/recipes/kde/kirigami/source/src/layouts/formlayoutattached.h +++ b/local/recipes/kde/kirigami/source/src/layouts/formlayoutattached.h @@ -36,9 +36,9 @@ class QQuickItem; class FormLayoutAttached : public QObject { Q_OBJECT - QML_NAMED_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF(FormData) - QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(FormLayoutAttached) - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("") + QML_NAMED_ELEMENT(FormData) + QML_ATTACHED(FormLayoutAttached) + QML_UNCREATABLE("") /** * The label for a org::kde::kirigami::FormLayout field */ diff --git a/local/recipes/kde/kirigami/source/src/layouts/headerfooterlayout.h b/local/recipes/kde/kirigami/source/src/layouts/headerfooterlayout.h index 0cf0e922..f5c81ca0 100644 --- a/local/recipes/kde/kirigami/source/src/layouts/headerfooterlayout.h +++ b/local/recipes/kde/kirigami/source/src/layouts/headerfooterlayout.h @@ -21,7 +21,7 @@ class HeaderFooterLayout : public QQuickItem { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT /** * @brief This property holds the page header item. * diff --git a/local/recipes/kde/kirigami/source/src/layouts/padding.h b/local/recipes/kde/kirigami/source/src/layouts/padding.h index d3193079..b23c3d3a 100644 --- a/local/recipes/kde/kirigami/source/src/layouts/padding.h +++ b/local/recipes/kde/kirigami/source/src/layouts/padding.h @@ -60,7 +60,7 @@ class PaddingPrivate; class Padding : public QQuickItem { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT /** * @brief This property holds the visual content Item. diff --git a/local/recipes/kde/kirigami/source/src/layouts/sizegroup.h b/local/recipes/kde/kirigami/source/src/layouts/sizegroup.h index 5ec9d60d..6677680d 100644 --- a/local/recipes/kde/kirigami/source/src/layouts/sizegroup.h +++ b/local/recipes/kde/kirigami/source/src/layouts/sizegroup.h @@ -20,8 +20,8 @@ class SizeGroup : public QObject, public QQmlParserStatus { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - + QML_ELEMENT + Q_INTERFACES(QQmlParserStatus) public: enum Mode { diff --git a/local/recipes/kde/kirigami/source/src/layouts/toolbarlayout.h b/local/recipes/kde/kirigami/source/src/layouts/toolbarlayout.h index 096e4bdc..9f174975 100644 --- a/local/recipes/kde/kirigami/source/src/layouts/toolbarlayout.h +++ b/local/recipes/kde/kirigami/source/src/layouts/toolbarlayout.h @@ -48,8 +48,8 @@ private: class ToolBarLayout : public QQuickItem { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(ToolBarLayoutAttached) + QML_ELEMENT + QML_ATTACHED(ToolBarLayoutAttached) /** * The actions this layout should create delegates for. */ diff --git a/local/recipes/kde/kirigami/source/src/mnemonicattached.h b/local/recipes/kde/kirigami/source/src/mnemonicattached.h index aef4dc89..baa04013 100644 --- a/local/recipes/kde/kirigami/source/src/mnemonicattached.h +++ b/local/recipes/kde/kirigami/source/src/mnemonicattached.h @@ -30,9 +30,9 @@ class MnemonicAttached : public QObject { Q_OBJECT - QML_NAMED_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF(MnemonicData) - QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(MnemonicAttached) - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("Cannot create objects of type MnemonicData, use it as an attached property") + QML_NAMED_ELEMENT(MnemonicData) + QML_ATTACHED(MnemonicAttached) + QML_UNCREATABLE("Cannot create objects of type MnemonicData, use it as an attached property") /** * The label of the control we want to compute a mnemonic for, instance * "Label:" or "&Ok" diff --git a/local/recipes/kde/kirigami/source/src/overlayzstackingattached.h b/local/recipes/kde/kirigami/source/src/overlayzstackingattached.h index b5bd1d0e..12ae340d 100644 --- a/local/recipes/kde/kirigami/source/src/overlayzstackingattached.h +++ b/local/recipes/kde/kirigami/source/src/overlayzstackingattached.h @@ -39,10 +39,10 @@ class QQuickItem; class OverlayZStackingAttached : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_NAMED_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF(OverlayZStacking) - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("Cannot create objects of type OverlayZStacking, use it as an attached property") - QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(OverlayZStackingAttached) + QML_ELEMENT + QML_NAMED_ELEMENT(OverlayZStacking) + QML_UNCREATABLE("Cannot create objects of type OverlayZStacking, use it as an attached property") + QML_ATTACHED(OverlayZStackingAttached) /** * An optimal z-index that attachee popup should bind to. */ diff --git a/local/recipes/kde/kirigami/source/src/pagepool.h b/local/recipes/kde/kirigami/source/src/pagepool.h index fd9fc30a..19364f1e 100644 --- a/local/recipes/kde/kirigami/source/src/pagepool.h +++ b/local/recipes/kde/kirigami/source/src/pagepool.h @@ -22,7 +22,7 @@ class PagePool : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT /** * The last url that was loaded with @loadPage. Useful if you need * to have a "checked" state to buttons or list items that diff --git a/local/recipes/kde/kirigami/source/src/platform/CMakeLists.txt b/local/recipes/kde/kirigami/source/src/platform/CMakeLists.txt new file mode 100644 index 00000000..390d0dce --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/CMakeLists.txt @@ -0,0 +1,172 @@ +add_library(KirigamiPlatform) +add_library(KF6::KirigamiPlatform ALIAS KirigamiPlatform) + +ecm_add_qml_module(KirigamiPlatform + URI "org.kde.kirigami.platform" + VERSION 2.0 + GENERATE_PLUGIN_SOURCE + INSTALLED_PLUGIN_TARGET KF6::KirigamiPlatformplugin + DEPENDENCIES QtQuick +) + +set_target_properties(KirigamiPlatform PROPERTIES + VERSION ${PROJECT_VERSION} + SOVERSION 6 + EXPORT_NAME "KirigamiPlatform" +) + +target_sources(KirigamiPlatform PRIVATE + platformtheme.cpp + platformtheme.h + basictheme.cpp + basictheme_p.h + inputmethod.cpp + inputmethod.h + platformpluginfactory.cpp + platformpluginfactory.h + tabletmodewatcher.cpp + tabletmodewatcher.h + settings.cpp + settings.h + smoothscrollwatcher.cpp + smoothscrollwatcher.h + styleselector.cpp + styleselector.h + units.cpp + units.h + virtualkeyboardwatcher.cpp + virtualkeyboardwatcher.h + colorutils.cpp + colorutils.h +) + +set(libkirigami_extra_sources "") + +if (WITH_DBUS) + set_source_files_properties(org.freedesktop.portal.Settings.xml PROPERTIES INCLUDE dbustypes.h) + qt_add_dbus_interface(libkirigami_extra_sources org.freedesktop.portal.Settings.xml settings_interface) + set(LIBKIRIGAMKI_EXTRA_LIBS Qt6::DBus) +endif() + +target_sources(KirigamiPlatform PRIVATE ${libkirigami_extra_sources}) + +ecm_qt_declare_logging_category(KirigamiPlatform + HEADER kirigamiplatform_logging.h + IDENTIFIER KirigamiPlatform + CATEGORY_NAME kf.kirigami.platform + DESCRIPTION "Kirigami Platform" + DEFAULT_SEVERITY Warning + EXPORT KIRIGAMI +) + +ecm_setup_version(PROJECT + VARIABLE_PREFIX KIRIGAMIPLATFORM + VERSION_HEADER "${CMAKE_CURRENT_BINARY_DIR}/kirigamiplatform_version.h" + PACKAGE_VERSION_FILE "${CMAKE_CURRENT_BINARY_DIR}/KF6KirigamiPlatformConfigVersion.cmake" + SOVERSION 6 +) + +ecm_generate_export_header(KirigamiPlatform + VERSION ${PROJECT_VERSION} + BASE_NAME KirigamiPlatform + USE_VERSION_HEADER + DEPRECATION_VERSIONS +) + +target_include_directories(KirigamiPlatform + PUBLIC + "$" + "$" + "$" +) + +target_link_libraries(KirigamiPlatform + PUBLIC + Qt6::Core + Qt6::Qml + Qt6::Quick + PRIVATE + Qt6::GuiPrivate + Qt6::QuickControls2 + ${LIBKIRIGAMKI_EXTRA_LIBS} +) + +ecm_generate_headers(KirigamiPlatform_CamelCase_HEADERS + HEADER_NAMES + PlatformTheme + PlatformPluginFactory + StyleSelector + TabletModeWatcher + Units + VirtualKeyboardWatcher + REQUIRED_HEADERS KirigamiPlatform_HEADERS +) + +configure_package_config_file( + "${CMAKE_CURRENT_SOURCE_DIR}/KF6KirigamiPlatformConfig.cmake.in" + "${CMAKE_CURRENT_BINARY_DIR}/KF6KirigamiPlatformConfig.cmake" + INSTALL_DESTINATION ${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6KirigamiPlatform + PATH_VARS CMAKE_INSTALL_PREFIX +) + +install(FILES + "${CMAKE_CURRENT_BINARY_DIR}/KF6KirigamiPlatformConfig.cmake" + "${CMAKE_CURRENT_BINARY_DIR}/KF6KirigamiPlatformConfigVersion.cmake" + DESTINATION ${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6KirigamiPlatform + COMPONENT Devel +) + +install(TARGETS KirigamiPlatform EXPORT KF6KirigamiPlatformTargets ${KF_INSTALL_TARGETS_DEFAULT_ARGS}) + +install(EXPORT KF6KirigamiPlatformTargets + DESTINATION ${KDE_INSTALL_CMAKEPACKAGEDIR}/KF6KirigamiPlatform + FILE KF6KirigamiPlatformTargets.cmake + NAMESPACE KF6:: +) + +install(FILES + ${KirigamiPlatform_HEADERS} + ${CMAKE_CURRENT_BINARY_DIR}/kirigamiplatform_export.h + ${CMAKE_CURRENT_BINARY_DIR}/kirigamiplatform_version.h + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/Kirigami/Platform # prefix matching C++ namespace + COMPONENT Devel +) +install(FILES + ${KirigamiPlatform_CamelCase_HEADERS} + DESTINATION ${KDE_INSTALL_INCLUDEDIR_KF}/Kirigami/Platform # prefix matching C++ namespace + COMPONENT Devel +) + +ecm_qt_install_logging_categories( + EXPORT KIRIGAMI + FILE kirigami.categories + DESTINATION ${KDE_INSTALL_LOGGINGCATEGORIESDIR} +) + +ecm_finalize_qml_module(KirigamiPlatform EXPORT KF6KirigamiPlatformTargets) + +if(BUILD_QCH) + ecm_add_qch( + KF6Kirigami_QCH + NAME KirigamiPlatform + BASE_NAME KF6KirigamiPlatform + VERSION ${KF_VERSION} + ORG_DOMAIN org.kde + SOURCES # using only public headers, to cover only public API + platformpluginfactory.h + platformtheme.h + tabletmodewatcher.h + units.h + virtualkeyboardwatcher.h + MD_MAINPAGE "${CMAKE_CURRENT_SOURCE_DIR}/README.md" + LINK_QCHS + Qt6Core_QCH + BLANK_MACROS + KIRIGAMIPLATFORM_EXPORT + KIRIGAMIPLATFORM_DEPRECATED + TAGFILE_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + QCH_INSTALL_DESTINATION ${KDE_INSTALL_QTQCHDIR} + COMPONENT Devel + ) +endif() + diff --git a/local/recipes/kde/kirigami/source/src/platform/KF6KirigamiPlatformConfig.cmake.in b/local/recipes/kde/kirigami/source/src/platform/KF6KirigamiPlatformConfig.cmake.in new file mode 100644 index 00000000..9d78e00b --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/KF6KirigamiPlatformConfig.cmake.in @@ -0,0 +1,14 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) +find_dependency(Qt6Core @REQUIRED_QT_VERSION@) +find_dependency(Qt6Qml @REQUIRED_QT_VERSION@) +find_dependency(Qt6Quick @REQUIRED_QT_VERSION@) + +# Any changes in this ".cmake" file will be overwritten by CMake, the source is the ".cmake.in" file. + +include("${CMAKE_CURRENT_LIST_DIR}/KF6KirigamiPlatformTargets.cmake") + +set(KirigamiPlatform_INSTALL_PREFIX "@PACKAGE_CMAKE_INSTALL_PREFIX@") + +@PACKAGE_INCLUDE_QCHTARGETS@ diff --git a/local/recipes/kde/kirigami/source/src/platform/README.md b/local/recipes/kde/kirigami/source/src/platform/README.md new file mode 100644 index 00000000..2f7dd4c6 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/README.md @@ -0,0 +1,12 @@ +# Kirigami Platform Submodule + +This module contains platform integration types. + +# What Goes Here + +The following criteria should be used to determine if a type belongs here: + +- No visual items. +- No dependencies on other Kirigami submodules. +- Types that help expose underlying platform properties like colors, device + state, etc. diff --git a/local/recipes/kde/kirigami/source/src/platform/basictheme.cpp b/local/recipes/kde/kirigami/source/src/platform/basictheme.cpp new file mode 100644 index 00000000..ddbe45a3 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/basictheme.cpp @@ -0,0 +1,207 @@ +/* + * SPDX-FileCopyrightText: 2017 Marco Martin + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "basictheme_p.h" +#include "styleselector.h" + +#include +#include + +#include "kirigamiplatform_logging.h" + +namespace Kirigami +{ +namespace Platform +{ + +BasicThemeDefinition::BasicThemeDefinition(QObject *parent) + : QObject(parent) +{ + defaultFont = qGuiApp->font(); + + smallFont = qGuiApp->font(); + smallFont.setPointSize(smallFont.pointSize() - 2); +} + +void BasicThemeDefinition::syncToQml(PlatformTheme *object) +{ + auto item = qobject_cast(object->parent()); + if (item && qmlAttachedPropertiesObject(item, false) == object) { + Q_EMIT sync(item); + } +} + +BasicThemeInstance::BasicThemeInstance(QObject *parent) + : QObject(parent) +{ +} + +BasicThemeDefinition &BasicThemeInstance::themeDefinition(QQmlEngine *engine) +{ + if (m_themeDefinition) { + return *m_themeDefinition; + } + + auto themeUrl = StyleSelector::componentUrl(QStringLiteral("Theme.qml")); + QQmlComponent component(engine); + component.loadUrl(themeUrl); + + if (auto themeDefinition = qobject_cast(component.create())) { + m_themeDefinition.reset(themeDefinition); + } else { + const auto errors = component.errors(); + for (auto error : errors) { + qCWarning(KirigamiPlatform) << error.toString(); + } + + qCWarning(KirigamiPlatform) << "Invalid Theme file, using default Basic theme."; + m_themeDefinition = std::make_unique(); + } + + connect(m_themeDefinition.get(), &BasicThemeDefinition::changed, this, &BasicThemeInstance::onDefinitionChanged); + + return *m_themeDefinition; +} + +void BasicThemeInstance::onDefinitionChanged() +{ + for (auto watcher : std::as_const(watchers)) { + watcher->sync(); + } +} + +Q_GLOBAL_STATIC(BasicThemeInstance, basicThemeInstance) + +BasicTheme::BasicTheme(QObject *parent) + : PlatformTheme(parent) +{ + basicThemeInstance()->watchers.append(this); + + sync(); +} + +BasicTheme::~BasicTheme() +{ + basicThemeInstance()->watchers.removeOne(this); +} + +void BasicTheme::sync() +{ + PlatformThemeChangeTracker tracker{this}; + + auto &definition = basicThemeInstance()->themeDefinition(qmlEngine(parent())); + + switch (colorSet()) { + case BasicTheme::Button: + setTextColor(tint(definition.buttonTextColor)); + setBackgroundColor(tint(definition.buttonBackgroundColor)); + setAlternateBackgroundColor(tint(definition.buttonAlternateBackgroundColor)); + setHoverColor(tint(definition.buttonHoverColor)); + setFocusColor(tint(definition.buttonFocusColor)); + break; + case BasicTheme::View: + setTextColor(tint(definition.viewTextColor)); + setBackgroundColor(tint(definition.viewBackgroundColor)); + setAlternateBackgroundColor(tint(definition.viewAlternateBackgroundColor)); + setHoverColor(tint(definition.viewHoverColor)); + setFocusColor(tint(definition.viewFocusColor)); + break; + case BasicTheme::Selection: + setTextColor(tint(definition.selectionTextColor)); + setBackgroundColor(tint(definition.selectionBackgroundColor)); + setAlternateBackgroundColor(tint(definition.selectionAlternateBackgroundColor)); + setHoverColor(tint(definition.selectionHoverColor)); + setFocusColor(tint(definition.selectionFocusColor)); + break; + case BasicTheme::Tooltip: + setTextColor(tint(definition.tooltipTextColor)); + setBackgroundColor(tint(definition.tooltipBackgroundColor)); + setAlternateBackgroundColor(tint(definition.tooltipAlternateBackgroundColor)); + setHoverColor(tint(definition.tooltipHoverColor)); + setFocusColor(tint(definition.tooltipFocusColor)); + break; + case BasicTheme::Complementary: + setTextColor(tint(definition.complementaryTextColor)); + setBackgroundColor(tint(definition.complementaryBackgroundColor)); + setAlternateBackgroundColor(tint(definition.complementaryAlternateBackgroundColor)); + setHoverColor(tint(definition.complementaryHoverColor)); + setFocusColor(tint(definition.complementaryFocusColor)); + break; + case BasicTheme::Window: + default: + setTextColor(tint(definition.textColor)); + setBackgroundColor(tint(definition.backgroundColor)); + setAlternateBackgroundColor(tint(definition.alternateBackgroundColor)); + setHoverColor(tint(definition.hoverColor)); + setFocusColor(tint(definition.focusColor)); + break; + } + + setDisabledTextColor(tint(definition.disabledTextColor)); + setHighlightColor(tint(definition.highlightColor)); + setHighlightedTextColor(tint(definition.highlightedTextColor)); + setActiveTextColor(tint(definition.activeTextColor)); + setActiveBackgroundColor(tint(definition.activeBackgroundColor)); + setLinkColor(tint(definition.linkColor)); + setLinkBackgroundColor(tint(definition.linkBackgroundColor)); + setVisitedLinkColor(tint(definition.visitedLinkColor)); + setVisitedLinkBackgroundColor(tint(definition.visitedLinkBackgroundColor)); + setNegativeTextColor(tint(definition.negativeTextColor)); + setNegativeBackgroundColor(tint(definition.negativeBackgroundColor)); + setNeutralTextColor(tint(definition.neutralTextColor)); + setNeutralBackgroundColor(tint(definition.neutralBackgroundColor)); + setPositiveTextColor(tint(definition.positiveTextColor)); + setPositiveBackgroundColor(tint(definition.positiveBackgroundColor)); + + setDefaultFont(definition.defaultFont); + setSmallFont(definition.smallFont); +} + +bool BasicTheme::event(QEvent *event) +{ + if (event->type() == PlatformThemeEvents::DataChangedEvent::type) { + sync(); + } + + if (event->type() == PlatformThemeEvents::ColorSetChangedEvent::type) { + sync(); + } + + if (event->type() == PlatformThemeEvents::ColorGroupChangedEvent::type) { + sync(); + } + + if (event->type() == PlatformThemeEvents::ColorChangedEvent::type) { + basicThemeInstance()->themeDefinition(qmlEngine(parent())).syncToQml(this); + } + + if (event->type() == PlatformThemeEvents::FontChangedEvent::type) { + basicThemeInstance()->themeDefinition(qmlEngine(parent())).syncToQml(this); + } + + return PlatformTheme::event(event); +} + +QColor BasicTheme::tint(const QColor &color) +{ + if (QQuickItem *item = qobject_cast(parent()); item && !item->isEnabled()) { + return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF() * 0.8); + } + switch (colorGroup()) { + case PlatformTheme::Inactive: + return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF()); + case PlatformTheme::Disabled: + return QColor::fromHsvF(color.hueF(), color.saturationF() * 0.5, color.valueF() * 0.8); + default: + return color; + } +} + +} +} + +#include "moc_basictheme_p.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/basictheme_p.h b/local/recipes/kde/kirigami/source/src/platform/basictheme_p.h new file mode 100644 index 00000000..b334b03c --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/basictheme_p.h @@ -0,0 +1,197 @@ +/* + * SPDX-FileCopyrightText: 2017 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef BASICTHEME_H +#define BASICTHEME_H + +#include "platformtheme.h" + +#include "kirigamiplatform_export.h" + +namespace Kirigami +{ +namespace Platform +{ +class BasicTheme; + +class KIRIGAMIPLATFORM_EXPORT BasicThemeDefinition : public QObject +{ + Q_OBJECT + QML_ELEMENT + + Q_PROPERTY(QColor textColor MEMBER textColor NOTIFY changed FINAL) + Q_PROPERTY(QColor disabledTextColor MEMBER disabledTextColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor highlightColor MEMBER highlightColor NOTIFY changed FINAL) + Q_PROPERTY(QColor highlightedTextColor MEMBER highlightedTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor backgroundColor MEMBER backgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor alternateBackgroundColor MEMBER alternateBackgroundColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor focusColor MEMBER focusColor NOTIFY changed FINAL) + Q_PROPERTY(QColor hoverColor MEMBER hoverColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor activeTextColor MEMBER activeTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor activeBackgroundColor MEMBER activeBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor linkColor MEMBER linkColor NOTIFY changed FINAL) + Q_PROPERTY(QColor linkBackgroundColor MEMBER linkBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor visitedLinkColor MEMBER visitedLinkColor NOTIFY changed FINAL) + Q_PROPERTY(QColor visitedLinkBackgroundColor MEMBER visitedLinkBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor negativeTextColor MEMBER negativeTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor negativeBackgroundColor MEMBER negativeBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor neutralTextColor MEMBER neutralTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor neutralBackgroundColor MEMBER neutralBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor positiveTextColor MEMBER positiveTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor positiveBackgroundColor MEMBER positiveBackgroundColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor buttonTextColor MEMBER buttonTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor buttonBackgroundColor MEMBER buttonBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor buttonAlternateBackgroundColor MEMBER buttonAlternateBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor buttonHoverColor MEMBER buttonHoverColor NOTIFY changed FINAL) + Q_PROPERTY(QColor buttonFocusColor MEMBER buttonFocusColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor viewTextColor MEMBER viewTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor viewBackgroundColor MEMBER viewBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor viewAlternateBackgroundColor MEMBER viewAlternateBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor viewHoverColor MEMBER viewHoverColor NOTIFY changed FINAL) + Q_PROPERTY(QColor viewFocusColor MEMBER viewFocusColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor selectionTextColor MEMBER selectionTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor selectionBackgroundColor MEMBER selectionBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor selectionAlternateBackgroundColor MEMBER selectionAlternateBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor selectionHoverColor MEMBER selectionHoverColor NOTIFY changed FINAL) + Q_PROPERTY(QColor selectionFocusColor MEMBER selectionFocusColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor tooltipTextColor MEMBER tooltipTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor tooltipBackgroundColor MEMBER tooltipBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor tooltipAlternateBackgroundColor MEMBER tooltipAlternateBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor tooltipHoverColor MEMBER tooltipHoverColor NOTIFY changed FINAL) + Q_PROPERTY(QColor tooltipFocusColor MEMBER tooltipFocusColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor complementaryTextColor MEMBER complementaryTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor complementaryBackgroundColor MEMBER complementaryBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor complementaryAlternateBackgroundColor MEMBER complementaryAlternateBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor complementaryHoverColor MEMBER complementaryHoverColor NOTIFY changed FINAL) + Q_PROPERTY(QColor complementaryFocusColor MEMBER complementaryFocusColor NOTIFY changed FINAL) + + Q_PROPERTY(QColor headerTextColor MEMBER headerTextColor NOTIFY changed FINAL) + Q_PROPERTY(QColor headerBackgroundColor MEMBER headerBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor headerAlternateBackgroundColor MEMBER headerAlternateBackgroundColor NOTIFY changed FINAL) + Q_PROPERTY(QColor headerHoverColor MEMBER headerHoverColor NOTIFY changed FINAL) + Q_PROPERTY(QColor headerFocusColor MEMBER headerFocusColor NOTIFY changed FINAL) + + Q_PROPERTY(QFont defaultFont MEMBER defaultFont NOTIFY changed FINAL) + Q_PROPERTY(QFont smallFont MEMBER smallFont NOTIFY changed FINAL) + +public: + explicit BasicThemeDefinition(QObject *parent = nullptr); + + virtual void syncToQml(PlatformTheme *object); + + QColor textColor{0x31363b}; + QColor disabledTextColor{0x31, 0x36, 0x3b, 0x99}; + + QColor highlightColor{0x2196F3}; + QColor highlightedTextColor{0xeff0fa}; + QColor backgroundColor{0xeff0f1}; + QColor alternateBackgroundColor{0xbdc3c7}; + + QColor focusColor{0x2196F3}; + QColor hoverColor{0x2196F3}; + + QColor activeTextColor{0x0176D3}; + QColor activeBackgroundColor{0x0176D3}; + QColor linkColor{0x2196F3}; + QColor linkBackgroundColor{0x2196F3}; + QColor visitedLinkColor{0x2196F3}; + QColor visitedLinkBackgroundColor{0x2196F3}; + QColor negativeTextColor{0xDA4453}; + QColor negativeBackgroundColor{0xDA4453}; + QColor neutralTextColor{0xF67400}; + QColor neutralBackgroundColor{0xF67400}; + QColor positiveTextColor{0x27AE60}; + QColor positiveBackgroundColor{0x27AE60}; + + QColor buttonTextColor{0x31363b}; + QColor buttonBackgroundColor{0xeff0f1}; + QColor buttonAlternateBackgroundColor{0xbdc3c7}; + QColor buttonHoverColor{0x2196F3}; + QColor buttonFocusColor{0x2196F3}; + + QColor viewTextColor{0x31363b}; + QColor viewBackgroundColor{0xfcfcfc}; + QColor viewAlternateBackgroundColor{0xeff0f1}; + QColor viewHoverColor{0x2196F3}; + QColor viewFocusColor{0x2196F3}; + + QColor selectionTextColor{0xeff0fa}; + QColor selectionBackgroundColor{0x2196F3}; + QColor selectionAlternateBackgroundColor{0x1d99f3}; + QColor selectionHoverColor{0x2196F3}; + QColor selectionFocusColor{0x2196F3}; + + QColor tooltipTextColor{0xeff0f1}; + QColor tooltipBackgroundColor{0x31363b}; + QColor tooltipAlternateBackgroundColor{0x4d4d4d}; + QColor tooltipHoverColor{0x2196F3}; + QColor tooltipFocusColor{0x2196F3}; + + QColor complementaryTextColor{0xeff0f1}; + QColor complementaryBackgroundColor{0x31363b}; + QColor complementaryAlternateBackgroundColor{0x3b4045}; + QColor complementaryHoverColor{0x2196F3}; + QColor complementaryFocusColor{0x2196F3}; + + QColor headerTextColor{0x232629}; + QColor headerBackgroundColor{0xe3e5e7}; + QColor headerAlternateBackgroundColor{0xeff0f1}; + QColor headerHoverColor{0x2196F3}; + QColor headerFocusColor{0x93cee9}; + + QFont defaultFont; + QFont smallFont; + + Q_SIGNAL void changed(); + Q_SIGNAL void sync(QQuickItem *object); +}; + +class BasicThemeInstance : public QObject +{ + Q_OBJECT + +public: + explicit BasicThemeInstance(QObject *parent = nullptr); + + BasicThemeDefinition &themeDefinition(QQmlEngine *engine); + + QList watchers; + +private: + void onDefinitionChanged(); + + std::unique_ptr m_themeDefinition; +}; + +class BasicTheme : public PlatformTheme +{ + Q_OBJECT + +public: + explicit BasicTheme(QObject *parent = nullptr); + ~BasicTheme() override; + + void sync(); + +protected: + bool event(QEvent *event) override; + +private: + QColor tint(const QColor &color); +}; + +} +} + +#endif // BASICTHEME_H diff --git a/local/recipes/kde/kirigami/source/src/platform/colorutils.cpp b/local/recipes/kde/kirigami/source/src/platform/colorutils.cpp new file mode 100644 index 00000000..5a3bc7d6 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/colorutils.cpp @@ -0,0 +1,319 @@ +/* + * SPDX-FileCopyrightText: 2020 Carson Black + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "colorutils.h" + +#include +#include +#include +#include + +#include "kirigamiplatform_logging.h" + +ColorUtils::ColorUtils(QObject *parent) + : QObject(parent) +{ +} + +ColorUtils::Brightness ColorUtils::brightnessForColor(const QColor &color) +{ + auto luma = [](const QColor &color) { + return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255; + }; + + return luma(color) > 0.5 ? ColorUtils::Brightness::Light : ColorUtils::Brightness::Dark; +} + +qreal ColorUtils::grayForColor(const QColor &color) +{ + return (0.299 * color.red() + 0.587 * color.green() + 0.114 * color.blue()) / 255; +} + +QColor ColorUtils::alphaBlend(const QColor &foreground, const QColor &background) +{ + const auto foregroundAlpha = foreground.alpha(); + const auto inverseForegroundAlpha = 0xff - foregroundAlpha; + const auto backgroundAlpha = background.alpha(); + + if (foregroundAlpha == 0x00) { + return background; + } + + if (backgroundAlpha == 0xff) { + return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseForegroundAlpha * background.red()), + (foregroundAlpha * foreground.green()) + (inverseForegroundAlpha * background.green()), + (foregroundAlpha * foreground.blue()) + (inverseForegroundAlpha * background.blue()), + 0xff); + } else { + const auto inverseBackgroundAlpha = (backgroundAlpha * inverseForegroundAlpha) / 255; + const auto finalAlpha = foregroundAlpha + inverseBackgroundAlpha; + Q_ASSERT(finalAlpha != 0x00); + return QColor::fromRgb((foregroundAlpha * foreground.red()) + (inverseBackgroundAlpha * background.red()), + (foregroundAlpha * foreground.green()) + (inverseBackgroundAlpha * background.green()), + (foregroundAlpha * foreground.blue()) + (inverseBackgroundAlpha * background.blue()), + finalAlpha); + } +} + +QColor ColorUtils::linearInterpolation(const QColor &one, const QColor &two, double balance) +{ + auto linearlyInterpolateDouble = [](double one, double two, double factor) { + return one + (two - one) * factor; + }; + + // QColor returns -1 when hue is undefined, which happens whenever + // saturation is 0. When this happens, interpolation can go wrong so handle + // it by first trying to use the other color's hue and if that is also -1, + // just skip the hue interpolation by using 0 for both. + auto sourceHue = std::max(one.hueF() > 0.0 ? one.hueF() : two.hueF(), 0.0f); + auto targetHue = std::max(two.hueF() > 0.0 ? two.hueF() : one.hueF(), 0.0f); + + auto hue = std::fmod(linearlyInterpolateDouble(sourceHue, targetHue, balance), 1.0); + auto saturation = std::clamp(linearlyInterpolateDouble(one.saturationF(), two.saturationF(), balance), 0.0, 1.0); + auto value = std::clamp(linearlyInterpolateDouble(one.valueF(), two.valueF(), balance), 0.0, 1.0); + auto alpha = std::clamp(linearlyInterpolateDouble(one.alphaF(), two.alphaF(), balance), 0.0, 1.0); + + return QColor::fromHsvF(hue, saturation, value, alpha); +} + +// Some private things for the adjust, change, and scale properties +struct ParsedAdjustments { + double red = 0.0; + double green = 0.0; + double blue = 0.0; + + double hue = 0.0; + double saturation = 0.0; + double value = 0.0; + + double alpha = 0.0; +}; + +ParsedAdjustments parseAdjustments(const QJSValue &value) +{ + ParsedAdjustments parsed; + + auto checkProperty = [](const QJSValue &value, const QString &property) { + if (value.hasProperty(property)) { + auto val = value.property(property); + if (val.isNumber()) { + return QVariant::fromValue(val.toNumber()); + } + } + return QVariant(); + }; + + std::vector> items{{QStringLiteral("red"), parsed.red}, + {QStringLiteral("green"), parsed.green}, + {QStringLiteral("blue"), parsed.blue}, + // + {QStringLiteral("hue"), parsed.hue}, + {QStringLiteral("saturation"), parsed.saturation}, + {QStringLiteral("value"), parsed.value}, + // + {QStringLiteral("alpha"), parsed.alpha}}; + + for (const auto &item : items) { + auto val = checkProperty(value, item.first); + if (val.isValid()) { + item.second = val.toDouble(); + } + } + + if ((parsed.red || parsed.green || parsed.blue) && (parsed.hue || parsed.saturation || parsed.value)) { + qCCritical(KirigamiPlatform) << "It is an error to have both RGB and HSV values in an adjustment."; + } + + return parsed; +} + +QColor ColorUtils::adjustColor(const QColor &color, const QJSValue &adjustments) +{ + auto adjusts = parseAdjustments(adjustments); + + if (qBound(-360.0, adjusts.hue, 360.0) != adjusts.hue) { + qCCritical(KirigamiPlatform) << "Hue is out of bounds"; + } + if (qBound(-255.0, adjusts.red, 255.0) != adjusts.red) { + qCCritical(KirigamiPlatform) << "Red is out of bounds"; + } + if (qBound(-255.0, adjusts.green, 255.0) != adjusts.green) { + qCCritical(KirigamiPlatform) << "Green is out of bounds"; + } + if (qBound(-255.0, adjusts.blue, 255.0) != adjusts.blue) { + qCCritical(KirigamiPlatform) << "Green is out of bounds"; + } + if (qBound(-255.0, adjusts.saturation, 255.0) != adjusts.saturation) { + qCCritical(KirigamiPlatform) << "Saturation is out of bounds"; + } + if (qBound(-255.0, adjusts.value, 255.0) != adjusts.value) { + qCCritical(KirigamiPlatform) << "Value is out of bounds"; + } + if (qBound(-255.0, adjusts.alpha, 255.0) != adjusts.alpha) { + qCCritical(KirigamiPlatform) << "Alpha is out of bounds"; + } + + auto copy = color; + + if (adjusts.alpha) { + copy.setAlpha(qBound(0.0, copy.alpha() + adjusts.alpha, 255.0)); + } + + if (adjusts.red || adjusts.green || adjusts.blue) { + copy.setRed(qBound(0.0, copy.red() + adjusts.red, 255.0)); + copy.setGreen(qBound(0.0, copy.green() + adjusts.green, 255.0)); + copy.setBlue(qBound(0.0, copy.blue() + adjusts.blue, 255.0)); + } else if (adjusts.hue || adjusts.saturation || adjusts.value) { + copy.setHsv(std::fmod(copy.hue() + adjusts.hue, 360.0), + qBound(0.0, copy.saturation() + adjusts.saturation, 255.0), + qBound(0.0, copy.value() + adjusts.value, 255.0), + copy.alpha()); + } + + return copy; +} + +QColor ColorUtils::scaleColor(const QColor &color, const QJSValue &adjustments) +{ + auto adjusts = parseAdjustments(adjustments); + auto copy = color; + + if (qBound(-100.0, adjusts.red, 100.00) != adjusts.red) { + qCCritical(KirigamiPlatform) << "Red is out of bounds"; + } + if (qBound(-100.0, adjusts.green, 100.00) != adjusts.green) { + qCCritical(KirigamiPlatform) << "Green is out of bounds"; + } + if (qBound(-100.0, adjusts.blue, 100.00) != adjusts.blue) { + qCCritical(KirigamiPlatform) << "Blue is out of bounds"; + } + if (qBound(-100.0, adjusts.saturation, 100.00) != adjusts.saturation) { + qCCritical(KirigamiPlatform) << "Saturation is out of bounds"; + } + if (qBound(-100.0, adjusts.value, 100.00) != adjusts.value) { + qCCritical(KirigamiPlatform) << "Value is out of bounds"; + } + if (qBound(-100.0, adjusts.alpha, 100.00) != adjusts.alpha) { + qCCritical(KirigamiPlatform) << "Alpha is out of bounds"; + } + + if (adjusts.hue != 0) { + qCCritical(KirigamiPlatform) << "Hue cannot be scaled"; + } + + auto shiftToAverage = [](double current, double factor) { + auto scale = qBound(-100.0, factor, 100.0) / 100; + return current + (scale > 0 ? 255 - current : current) * scale; + }; + + if (adjusts.alpha) { + copy.setAlpha(qBound(0.0, shiftToAverage(copy.alpha(), adjusts.alpha), 255.0)); + } + + if (adjusts.red || adjusts.green || adjusts.blue) { + copy.setRed(qBound(0.0, shiftToAverage(copy.red(), adjusts.red), 255.0)); + copy.setGreen(qBound(0.0, shiftToAverage(copy.green(), adjusts.green), 255.0)); + copy.setBlue(qBound(0.0, shiftToAverage(copy.blue(), adjusts.blue), 255.0)); + } else { + copy.setHsv(copy.hue(), + qBound(0.0, shiftToAverage(copy.saturation(), adjusts.saturation), 255.0), + qBound(0.0, shiftToAverage(copy.value(), adjusts.value), 255.0), + copy.alpha()); + } + + return copy; +} + +QColor ColorUtils::tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha) +{ + qreal tintAlpha = tintColor.alphaF() * alpha; + qreal inverseAlpha = 1.0 - tintAlpha; + + if (qFuzzyCompare(tintAlpha, 1.0)) { + return tintColor; + } else if (qFuzzyIsNull(tintAlpha)) { + return targetColor; + } + + return QColor::fromRgbF(tintColor.redF() * tintAlpha + targetColor.redF() * inverseAlpha, + tintColor.greenF() * tintAlpha + targetColor.greenF() * inverseAlpha, + tintColor.blueF() * tintAlpha + targetColor.blueF() * inverseAlpha, + tintAlpha + inverseAlpha * targetColor.alphaF()); +} + +ColorUtils::XYZColor ColorUtils::colorToXYZ(const QColor &color) +{ + // http://wiki.nuaj.net/index.php/Color_Transforms#RGB_.E2.86.92_XYZ + qreal r = color.redF(); + qreal g = color.greenF(); + qreal b = color.blueF(); + // Apply gamma correction (i.e. conversion to linear-space) + auto correct = [](qreal &v) { + if (v > 0.04045) { + v = std::pow((v + 0.055) / 1.055, 2.4); + } else { + v = v / 12.92; + } + }; + + correct(r); + correct(g); + correct(b); + + // Observer. = 2°, Illuminant = D65 + const qreal x = r * 0.4124 + g * 0.3576 + b * 0.1805; + const qreal y = r * 0.2126 + g * 0.7152 + b * 0.0722; + const qreal z = r * 0.0193 + g * 0.1192 + b * 0.9505; + + return XYZColor{x, y, z}; +} + +ColorUtils::LabColor ColorUtils::colorToLab(const QColor &color) +{ + // First: convert to XYZ + const auto xyz = colorToXYZ(color); + + // Second: convert from XYZ to L*a*b + qreal x = xyz.x / 0.95047; // Observer= 2°, Illuminant= D65 + qreal y = xyz.y / 1.0; + qreal z = xyz.z / 1.08883; + + auto pivot = [](qreal &v) { + if (v > 0.008856) { + v = std::pow(v, 1.0 / 3.0); + } else { + v = (7.787 * v) + (16.0 / 116.0); + } + }; + + pivot(x); + pivot(y); + pivot(z); + + LabColor labColor; + labColor.l = std::max(0.0, (116 * y) - 16); + labColor.a = 500 * (x - y); + labColor.b = 200 * (y - z); + + return labColor; +} + +qreal ColorUtils::chroma(const QColor &color) +{ + LabColor labColor = colorToLab(color); + + // Chroma is hypotenuse of a and b + return sqrt(pow(labColor.a, 2) + pow(labColor.b, 2)); +} + +qreal ColorUtils::luminance(const QColor &color) +{ + const auto &xyz = colorToXYZ(color); + // Luminance is equal to Y + return xyz.y; +} + +#include "moc_colorutils.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/colorutils.h b/local/recipes/kde/kirigami/source/src/platform/colorutils.h new file mode 100644 index 00000000..26a471b5 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/colorutils.h @@ -0,0 +1,218 @@ +/* + * SPDX-FileCopyrightText: 2020 Carson Black + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#pragma once + +#include +#include +#include +#include + +#include "kirigamiplatform_export.h" + +/** + * Utilities for processing items to obtain colors and information useful for + * UIs that need to adjust to variable elements. + */ +class KIRIGAMIPLATFORM_EXPORT ColorUtils : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON +public: + /** + * Describes the contrast of an item. + */ + enum Brightness { + Dark, /**< The item is dark and requires a light foreground color to achieve readable contrast. */ + Light, /**< The item is light and requires a dark foreground color to achieve readable contrast. */ + }; + Q_ENUM(Brightness) + + explicit ColorUtils(QObject *parent = nullptr); + + /** + * Returns whether a color is bright or dark. + * + * @code{.qml} + * import QtQuick + * import org.kde.kirigami as Kirigami + * + * Kirigami.Heading { + * text: { + * if (Kirigami.ColorUtils.brightnessForColor("pink") == Kirigami.ColorUtils.Light) { + * return "The color is light" + * } else { + * return "The color is dark" + * } + * } + * } + * @endcode + * + * @since 5.69 + * @since org.kde.kirigami 2.12 + */ + Q_INVOKABLE ColorUtils::Brightness brightnessForColor(const QColor &color); + + /** + * Same Algorithm as brightnessForColor but returns a 0 to 1 value for an + * estimate of the equivalent gray light value (luma). + * 0 as full black, 1 as full white and 0.5 equivalent to a 50% gray. + * + * @since 5.81 + * @since org.kde.kirigami 2.16 + */ + Q_INVOKABLE qreal grayForColor(const QColor &color); + + /** + * Returns the result of overlaying the foreground color on the background + * color. + * + * @param foreground The color to overlay on the background. + * + * @param background The color to overlay the foreground on. + * + * @code{.qml} + * import QtQuick + * import org.kde.kirigami as Kirigami + * + * Rectangle { + * color: Kirigami.ColorUtils.alphaBlend(Qt.rgba(0, 0, 0, 0.5), Qt.rgba(1, 1, 1, 1)) + * } + * @endcode + * + * @since 5.69 + * @since org.kde.kirigami 2.12 + */ + Q_INVOKABLE QColor alphaBlend(const QColor &foreground, const QColor &background); + + /** + * Returns a linearly interpolated color between color one and color two. + * + * @param one The color to linearly interpolate from. + * + * @param two The color to linearly interpolate to. + * + * @param balance The balance between the two colors. 0.0 will return the + * first color, 1.0 will return the second color. Values beyond these bounds + * are valid, and will result in extrapolation. + * + * @code{.qml} + * import QtQuick + * import org.kde.kirigami as Kirigami + * + * Rectangle { + * color: Kirigami.ColorUtils.linearInterpolation("black", "white", 0.5) + * } + * @endcode + * + * @since 5.69 + * @since org.kde.kirigami 2.12 + */ + Q_INVOKABLE QColor linearInterpolation(const QColor &one, const QColor &two, double balance); + + /** + * Increases or decreases the properties of `color` by fixed amounts. + * + * @param color The color to adjust. + * + * @param adjustments The adjustments to apply to the color. + * + * @code{.js} + * { + * red: null, // Range: -255 to 255 + * green: null, // Range: -255 to 255 + * blue: null, // Range: -255 to 255 + * hue: null, // Range: -360 to 360 + * saturation: null, // Range: -255 to 255 + * value: null // Range: -255 to 255 + * alpha: null, // Range: -255 to 255 + * } + * @endcode + * + * @warning It is an error to adjust both RGB and HSV properties. + * + * @since 5.69 + * @since org.kde.kirigami 2.12 + */ + Q_INVOKABLE QColor adjustColor(const QColor &color, const QJSValue &adjustments); + + /** + * Smoothly scales colors. + * + * @param color The color to adjust. + * + * @param adjustments The adjustments to apply to the color. Each value must + * be between `-100.0` and `100.0`. This indicates how far the property should + * be scaled from its original to the maximum if positive or to the minimum if + * negative. + * + * @code{.js} + * { + * red: null + * green: null + * blue: null + * saturation: null + * value: null + * alpha: null + * } + * @endcode + * + * @warning It is an error to scale both RGB and HSV properties. + * + * @since 5.69 + * @since org.kde.kirigami 2.12 + */ + Q_INVOKABLE QColor scaleColor(const QColor &color, const QJSValue &adjustments); + + /** + * Tint a color using a separate alpha value. + * + * This does the same as Qt.tint() except that rather than using the tint + * color's alpha value, it uses a separate value that gets multiplied with + * the tint color's alpha. This avoids needing to create a new color just to + * adjust an alpha value. + * + * \param targetColor The color to tint. + * \param tintColor The color to tint with. + * \param alpha The amount of tinting to apply. + * + * \return The tinted color. + * + * \sa Qt.tint() + */ + Q_INVOKABLE QColor tintWithAlpha(const QColor &targetColor, const QColor &tintColor, double alpha); + + /** + * Returns the CIELAB chroma of the given color. + * + * CIELAB chroma may give a better quantification of how vibrant a color is compared to HSV saturation. + * + * \sa https://en.wikipedia.org/wiki/Colorfulness + * \sa https://en.wikipedia.org/wiki/CIELAB_color_space + */ + Q_INVOKABLE static qreal chroma(const QColor &color); + + struct XYZColor { + qreal x = 0; + qreal y = 0; + qreal z = 0; + }; + + struct LabColor { + qreal l = 0; + qreal a = 0; + qreal b = 0; + }; + + // Not for QML, returns the comvertion from srgb of a QColor and XYZ colorspace + static ColorUtils::XYZColor colorToXYZ(const QColor &color); + + // Not for QML, returns the comvertion from srgb of a QColor and Lab colorspace + static ColorUtils::LabColor colorToLab(const QColor &color); + + static qreal luminance(const QColor &color); +}; diff --git a/local/recipes/kde/kirigami/source/src/platform/dbustypes.h b/local/recipes/kde/kirigami/source/src/platform/dbustypes.h new file mode 100644 index 00000000..2357c467 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/dbustypes.h @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: 2018-2019 Red Hat Inc + * SPDX-FileCopyrightText: 2022 Aleix Pol Gonzalez + * + * SPDX-License-Identifier: LGPL-2.0-or-later + * + * SPDX-FileCopyrightText: 2018-2019 Jan Grulich + */ + +#pragma once + +#include +#include + +/// a{sa{sv}} +using VariantMapMap = QMap>; + +Q_DECLARE_METATYPE(VariantMapMap) diff --git a/local/recipes/kde/kirigami/source/src/platform/inputmethod.cpp b/local/recipes/kde/kirigami/source/src/platform/inputmethod.cpp new file mode 100644 index 00000000..54b9fb2d --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/inputmethod.cpp @@ -0,0 +1,91 @@ +/* + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "inputmethod.h" + +#include "virtualkeyboardwatcher.h" + +namespace Kirigami +{ +namespace Platform +{ + +class KIRIGAMIPLATFORM_NO_EXPORT InputMethod::Private +{ +public: + bool available = false; + bool enabled = false; + bool active = false; + bool visible = false; +}; + +InputMethod::InputMethod(QObject *parent) + : QObject(parent) + , d(std::make_unique()) +{ + auto watcher = VirtualKeyboardWatcher::self(); + + connect(watcher, &VirtualKeyboardWatcher::availableChanged, this, [this]() { + d->available = VirtualKeyboardWatcher::self()->available(); + Q_EMIT availableChanged(); + }); + + connect(watcher, &VirtualKeyboardWatcher::enabledChanged, this, [this]() { + d->enabled = VirtualKeyboardWatcher::self()->enabled(); + Q_EMIT enabledChanged(); + }); + + connect(watcher, &VirtualKeyboardWatcher::activeChanged, this, [this]() { + d->active = VirtualKeyboardWatcher::self()->active(); + Q_EMIT activeChanged(); + }); + + connect(watcher, &VirtualKeyboardWatcher::visibleChanged, this, [this]() { + d->visible = VirtualKeyboardWatcher::self()->visible(); + Q_EMIT visibleChanged(); + }); + + connect(watcher, &VirtualKeyboardWatcher::willShowOnActiveChanged, this, [this]() { + Q_EMIT willShowOnActiveChanged(); + }); + + d->available = watcher->available(); + d->enabled = watcher->enabled(); + d->active = watcher->active(); + d->visible = watcher->visible(); +} + +InputMethod::~InputMethod() = default; + +bool InputMethod::available() const +{ + return d->available; +} + +bool InputMethod::enabled() const +{ + return d->enabled; +} + +bool InputMethod::active() const +{ + return d->active; +} + +bool InputMethod::visible() const +{ + return d->visible; +} + +bool InputMethod::willShowOnActive() const +{ + return VirtualKeyboardWatcher::self()->willShowOnActive(); +} + +} +} + +#include "moc_inputmethod.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/inputmethod.h b/local/recipes/kde/kirigami/source/src/platform/inputmethod.h new file mode 100644 index 00000000..981d1960 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/inputmethod.h @@ -0,0 +1,97 @@ +/* + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef INPUTMETHOD_H +#define INPUTMETHOD_H + +#include + +#include +#include + +#include "kirigamiplatform_export.h" + +namespace Kirigami +{ +namespace Platform +{ + +/** + * This exposes information about the current used input method. + */ +class KIRIGAMIPLATFORM_EXPORT InputMethod : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + +public: + InputMethod(QObject *parent = nullptr); + ~InputMethod() override; + + /** + * Is an input method available? + * + * This will be true if there is an input method available. When it is + * false it means there's no special input method configured and input + * happens directly through keyboard events. + */ + Q_PROPERTY(bool available READ available NOTIFY availableChanged FINAL) + bool available() const; + Q_SIGNAL void availableChanged(); + + /** + * Is the current input method enabled? + * + * If this is false, that means the input method is available but not in use. + */ + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged FINAL) + bool enabled() const; + Q_SIGNAL void enabledChanged(); + + /** + * Is the current input method active? + * + * What active means depends on the type of input method. In case of a + * virtual keyboard for example, it would mean the virtual keyboard is + * visible. + */ + Q_PROPERTY(bool active READ active NOTIFY activeChanged FINAL) + bool active() const; + Q_SIGNAL void activeChanged(); + + /** + * Is the current input method visible? + * + * For some input methods this will match \ref active however for others this + * may differ. + */ + Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged FINAL) + bool visible() const; + Q_SIGNAL void visibleChanged(); + + /** + * Will the input method be shown when a text input field gains focus? + * + * This is intended to be used to decide whether to give an input field + * default focus. For certain input methods, like virtual keyboards, it may + * not be desirable to show it by default. For example, having a search + * field focused on application startup may cause the virtual keyboard to + * show, greatly reducing the available application space. + */ + Q_PROPERTY(bool willShowOnActive READ willShowOnActive NOTIFY willShowOnActiveChanged FINAL) + bool willShowOnActive() const; + Q_SIGNAL void willShowOnActiveChanged(); + +private: + class Private; + const std::unique_ptr d; +}; + +} +} + +#endif // INPUTMETHOD_H diff --git a/local/recipes/kde/kirigami/source/src/platform/org.freedesktop.portal.Settings.xml b/local/recipes/kde/kirigami/source/src/platform/org.freedesktop.portal.Settings.xml new file mode 100644 index 00000000..54135389 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/org.freedesktop.portal.Settings.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/local/recipes/kde/kirigami/source/src/platform/platformpluginfactory.cpp b/local/recipes/kde/kirigami/source/src/platform/platformpluginfactory.cpp new file mode 100644 index 00000000..f4179d17 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/platformpluginfactory.cpp @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: 2017 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "platformpluginfactory.h" + +#include +#include +#include +#include +#include + +#include "kirigamiplatform_logging.h" + +namespace Kirigami +{ +namespace Platform +{ + +PlatformPluginFactory::PlatformPluginFactory(QObject *parent) + : QObject(parent) +{ +} + +PlatformPluginFactory::~PlatformPluginFactory() = default; + +PlatformPluginFactory *PlatformPluginFactory::findPlugin(const QString &preferredName) +{ + static QHash factories = QHash(); + + QString pluginName = preferredName.isEmpty() ? QQuickStyle::name() : preferredName; + // check for the plugin only once: it's an heavy operation + if (auto it = factories.constFind(pluginName); it != factories.constEnd()) { + return it.value(); + } + + // Even plugins that aren't found are in the map, so we know we shouldn't check again withthis expensive operation + factories[pluginName] = nullptr; + +#ifdef KIRIGAMI_BUILD_TYPE_STATIC + for (QObject *staticPlugin : QPluginLoader::staticInstances()) { + PlatformPluginFactory *factory = qobject_cast(staticPlugin); + if (factory) { + factories[pluginName] = factory; + break; + } + } +#else + const auto libraryPaths = QCoreApplication::libraryPaths(); + for (const QString &path : libraryPaths) { + +#ifdef Q_OS_ANDROID + const QDir dir(path); +#else + const QDir dir(path + QStringLiteral("/kf6/kirigami/platform")); +#endif + + const auto fileNames = dir.entryList(QDir::Files); + + for (const QString &fileName : fileNames) { + +#ifdef Q_OS_ANDROID + if (fileName.startsWith(QStringLiteral("libplugins_kf6_kirigami_platform_")) && QLibrary::isLibrary(fileName)) { +#endif + if (!pluginName.isEmpty() && fileName.contains(pluginName)) { + // TODO: env variable too? + QPluginLoader loader(dir.absoluteFilePath(fileName)); + QObject *plugin = loader.instance(); + // TODO: load actually a factory as plugin + + qCDebug(KirigamiPlatform) << "Loading style plugin from" << dir.absoluteFilePath(fileName); + + if (auto factory = qobject_cast(plugin)) { + factories[pluginName] = factory; + break; + } + } +#ifdef Q_OS_ANDROID + } +#endif + } + + // Ensure we only load the first plugin from the first plugin location. + // If we do not break here, we may end up loading a different plugin + // in place of the first one. + if (factories.value(pluginName) != nullptr) { + break; + } + } +#endif + + PlatformPluginFactory *factory = factories.value(pluginName); + + if (factory == nullptr) { + qWarning(KirigamiPlatform) << "Failed to find a Kirigami platform plugin for style" << QQuickStyle::name(); + } + + return factory; +} + +} +} + +#include "moc_platformpluginfactory.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/platformpluginfactory.h b/local/recipes/kde/kirigami/source/src/platform/platformpluginfactory.h new file mode 100644 index 00000000..a598b86d --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/platformpluginfactory.h @@ -0,0 +1,73 @@ +/* + * SPDX-FileCopyrightText: 2017 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef KIRIGAMI_PLATFORMPLUGINFACTORY_H +#define KIRIGAMI_PLATFORMPLUGINFACTORY_H + +#include + +#include "kirigamiplatform_export.h" + +class QQmlEngine; + +namespace Kirigami +{ +namespace Platform +{ +class PlatformTheme; +class Units; + +/** + * @class PlatformPluginFactory platformpluginfactory.h + * + * This class is reimpleented by plugins to provide different implementations + * of PlatformTheme + */ +class KIRIGAMIPLATFORM_EXPORT PlatformPluginFactory : public QObject +{ + Q_OBJECT + +public: + explicit PlatformPluginFactory(QObject *parent = nullptr); + ~PlatformPluginFactory() override; + + /** + * Creates an instance of PlatformTheme which can come out from + * an implementation provided by a plugin + * + * If this returns nullptr the PlatformTheme will use a fallback + * implementation that loads a theme definition from a QML file. + * + * @param parent the parent object of the created PlatformTheme + */ + virtual PlatformTheme *createPlatformTheme(QObject *parent) = 0; + + /** + * Creates an instance of Units which can come from an implementation + * provided by a plugin + * @param parent the parent of the units object + */ + virtual Units *createUnits(QObject *parent) = 0; + + /** + * finds the plugin providing units and platformtheme for the current style + * The plugin pointer is cached, so only the first call is a potentially heavy operation + * @param pluginName The name we want to search for, if empty the name of + * the current QtQuickControls style will be searched + * @return pointer to the PlatformPluginFactory of the current style + */ + static PlatformPluginFactory *findPlugin(const QString &pluginName = {}); +}; + +} +} + +QT_BEGIN_NAMESPACE +#define PlatformPluginFactory_iid "org.kde.kirigami.PlatformPluginFactory" +Q_DECLARE_INTERFACE(Kirigami::Platform::PlatformPluginFactory, PlatformPluginFactory_iid) +QT_END_NAMESPACE + +#endif // PLATFORMPLUGINFACTORY_H diff --git a/local/recipes/kde/kirigami/source/src/platform/platformtheme.cpp b/local/recipes/kde/kirigami/source/src/platform/platformtheme.cpp new file mode 100644 index 00000000..09378e8e --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/platformtheme.cpp @@ -0,0 +1,1084 @@ +/* + * SPDX-FileCopyrightText: 2017 Marco Martin + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "platformtheme.h" +#include "basictheme_p.h" +#include "platformpluginfactory.h" +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace Kirigami +{ +namespace Platform +{ +template<> +KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::DataChangedEvent::type = QEvent::None; +template<> +KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::ColorSetChangedEvent::type = QEvent::None; +template<> +KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::ColorGroupChangedEvent::type = QEvent::None; +template<> +KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::ColorChangedEvent::type = QEvent::None; +template<> +KIRIGAMIPLATFORM_EXPORT QEvent::Type PlatformThemeEvents::FontChangedEvent::type = QEvent::None; + +// Initialize event types. +// We want to avoid collisions with application event types so we should use +// registerEventType for generating the event types. Unfortunately, that method +// is not constexpr so we need to call it somewhere during application startup. +// This struct handles that. +struct TypeInitializer { + TypeInitializer() + { + PlatformThemeEvents::DataChangedEvent::type = QEvent::Type(QEvent::registerEventType()); + PlatformThemeEvents::ColorSetChangedEvent::type = QEvent::Type(QEvent::registerEventType()); + PlatformThemeEvents::ColorGroupChangedEvent::type = QEvent::Type(QEvent::registerEventType()); + PlatformThemeEvents::ColorChangedEvent::type = QEvent::Type(QEvent::registerEventType()); + PlatformThemeEvents::FontChangedEvent::type = QEvent::Type(QEvent::registerEventType()); + } +}; +static TypeInitializer initializer; + +// This class encapsulates the actual data of the Theme object. It may be shared +// among several instances of PlatformTheme, to ensure that the memory usage of +// PlatformTheme stays low. +class PlatformThemeData : public QObject +{ + Q_OBJECT + +public: + // An enum for all colors in PlatformTheme. + // This is used so we can have a QHash of local overrides in the + // PlatformTheme, which avoids needing to store all these colors in + // PlatformTheme even when they're not used. + enum ColorRole { + TextColor, + DisabledTextColor, + HighlightedTextColor, + ActiveTextColor, + LinkColor, + VisitedLinkColor, + NegativeTextColor, + NeutralTextColor, + PositiveTextColor, + BackgroundColor, + AlternateBackgroundColor, + HighlightColor, + ActiveBackgroundColor, + LinkBackgroundColor, + VisitedLinkBackgroundColor, + NegativeBackgroundColor, + NeutralBackgroundColor, + PositiveBackgroundColor, + FocusColor, + HoverColor, + + // This should always be the last item. It indicates how many items + // there are and is used for the storage array below. + ColorRoleCount, + }; + + using ColorMap = std::unordered_map::type, QColor>; + + // Which PlatformTheme instance "owns" this data object. Only the owner is + // allowed to make changes to data. + QPointer owner; + + PlatformTheme::ColorSet colorSet = PlatformTheme::Window; + PlatformTheme::ColorGroup colorGroup = PlatformTheme::Active; + + std::array colors; + + QFont defaultFont; + QFont smallFont; + + QPalette palette; + + // A list of PlatformTheme instances that want to be notified when the data + // changes. This is used instead of signal/slots as this way we only store + // a little bit of data and that data is shared among instances, whereas + // signal/slots turn out to have a pretty large memory overhead per instance. + using Watcher = PlatformTheme *; + QList watchers; + + inline void setColorSet(PlatformTheme *sender, PlatformTheme::ColorSet set) + { + if (sender != owner || colorSet == set) { + return; + } + + auto oldValue = colorSet; + + colorSet = set; + + notifyWatchers(sender, oldValue, set); + } + + inline void setColorGroup(PlatformTheme *sender, PlatformTheme::ColorGroup group) + { + if (sender != owner || colorGroup == group) { + return; + } + + auto oldValue = colorGroup; + + colorGroup = group; + palette.setCurrentColorGroup(QPalette::ColorGroup(group)); + + notifyWatchers(sender, oldValue, group); + } + + inline void setColor(PlatformTheme *sender, ColorRole role, const QColor &color) + { + if (sender != owner || colors[role] == color) { + return; + } + + auto oldValue = colors[role]; + + colors[role] = color; + updatePalette(palette, colors); + + notifyWatchers(sender, oldValue, colors[role]); + } + + inline void setDefaultFont(PlatformTheme *sender, const QFont &font) + { + if (sender != owner || font == defaultFont) { + return; + } + + auto oldValue = defaultFont; + + defaultFont = font; + + notifyWatchers(sender, oldValue, font); + } + + inline void setSmallFont(PlatformTheme *sender, const QFont &font) + { + if (sender != owner || font == smallFont) { + return; + } + + auto oldValue = smallFont; + + smallFont = font; + + notifyWatchers(sender, oldValue, smallFont); + } + + inline void addChangeWatcher(PlatformTheme *object) + { + watchers.append(object); + } + + inline void removeChangeWatcher(PlatformTheme *object) + { + watchers.removeOne(object); + } + + template + inline void notifyWatchers(PlatformTheme *sender, const T &oldValue, const T &newValue) + { + for (auto object : std::as_const(watchers)) { + PlatformThemeEvents::PropertyChangedEvent event(sender, oldValue, newValue); + QCoreApplication::sendEvent(object, &event); + } + } + + // Update a palette from a list of colors. + inline static void updatePalette(QPalette &palette, const std::array &colors) + { + for (std::size_t i = 0; i < colors.size(); ++i) { + setPaletteColor(palette, ColorRole(i), colors.at(i)); + } + } + + // Update a palette from a hash of colors. + inline static void updatePalette(QPalette &palette, const ColorMap &colors) + { + for (auto entry : colors) { + setPaletteColor(palette, ColorRole(entry.first), entry.second); + } + } + + inline static void setPaletteColor(QPalette &palette, ColorRole role, const QColor &color) + { + switch (role) { + case TextColor: + palette.setColor(QPalette::Text, color); + palette.setColor(QPalette::WindowText, color); + palette.setColor(QPalette::ButtonText, color); + break; + case BackgroundColor: + palette.setColor(QPalette::Window, color); + palette.setColor(QPalette::Base, color); + palette.setColor(QPalette::Button, color); + break; + case AlternateBackgroundColor: + palette.setColor(QPalette::AlternateBase, color); + break; + case HighlightColor: + palette.setColor(QPalette::Highlight, color); + palette.setColor(QPalette::Accent, color); + break; + case HighlightedTextColor: + palette.setColor(QPalette::HighlightedText, color); + break; + case LinkColor: + palette.setColor(QPalette::Link, color); + break; + case VisitedLinkColor: + palette.setColor(QPalette::LinkVisited, color); + break; + + default: + break; + } + } +}; + +class PlatformThemePrivate +{ +public: + PlatformThemePrivate() + : inherit(true) + , supportsIconColoring(false) + , pendingColorChange(false) + , pendingChildUpdate(false) + , useAlternateBackgroundColor(false) + , colorSet(PlatformTheme::Window) + , colorGroup(PlatformTheme::Active) + { + } + + inline QColor color(const PlatformTheme *theme, PlatformThemeData::ColorRole color) const + { + if (!data) { + return QColor{}; + } + + QColor value = data->colors.at(color); + + if (data->owner != theme && localOverrides) { + auto itr = localOverrides->find(color); + if (itr != localOverrides->end()) { + value = itr->second; + } + } + + return value; + } + + inline void setColor(PlatformTheme *theme, PlatformThemeData::ColorRole color, const QColor &value) + { + if (!localOverrides) { + localOverrides = std::make_unique(); + } + + if (!value.isValid()) { + // Invalid color, assume we are resetting the value. + auto itr = localOverrides->find(color); + if (itr != localOverrides->end()) { + PlatformThemeChangeTracker tracker(theme, PlatformThemeChangeTracker::PropertyChange::Color); + localOverrides->erase(itr); + + if (data) { + // TODO: Find a better way to determine "default" color. + // Right now this sets the color to transparent to force a + // color change and relies on the style-specific subclass to + // handle resetting the actual color. + data->setColor(theme, color, Qt::transparent); + } + } + + return; + } + + auto itr = localOverrides->find(color); + if (itr != localOverrides->end() && itr->second == value && (data && data->owner != theme)) { + return; + } + + PlatformThemeChangeTracker tracker(theme, PlatformThemeChangeTracker::PropertyChange::Color); + + (*localOverrides)[color] = value; + + if (data) { + data->setColor(theme, color, value); + } + } + + inline void setDataColor(PlatformTheme *theme, PlatformThemeData::ColorRole color, const QColor &value) + { + // Only set color if we have no local override of the color. + // This is done because colorSet/colorGroup changes will trigger most + // subclasses to reevaluate and reset the colors, breaking any local + // overrides we have. + if (localOverrides) { + auto itr = localOverrides->find(color); + if (itr != localOverrides->end()) { + return; + } + } + + PlatformThemeChangeTracker tracker(theme, PlatformThemeChangeTracker::PropertyChange::Color); + + if (data) { + data->setColor(theme, color, value); + } + } + + /* + * Please note that there is no q pointer. This is intentional, as it avoids + * having to store that information for each instance of PlatformTheme, + * saving us 8 bytes per instance. Instead, we pass the theme object as + * first parameter of each method. This is a little uglier but essentially + * works the same without needing memory. + */ + + // An instance of the data object. This is potentially shared with many + // instances of PlatformTheme. + std::shared_ptr data; + // Used to store color overrides of inherited data. This is created on + // demand and will only exist if we actually have local overrides. + std::unique_ptr localOverrides; + + bool inherit : 1; + bool supportsIconColoring : 1; // TODO KF6: Remove in favour of virtual method + bool pendingColorChange : 1; + bool pendingChildUpdate : 1; + bool useAlternateBackgroundColor : 1; + + // Note: We use these to store local values of PlatformTheme::ColorSet and + // PlatformTheme::ColorGroup. While these are standard enums and thus 32 + // bits they only contain a few items so we store the value in only 4 bits + // to save space. + uint8_t colorSet : 4; + uint8_t colorGroup : 4; + + // Ensure the above assumption holds. Should this static assert fail, the + // bit size above needs to be adjusted. + static_assert(PlatformTheme::ColorGroupCount <= 16, "PlatformTheme::ColorGroup contains more elements than can be stored in PlatformThemePrivate"); + static_assert(PlatformTheme::ColorSetCount <= 16, "PlatformTheme::ColorSet contains more elements than can be stored in PlatformThemePrivate"); + + inline static PlatformPluginFactory *s_pluginFactory = nullptr; +}; + +PlatformTheme::PlatformTheme(QObject *parent) + : QObject(parent) + , d(new PlatformThemePrivate) +{ + if (QQuickItem *item = qobject_cast(parent)) { + connect(item, &QQuickItem::windowChanged, this, [this](QQuickWindow *window) { + if (window) { + update(); + } + }); + connect(item, &QQuickItem::parentChanged, this, &PlatformTheme::update); + // Needs to be connected to enabledChanged twice to correctly fully update when a + // Theme that does inherit becomes temporarly non-inherit and back due to + // the item being enabled or disabled + connect(item, &QQuickItem::enabledChanged, this, &PlatformTheme::update); + connect(item, &QQuickItem::enabledChanged, this, &PlatformTheme::update, Qt::QueuedConnection); + } + + update(); +} + +PlatformTheme::~PlatformTheme() +{ + if (d->data) { + d->data->removeChangeWatcher(this); + } + + delete d; +} + +void PlatformTheme::setColorSet(PlatformTheme::ColorSet colorSet) +{ + PlatformThemeChangeTracker tracker(this, PlatformThemeChangeTracker::PropertyChange::ColorSet); + d->colorSet = colorSet; + + if (d->data) { + d->data->setColorSet(this, colorSet); + } +} + +PlatformTheme::ColorSet PlatformTheme::colorSet() const +{ + return d->data ? d->data->colorSet : Window; +} + +void PlatformTheme::setColorGroup(PlatformTheme::ColorGroup colorGroup) +{ + PlatformThemeChangeTracker tracker(this, PlatformThemeChangeTracker::PropertyChange::ColorGroup); + d->colorGroup = colorGroup; + + if (d->data) { + d->data->setColorGroup(this, colorGroup); + } +} + +PlatformTheme::ColorGroup PlatformTheme::colorGroup() const +{ + return d->data ? d->data->colorGroup : Active; +} + +bool PlatformTheme::inherit() const +{ + return d->inherit; +} + +void PlatformTheme::setInherit(bool inherit) +{ + if (inherit == d->inherit) { + return; + } + + d->inherit = inherit; + update(); + + Q_EMIT inheritChanged(inherit); +} + +QColor PlatformTheme::textColor() const +{ + return d->color(this, PlatformThemeData::TextColor); +} + +QColor PlatformTheme::disabledTextColor() const +{ + return d->color(this, PlatformThemeData::DisabledTextColor); +} + +QColor PlatformTheme::highlightColor() const +{ + return d->color(this, PlatformThemeData::HighlightColor); +} + +QColor PlatformTheme::highlightedTextColor() const +{ + return d->color(this, PlatformThemeData::HighlightedTextColor); +} + +QColor PlatformTheme::backgroundColor() const +{ + return d->color(this, PlatformThemeData::BackgroundColor); +} + +QColor PlatformTheme::alternateBackgroundColor() const +{ + return d->color(this, PlatformThemeData::AlternateBackgroundColor); +} + +QColor PlatformTheme::activeTextColor() const +{ + return d->color(this, PlatformThemeData::ActiveTextColor); +} + +QColor PlatformTheme::activeBackgroundColor() const +{ + return d->color(this, PlatformThemeData::ActiveBackgroundColor); +} + +QColor PlatformTheme::linkColor() const +{ + return d->color(this, PlatformThemeData::LinkColor); +} + +QColor PlatformTheme::linkBackgroundColor() const +{ + return d->color(this, PlatformThemeData::LinkBackgroundColor); +} + +QColor PlatformTheme::visitedLinkColor() const +{ + return d->color(this, PlatformThemeData::VisitedLinkColor); +} + +QColor PlatformTheme::visitedLinkBackgroundColor() const +{ + return d->color(this, PlatformThemeData::VisitedLinkBackgroundColor); +} + +QColor PlatformTheme::negativeTextColor() const +{ + return d->color(this, PlatformThemeData::NegativeTextColor); +} + +QColor PlatformTheme::negativeBackgroundColor() const +{ + return d->color(this, PlatformThemeData::NegativeBackgroundColor); +} + +QColor PlatformTheme::neutralTextColor() const +{ + return d->color(this, PlatformThemeData::NeutralTextColor); +} + +QColor PlatformTheme::neutralBackgroundColor() const +{ + return d->color(this, PlatformThemeData::NeutralBackgroundColor); +} + +QColor PlatformTheme::positiveTextColor() const +{ + return d->color(this, PlatformThemeData::PositiveTextColor); +} + +QColor PlatformTheme::positiveBackgroundColor() const +{ + return d->color(this, PlatformThemeData::PositiveBackgroundColor); +} + +QColor PlatformTheme::focusColor() const +{ + return d->color(this, PlatformThemeData::FocusColor); +} + +QColor PlatformTheme::hoverColor() const +{ + return d->color(this, PlatformThemeData::HoverColor); +} + +// setters for theme implementations +void PlatformTheme::setTextColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::TextColor, color); +} + +void PlatformTheme::setDisabledTextColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::DisabledTextColor, color); +} + +void PlatformTheme::setBackgroundColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::BackgroundColor, color); +} + +void PlatformTheme::setAlternateBackgroundColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::AlternateBackgroundColor, color); +} + +void PlatformTheme::setHighlightColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::HighlightColor, color); +} + +void PlatformTheme::setHighlightedTextColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::HighlightedTextColor, color); +} + +void PlatformTheme::setActiveTextColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::ActiveTextColor, color); +} + +void PlatformTheme::setActiveBackgroundColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::ActiveBackgroundColor, color); +} + +void PlatformTheme::setLinkColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::LinkColor, color); +} + +void PlatformTheme::setLinkBackgroundColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::LinkBackgroundColor, color); +} + +void PlatformTheme::setVisitedLinkColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::VisitedLinkColor, color); +} + +void PlatformTheme::setVisitedLinkBackgroundColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::VisitedLinkBackgroundColor, color); +} + +void PlatformTheme::setNegativeTextColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::NegativeTextColor, color); +} + +void PlatformTheme::setNegativeBackgroundColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::NegativeBackgroundColor, color); +} + +void PlatformTheme::setNeutralTextColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::NeutralTextColor, color); +} + +void PlatformTheme::setNeutralBackgroundColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::NeutralBackgroundColor, color); +} + +void PlatformTheme::setPositiveTextColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::PositiveTextColor, color); +} + +void PlatformTheme::setPositiveBackgroundColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::PositiveBackgroundColor, color); +} + +void PlatformTheme::setHoverColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::HoverColor, color); +} + +void PlatformTheme::setFocusColor(const QColor &color) +{ + d->setDataColor(this, PlatformThemeData::FocusColor, color); +} + +QFont PlatformTheme::defaultFont() const +{ + return d->data ? d->data->defaultFont : QFont{}; +} + +void PlatformTheme::setDefaultFont(const QFont &font) +{ + PlatformThemeChangeTracker tracker(this, PlatformThemeChangeTracker::PropertyChange::Font); + if (d->data) { + d->data->setDefaultFont(this, font); + } +} + +QFont PlatformTheme::smallFont() const +{ + return d->data ? d->data->smallFont : QFont{}; +} + +void PlatformTheme::setSmallFont(const QFont &font) +{ + PlatformThemeChangeTracker tracker(this, PlatformThemeChangeTracker::PropertyChange::Font); + if (d->data) { + d->data->setSmallFont(this, font); + } +} + +qreal PlatformTheme::frameContrast() const +{ + // This value must be kept in sync with + // the value from Breeze Qt Widget theme. + // See: https://invent.kde.org/plasma/breeze/-/blob/master/kstyle/breezemetrics.h?ref_type=heads#L162 + return 0.20; +} + +qreal PlatformTheme::lightFrameContrast() const +{ + // This can be utilized to return full contrast + // if high contrast accessibility setting is enabled + return frameContrast() / 2.0; +} + +// setters for QML clients +void PlatformTheme::setCustomTextColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::TextColor, color); +} + +void PlatformTheme::setCustomDisabledTextColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::DisabledTextColor, color); +} + +void PlatformTheme::setCustomBackgroundColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::BackgroundColor, color); +} + +void PlatformTheme::setCustomAlternateBackgroundColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::AlternateBackgroundColor, color); +} + +void PlatformTheme::setCustomHighlightColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::HighlightColor, color); +} + +void PlatformTheme::setCustomHighlightedTextColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::HighlightedTextColor, color); +} + +void PlatformTheme::setCustomActiveTextColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::ActiveTextColor, color); +} + +void PlatformTheme::setCustomActiveBackgroundColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::ActiveBackgroundColor, color); +} + +void PlatformTheme::setCustomLinkColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::LinkColor, color); +} + +void PlatformTheme::setCustomLinkBackgroundColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::LinkBackgroundColor, color); +} + +void PlatformTheme::setCustomVisitedLinkColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::TextColor, color); +} + +void PlatformTheme::setCustomVisitedLinkBackgroundColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::VisitedLinkBackgroundColor, color); +} + +void PlatformTheme::setCustomNegativeTextColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::NegativeTextColor, color); +} + +void PlatformTheme::setCustomNegativeBackgroundColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::NegativeBackgroundColor, color); +} + +void PlatformTheme::setCustomNeutralTextColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::NeutralTextColor, color); +} + +void PlatformTheme::setCustomNeutralBackgroundColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::NeutralBackgroundColor, color); +} + +void PlatformTheme::setCustomPositiveTextColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::PositiveTextColor, color); +} + +void PlatformTheme::setCustomPositiveBackgroundColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::PositiveBackgroundColor, color); +} + +void PlatformTheme::setCustomHoverColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::HoverColor, color); +} + +void PlatformTheme::setCustomFocusColor(const QColor &color) +{ + d->setColor(this, PlatformThemeData::FocusColor, color); +} + +bool PlatformTheme::useAlternateBackgroundColor() const +{ + return d->useAlternateBackgroundColor; +} + +void PlatformTheme::setUseAlternateBackgroundColor(bool alternate) +{ + if (alternate == d->useAlternateBackgroundColor) { + return; + } + + d->useAlternateBackgroundColor = alternate; + Q_EMIT useAlternateBackgroundColorChanged(alternate); +} + +QPalette PlatformTheme::palette() const +{ + if (!d->data) { + return QPalette{}; + } + + auto palette = d->data->palette; + + if (d->localOverrides) { + PlatformThemeData::updatePalette(palette, *d->localOverrides); + } + + return palette; +} + +QIcon PlatformTheme::iconFromTheme(const QString &name, const QColor &customColor) +{ + Q_UNUSED(customColor); + QIcon icon = QIcon::fromTheme(name); + return icon; +} + +bool PlatformTheme::supportsIconColoring() const +{ + return d->supportsIconColoring; +} + +void PlatformTheme::setSupportsIconColoring(bool support) +{ + d->supportsIconColoring = support; +} + +PlatformTheme *PlatformTheme::qmlAttachedProperties(QObject *object) +{ + QQmlEngine *engine = qmlEngine(object); + QString pluginName; + + if (engine) { + pluginName = engine->property("_kirigamiTheme").toString(); + } + + auto plugin = PlatformPluginFactory::findPlugin(pluginName); + if (!plugin && !pluginName.isEmpty()) { + plugin = PlatformPluginFactory::findPlugin(); + } + + if (plugin) { + if (auto theme = plugin->createPlatformTheme(object)) { + return theme; + } + } + + return new BasicTheme(object); +} + +void PlatformTheme::emitSignalsForChanges(int changes) +{ + if (!d->data) { + return; + } + + auto propertyChanges = PlatformThemeChangeTracker::PropertyChanges::fromInt(changes); + + if (propertyChanges & PlatformThemeChangeTracker::PropertyChange::ColorSet) { + Q_EMIT colorSetChanged(ColorSet(d->data->colorSet)); + } + + if (propertyChanges & PlatformThemeChangeTracker::PropertyChange::ColorGroup) { + Q_EMIT colorGroupChanged(ColorGroup(d->data->colorGroup)); + } + + if (propertyChanges & PlatformThemeChangeTracker::PropertyChange::Color) { + Q_EMIT colorsChanged(); + } + + if (propertyChanges & PlatformThemeChangeTracker::PropertyChange::Palette) { + Q_EMIT paletteChanged(d->data->palette); + } + + if (propertyChanges & PlatformThemeChangeTracker::PropertyChange::Font) { + Q_EMIT defaultFontChanged(d->data->defaultFont); + Q_EMIT smallFontChanged(d->data->smallFont); + } + + if (propertyChanges & PlatformThemeChangeTracker::PropertyChange::Data) { + updateChildren(parent()); + } +} + +bool PlatformTheme::event(QEvent *event) +{ + PlatformThemeChangeTracker tracker(this); + + if (event->type() == PlatformThemeEvents::DataChangedEvent::type) { + auto changeEvent = static_cast(event); + + if (changeEvent->sender != this) { + return false; + } + + if (changeEvent->oldValue) { + changeEvent->oldValue->removeChangeWatcher(this); + } + + if (changeEvent->newValue) { + auto data = changeEvent->newValue; + data->addChangeWatcher(this); + } + + tracker.markDirty(PlatformThemeChangeTracker::PropertyChange::All); + return true; + } + + if (event->type() == PlatformThemeEvents::ColorSetChangedEvent::type) { + tracker.markDirty(PlatformThemeChangeTracker::PropertyChange::ColorSet); + return true; + } + + if (event->type() == PlatformThemeEvents::ColorGroupChangedEvent::type) { + tracker.markDirty(PlatformThemeChangeTracker::PropertyChange::ColorGroup); + return true; + } + + if (event->type() == PlatformThemeEvents::ColorChangedEvent::type) { + tracker.markDirty(PlatformThemeChangeTracker::PropertyChange::Color | PlatformThemeChangeTracker::PropertyChange::Palette); + return true; + } + + if (event->type() == PlatformThemeEvents::FontChangedEvent::type) { + tracker.markDirty(PlatformThemeChangeTracker::PropertyChange::Font); + return true; + } + + return QObject::event(event); +} + +void PlatformTheme::update() +{ + auto oldData = d->data; + + bool actualInherit = d->inherit; + if (QQuickItem *item = qobject_cast(parent())) { + // For inactive windows it should work already, as also the non inherit themes get it + if (colorGroup() != Disabled && !item->isEnabled()) { + actualInherit = false; + } + } + + if (actualInherit) { + QObject *candidate = parent(); + while (true) { + candidate = determineParent(candidate); + if (!candidate) { + break; + } + + auto t = static_cast(qmlAttachedPropertiesObject(candidate, false)); + if (t && t->d->data && t->d->data->owner == t) { + if (d->data == t->d->data) { + // Inheritance is already correct, do nothing. + return; + } + + d->data = t->d->data; + + PlatformThemeEvents::DataChangedEvent event{this, oldData, t->d->data}; + QCoreApplication::sendEvent(this, &event); + + return; + } + } + } else if (d->data && d->data->owner != this) { + // Inherit has changed and we no longer want to inherit, clear the data + // so it is recreated below. + d->data = nullptr; + } + + if (!d->data) { + d->data = std::make_shared(); + d->data->owner = this; + d->data->setColorSet(this, static_cast(d->colorSet)); + d->data->setColorGroup(this, static_cast(d->colorGroup)); + } + + if (d->localOverrides) { + for (auto entry : *d->localOverrides) { + d->data->setColor(this, PlatformThemeData::ColorRole(entry.first), entry.second); + } + } + + PlatformThemeEvents::DataChangedEvent event{this, oldData, d->data}; + QCoreApplication::sendEvent(this, &event); +} + +void PlatformTheme::updateChildren(QObject *object) +{ + if (!object) { + return; + } + + const auto children = object->children(); + for (auto child : children) { + auto t = static_cast(qmlAttachedPropertiesObject(child, false)); + if (t) { + t->update(); + } else { + updateChildren(child); + } + } +} + +// We sometimes set theme properties on non-visual objects. However, if an item +// has a visual and a non-visual parent that are different, we should prefer the +// visual parent, so we need to apply some extra logic. +QObject *PlatformTheme::determineParent(QObject *object) +{ + if (!object) { + return nullptr; + } + + auto item = qobject_cast(object); + if (item) { + return item->parentItem(); + } else { + return object->parent(); + } +} + +PlatformThemeChangeTracker::PlatformThemeChangeTracker(PlatformTheme *theme, PropertyChanges changes) + : m_theme(theme) +{ + auto itr = s_blockedChanges.constFind(theme); + if (itr == s_blockedChanges.constEnd() || (*itr).expired()) { + m_data = std::make_shared(); + s_blockedChanges.insert(theme, m_data); + } else { + m_data = (*itr).lock(); + } + + m_data->changes |= changes; +} + +PlatformThemeChangeTracker::~PlatformThemeChangeTracker() noexcept +{ + std::weak_ptr dataWatcher = m_data; + + auto changes = m_data->changes; + m_data.reset(); + + if (dataWatcher.use_count() <= 0) { + m_theme->emitSignalsForChanges(changes); + s_blockedChanges.remove(m_theme); + } +} + +void PlatformThemeChangeTracker::markDirty(PropertyChanges changes) +{ + m_data->changes |= changes; +} +} +} + +#include "moc_platformtheme.cpp" +#include "platformtheme.moc" diff --git a/local/recipes/kde/kirigami/source/src/platform/platformtheme.h b/local/recipes/kde/kirigami/source/src/platform/platformtheme.h new file mode 100644 index 00000000..562c6f89 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/platformtheme.h @@ -0,0 +1,470 @@ +/* + * SPDX-FileCopyrightText: 2017 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef KIRIGAMI_PLATFORMTHEME_H +#define KIRIGAMI_PLATFORMTHEME_H + +#include +#include +#include +#include +#include +#include + +#include "kirigamiplatform_export.h" + +namespace Kirigami +{ +namespace Platform +{ +class PlatformThemeData; +class PlatformThemePrivate; + +/** + * @class PlatformTheme platformtheme.h + * + * This class is the base for color management in Kirigami, + * different platforms can reimplement this class to integrate with + * system platform colors of a given platform + */ +class KIRIGAMIPLATFORM_EXPORT PlatformTheme : public QObject +{ + Q_OBJECT + QML_NAMED_ELEMENT(Theme) + QML_ATTACHED(Kirigami::Platform::PlatformTheme) + QML_UNCREATABLE("Attached Property") + + /** + * This enumeration describes the color set for which a color is being selected. + * + * Color sets define a color "environment", suitable for drawing all parts of a + * given region. Colors from different sets should not be combined. + */ + Q_PROPERTY(ColorSet colorSet READ colorSet WRITE setColorSet NOTIFY colorSetChanged FINAL) + + /** + * This enumeration describes the color group used to generate the colors. + * The enum value is based upon QPalette::ColorGroup and has the same values. + * It's redefined here in order to make it work with QML. + * @since 4.43 + */ + Q_PROPERTY(ColorGroup colorGroup READ colorGroup WRITE setColorGroup NOTIFY colorGroupChanged FINAL) + + /** + * If true, the colorSet will be inherited from the colorset of a theme of one + * of the ancestor items + * default: true + */ + Q_PROPERTY(bool inherit READ inherit WRITE setInherit NOTIFY inheritChanged FINAL) + + // foreground colors + /** + * Color for normal foregrounds, usually text, but not limited to it, + * anything that should be painted with a clear contrast should use this color + */ + Q_PROPERTY(QColor textColor READ textColor WRITE setCustomTextColor RESET setCustomTextColor NOTIFY colorsChanged FINAL) + + /** + * Foreground color for disabled areas, usually a mid-gray + * @note Depending on the implementation, the color used for this property may not be + * based on the disabled palette. For example, for the Plasma implementation, + * "Inactive Text Color" of the active palette is used. + */ + Q_PROPERTY(QColor disabledTextColor READ disabledTextColor WRITE setCustomDisabledTextColor RESET setCustomDisabledTextColor NOTIFY colorsChanged FINAL) + + /** + * Color for text that has been highlighted, often is a light color while normal text is dark + */ + Q_PROPERTY( + QColor highlightedTextColor READ highlightedTextColor WRITE setCustomHighlightedTextColor RESET setCustomHighlightedTextColor NOTIFY colorsChanged) + + /** + * Foreground for areas that are active or requesting attention + */ + Q_PROPERTY(QColor activeTextColor READ activeTextColor WRITE setCustomActiveTextColor RESET setCustomActiveTextColor NOTIFY colorsChanged FINAL) + + /** + * Color for links + */ + Q_PROPERTY(QColor linkColor READ linkColor WRITE setCustomLinkColor RESET setCustomLinkColor NOTIFY colorsChanged FINAL) + + /** + * Color for visited links, usually a bit darker than linkColor + */ + Q_PROPERTY(QColor visitedLinkColor READ visitedLinkColor WRITE setCustomVisitedLinkColor RESET setCustomVisitedLinkColor NOTIFY colorsChanged FINAL) + + /** + * Foreground color for negative areas, such as critical error text + */ + Q_PROPERTY(QColor negativeTextColor READ negativeTextColor WRITE setCustomNegativeTextColor RESET setCustomNegativeTextColor NOTIFY colorsChanged FINAL) + + /** + * Foreground color for neutral areas, such as warning texts (but not critical) + */ + Q_PROPERTY(QColor neutralTextColor READ neutralTextColor WRITE setCustomNeutralTextColor RESET setCustomNeutralTextColor NOTIFY colorsChanged FINAL) + + /** + * Success messages, trusted content + */ + Q_PROPERTY(QColor positiveTextColor READ positiveTextColor WRITE setCustomPositiveTextColor RESET setCustomPositiveTextColor NOTIFY colorsChanged FINAL) + + // background colors + /** + * The generic background color + */ + Q_PROPERTY(QColor backgroundColor READ backgroundColor WRITE setCustomBackgroundColor RESET setCustomBackgroundColor NOTIFY colorsChanged FINAL) + + /** + * The generic background color + * Alternate background; for example, for use in lists. + * This color may be the same as BackgroundNormal, + * especially in sets other than View and Window. + */ + Q_PROPERTY(QColor alternateBackgroundColor READ alternateBackgroundColor WRITE setCustomAlternateBackgroundColor RESET setCustomAlternateBackgroundColor + NOTIFY colorsChanged) + + /** + * The background color for selected areas + */ + Q_PROPERTY(QColor highlightColor READ highlightColor WRITE setCustomHighlightColor RESET setCustomHighlightColor NOTIFY colorsChanged FINAL) + + /** + * Background for areas that are active or requesting attention + */ + Q_PROPERTY( + QColor activeBackgroundColor READ activeBackgroundColor WRITE setCustomActiveBackgroundColor RESET setCustomActiveBackgroundColor NOTIFY colorsChanged) + + /** + * Background color for links + */ + Q_PROPERTY( + QColor linkBackgroundColor READ linkBackgroundColor WRITE setCustomLinkBackgroundColor RESET setCustomLinkBackgroundColor NOTIFY colorsChanged FINAL) + + /** + * Background color for visited links, usually a bit darker than linkBackgroundColor + */ + Q_PROPERTY(QColor visitedLinkBackgroundColor READ visitedLinkBackgroundColor WRITE setCustomVisitedLinkBackgroundColor RESET + setCustomVisitedLinkBackgroundColor NOTIFY colorsChanged) + + /** + * Background color for negative areas, such as critical errors and destructive actions + */ + Q_PROPERTY(QColor negativeBackgroundColor READ negativeBackgroundColor WRITE setCustomNegativeBackgroundColor RESET setCustomNegativeBackgroundColor NOTIFY + colorsChanged) + + /** + * Background color for neutral areas, such as warnings (but not critical) + */ + Q_PROPERTY(QColor neutralBackgroundColor READ neutralBackgroundColor WRITE setCustomNeutralBackgroundColor RESET setCustomNeutralBackgroundColor NOTIFY + colorsChanged) + + /** + * Background color for positive areas, such as success messages and trusted content + */ + Q_PROPERTY(QColor positiveBackgroundColor READ positiveBackgroundColor WRITE setCustomPositiveBackgroundColor RESET setCustomPositiveBackgroundColor NOTIFY + colorsChanged) + + // decoration colors + /** + * A decoration color that indicates active focus + */ + Q_PROPERTY(QColor focusColor READ focusColor WRITE setCustomFocusColor RESET setCustomFocusColor NOTIFY colorsChanged FINAL) + + /** + * A decoration color that indicates mouse hovering + */ + Q_PROPERTY(QColor hoverColor READ hoverColor WRITE setCustomHoverColor RESET setCustomHoverColor NOTIFY colorsChanged FINAL) + + /** + * Hint for item views to actually make use of the alternate background color feature + */ + Q_PROPERTY( + bool useAlternateBackgroundColor READ useAlternateBackgroundColor WRITE setUseAlternateBackgroundColor NOTIFY useAlternateBackgroundColorChanged FINAL) + + // font and palette + Q_PROPERTY(QFont defaultFont READ defaultFont NOTIFY defaultFontChanged FINAL) + + // small font + Q_PROPERTY(QFont smallFont READ smallFont NOTIFY smallFontChanged FINAL) + + // Active palette + Q_PROPERTY(QPalette palette READ palette NOTIFY paletteChanged FINAL) + + // Frame contrast value, usually used for separators and outlines + // Value is between 0.0 and 1.0 + Q_PROPERTY(qreal frameContrast READ frameContrast CONSTANT FINAL) + + // Returns half of the frameContrast value; used by Separator.Weight.Light + // Value is between 0.0 and 1.0 + Q_PROPERTY(qreal lightFrameContrast READ lightFrameContrast CONSTANT FINAL) + +public: + enum ColorSet { + /** Color set for item views, usually the lightest of all */ + View = 0, + /** Default Color set for windows and "chrome" areas */ + Window, + /** Color set used by buttons */ + Button, + /** Color set used by selected areas */ + Selection, + /** Color set used by tooltips */ + Tooltip, + /** Color set meant to be complementary to Window: usually is a dark theme for light themes */ + Complementary, + /** Color set to be used by heading areas of applications, such as toolbars */ + Header, + // Number of items in this enum, this should always be the last item. + ColorSetCount, + }; + Q_ENUM(ColorSet) + + enum ColorGroup { + Disabled = QPalette::Disabled, + Active = QPalette::Active, + Inactive = QPalette::Inactive, + Normal = QPalette::Normal, + + ColorGroupCount, // Number of items in this enum, this should always be the last item. + }; + Q_ENUM(ColorGroup) + + explicit PlatformTheme(QObject *parent = nullptr); + ~PlatformTheme() override; + + void setColorSet(PlatformTheme::ColorSet); + PlatformTheme::ColorSet colorSet() const; + + void setColorGroup(PlatformTheme::ColorGroup); + PlatformTheme::ColorGroup colorGroup() const; + + bool inherit() const; + void setInherit(bool inherit); + + // foreground colors + QColor textColor() const; + QColor disabledTextColor() const; + QColor highlightedTextColor() const; + QColor activeTextColor() const; + QColor linkColor() const; + QColor visitedLinkColor() const; + QColor negativeTextColor() const; + QColor neutralTextColor() const; + QColor positiveTextColor() const; + + // background colors + QColor backgroundColor() const; + QColor alternateBackgroundColor() const; + QColor highlightColor() const; + QColor activeBackgroundColor() const; + QColor linkBackgroundColor() const; + QColor visitedLinkBackgroundColor() const; + QColor negativeBackgroundColor() const; + QColor neutralBackgroundColor() const; + QColor positiveBackgroundColor() const; + + // decoration colors + QColor focusColor() const; + QColor hoverColor() const; + + QFont defaultFont() const; + QFont smallFont() const; + + // this may is used by the desktop QQC2 to set the styleoption palettes + QPalette palette() const; + + qreal frameContrast() const; + qreal lightFrameContrast() const; + + // this will be used by desktopicon to fetch icons with KIconLoader + virtual Q_INVOKABLE QIcon iconFromTheme(const QString &name, const QColor &customColor = Qt::transparent); + + bool supportsIconColoring() const; + + // foreground colors + void setCustomTextColor(const QColor &color = QColor()); + void setCustomDisabledTextColor(const QColor &color = QColor()); + void setCustomHighlightedTextColor(const QColor &color = QColor()); + void setCustomActiveTextColor(const QColor &color = QColor()); + void setCustomLinkColor(const QColor &color = QColor()); + void setCustomVisitedLinkColor(const QColor &color = QColor()); + void setCustomNegativeTextColor(const QColor &color = QColor()); + void setCustomNeutralTextColor(const QColor &color = QColor()); + void setCustomPositiveTextColor(const QColor &color = QColor()); + // background colors + void setCustomBackgroundColor(const QColor &color = QColor()); + void setCustomAlternateBackgroundColor(const QColor &color = QColor()); + void setCustomHighlightColor(const QColor &color = QColor()); + void setCustomActiveBackgroundColor(const QColor &color = QColor()); + void setCustomLinkBackgroundColor(const QColor &color = QColor()); + void setCustomVisitedLinkBackgroundColor(const QColor &color = QColor()); + void setCustomNegativeBackgroundColor(const QColor &color = QColor()); + void setCustomNeutralBackgroundColor(const QColor &color = QColor()); + void setCustomPositiveBackgroundColor(const QColor &color = QColor()); + // decoration colors + void setCustomFocusColor(const QColor &color = QColor()); + void setCustomHoverColor(const QColor &color = QColor()); + + bool useAlternateBackgroundColor() const; + void setUseAlternateBackgroundColor(bool alternate); + + // QML attached property + static PlatformTheme *qmlAttachedProperties(QObject *object); + +Q_SIGNALS: + void colorsChanged(); + void defaultFontChanged(const QFont &font); + void smallFontChanged(const QFont &font); + void colorSetChanged(Kirigami::Platform::PlatformTheme::ColorSet colorSet); + void colorGroupChanged(Kirigami::Platform::PlatformTheme::ColorGroup colorGroup); + void paletteChanged(const QPalette &pal); + void inheritChanged(bool inherit); + void useAlternateBackgroundColorChanged(bool alternate); + +protected: + // Setters, not accessible from QML but from implementations + void setSupportsIconColoring(bool support); + + // foreground colors + void setTextColor(const QColor &color); + void setDisabledTextColor(const QColor &color); + void setHighlightedTextColor(const QColor &color); + void setActiveTextColor(const QColor &color); + void setLinkColor(const QColor &color); + void setVisitedLinkColor(const QColor &color); + void setNegativeTextColor(const QColor &color); + void setNeutralTextColor(const QColor &color); + void setPositiveTextColor(const QColor &color); + + // background colors + void setBackgroundColor(const QColor &color); + void setAlternateBackgroundColor(const QColor &color); + void setHighlightColor(const QColor &color); + void setActiveBackgroundColor(const QColor &color); + void setLinkBackgroundColor(const QColor &color); + void setVisitedLinkBackgroundColor(const QColor &color); + void setNegativeBackgroundColor(const QColor &color); + void setNeutralBackgroundColor(const QColor &color); + void setPositiveBackgroundColor(const QColor &color); + + // decoration colors + void setFocusColor(const QColor &color); + void setHoverColor(const QColor &color); + + void setDefaultFont(const QFont &defaultFont); + void setSmallFont(const QFont &smallFont); + + bool event(QEvent *event) override; + +private: + KIRIGAMIPLATFORM_NO_EXPORT void update(); + KIRIGAMIPLATFORM_NO_EXPORT void updateChildren(QObject *item); + KIRIGAMIPLATFORM_NO_EXPORT QObject *determineParent(QObject *object); + KIRIGAMIPLATFORM_NO_EXPORT void emitSignalsForChanges(int changes); + + PlatformThemePrivate *d; + friend class PlatformThemePrivate; + friend class PlatformThemeData; + friend class PlatformThemeChangeTracker; +}; + +/** + * A class that tracks changes to PlatformTheme properties and emits signals at the right moment. + * + * This should be used by PlatformTheme implementations to ensure that multiple + * changes to a PlatformTheme's properties do not emit multiple change signals, + * instead batching all of them into a single signal emission. This then ensures + * things making use of PlatformTheme aren't needlessly redrawn or redrawn in a + * partially changed state. + * + * @since 6.7 + * + */ +class KIRIGAMIPLATFORM_EXPORT PlatformThemeChangeTracker +{ +public: + /** + * Flags used to indicate changes made to certain properties. + */ + enum class PropertyChange : uint8_t { + None = 0, + ColorSet = 1 << 0, + ColorGroup = 1 << 1, + Color = 1 << 2, + Palette = 1 << 3, + Font = 1 << 4, + Data = 1 << 5, + All = ColorSet | ColorGroup | Color | Palette | Font | Data, + }; + Q_DECLARE_FLAGS(PropertyChanges, PropertyChange) + + PlatformThemeChangeTracker(PlatformTheme *theme, PropertyChanges changes = PropertyChange::None); + ~PlatformThemeChangeTracker(); + + void markDirty(PropertyChanges changes); + +private: + PlatformTheme *m_theme; + + // Per-PlatformTheme data that we need for PlatformThemeChangeBlocker. + // We don't want to store this in PlatformTheme since that would increase the + // size of every instance of PlatformTheme while it's only used when we want to + // block property change signal emissions. So instead we store it in a separate + // hash using the PlatformTheme as key. + struct Data { + PropertyChanges changes; + }; + + std::shared_ptr m_data; + + inline static QHash> s_blockedChanges; +}; + +namespace PlatformThemeEvents +{ +// To avoid the overhead of Qt's signal/slot connections, we use custom events +// to communicate with subclasses. This way, we can indicate what actually +// changed without needing to add new virtual functions to PlatformTheme which +// would break binary compatibility. +// +// To handle these events in your subclass, override QObject::event() and check +// if you receive one of these events, then do what is needed. Finally, make +// sure to call PlatformTheme::event() since that will also do some processing +// of these events. + +template +class KIRIGAMIPLATFORM_EXPORT PropertyChangedEvent : public QEvent +{ +public: + PropertyChangedEvent(PlatformTheme *theme, const T &previous, const T ¤t) + : QEvent(PropertyChangedEvent::type) + , sender(theme) + , oldValue(previous) + , newValue(current) + { + } + + PlatformTheme *sender; + T oldValue; + T newValue; + + static QEvent::Type type; +}; + +using DataChangedEvent = PropertyChangedEvent>; +using ColorSetChangedEvent = PropertyChangedEvent; +using ColorGroupChangedEvent = PropertyChangedEvent; +using ColorChangedEvent = PropertyChangedEvent; +using FontChangedEvent = PropertyChangedEvent; + +} + +} +} // namespace Kirigami + +Q_DECLARE_OPERATORS_FOR_FLAGS(Kirigami::Platform::PlatformThemeChangeTracker::PropertyChanges) + +#endif // PLATFORMTHEME_H diff --git a/local/recipes/kde/kirigami/source/src/platform/settings.cpp b/local/recipes/kde/kirigami/source/src/platform/settings.cpp new file mode 100644 index 00000000..2da0d536 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/settings.cpp @@ -0,0 +1,242 @@ +/* + * SPDX-FileCopyrightText: 2016 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "settings.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +#include "kirigamiplatform_version.h" +#include "smoothscrollwatcher.h" +#include "tabletmodewatcher.h" + +namespace Kirigami +{ +namespace Platform +{ + +class SettingsSingleton +{ +public: + Settings self; +}; + +Settings::Settings(QObject *parent) + : QObject(parent) + , m_hasTouchScreen(false) + , m_hasTransientTouchInput(false) +{ + m_tabletModeAvailable = TabletModeWatcher::self()->isTabletModeAvailable(); + connect(TabletModeWatcher::self(), &TabletModeWatcher::tabletModeAvailableChanged, this, [this](bool tabletModeAvailable) { + setTabletModeAvailable(tabletModeAvailable); + }); + + m_tabletMode = TabletModeWatcher::self()->isTabletMode(); + connect(TabletModeWatcher::self(), &TabletModeWatcher::tabletModeChanged, this, [this](bool tabletMode) { + setTabletMode(tabletMode); + }); + +#if defined(Q_OS_ANDROID) || defined(Q_OS_IOS) || defined(UBUNTU_TOUCH) + m_mobile = true; + m_hasTouchScreen = true; +#else + // Mostly for debug purposes and for platforms which are always mobile, + // such as Plasma Mobile + if (qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MOBILE")) { + m_mobile = QByteArrayList{"1", "true"}.contains(qgetenv("QT_QUICK_CONTROLS_MOBILE")); + } else { + m_mobile = false; + } + + const auto touchDevices = QInputDevice::devices(); + const auto touchDeviceType = QInputDevice::DeviceType::TouchScreen; + for (const auto &device : touchDevices) { + if (device->type() == touchDeviceType) { + m_hasTouchScreen = true; + break; + } + } + if (m_hasTouchScreen) { + connect(qApp, &QGuiApplication::focusWindowChanged, this, [this](QWindow *win) { + if (win) { + win->installEventFilter(this); + } + }); + } +#endif + + auto bar = QGuiApplicationPrivate::platformTheme()->createPlatformMenuBar(); + m_hasPlatformMenuBar = bar != nullptr; + if (bar != nullptr) { + bar->deleteLater(); + } + + const QString configPath = QStandardPaths::locate(QStandardPaths::ConfigLocation, QStringLiteral("kdeglobals")); + if (QFile::exists(configPath)) { + QSettings globals(configPath, QSettings::IniFormat); + globals.beginGroup(QStringLiteral("KDE")); + m_scrollLines = qMax(1, globals.value(QStringLiteral("WheelScrollLines"), 3).toInt()); + m_smoothScroll = globals.value(QStringLiteral("SmoothScroll"), true).toBool(); + } else { + m_scrollLines = 3; + m_smoothScroll = true; + } + + connect(SmoothScrollWatcher::self(), &SmoothScrollWatcher::enabledChanged, this, [this](bool enabled) { + m_smoothScroll = enabled; + Q_EMIT smoothScrollChanged(); + }); +} + +Settings::~Settings() +{ +} + +bool Settings::eventFilter(QObject *watched, QEvent *event) +{ + Q_UNUSED(watched) + switch (event->type()) { + case QEvent::TouchBegin: + setTransientTouchInput(true); + break; + case QEvent::MouseButtonPress: + case QEvent::MouseMove: { + QMouseEvent *me = static_cast(event); + if (me->source() == Qt::MouseEventNotSynthesized) { + setTransientTouchInput(false); + } + break; + } + case QEvent::Wheel: + setTransientTouchInput(false); + default: + break; + } + + return false; +} + +void Settings::setTabletModeAvailable(bool mobileAvailable) +{ + if (mobileAvailable == m_tabletModeAvailable) { + return; + } + + m_tabletModeAvailable = mobileAvailable; + Q_EMIT tabletModeAvailableChanged(); +} + +bool Settings::isTabletModeAvailable() const +{ + return m_tabletModeAvailable; +} + +void Settings::setIsMobile(bool mobile) +{ + if (mobile == m_mobile) { + return; + } + + m_mobile = mobile; + Q_EMIT isMobileChanged(); +} + +bool Settings::isMobile() const +{ + return m_mobile; +} + +void Settings::setTabletMode(bool tablet) +{ + if (tablet == m_tabletMode) { + return; + } + + m_tabletMode = tablet; + Q_EMIT tabletModeChanged(); +} + +bool Settings::tabletMode() const +{ + return m_tabletMode; +} + +void Settings::setTransientTouchInput(bool touch) +{ + if (touch == m_hasTransientTouchInput) { + return; + } + + m_hasTransientTouchInput = touch; + if (!m_tabletMode) { + Q_EMIT hasTransientTouchInputChanged(); + } +} + +bool Settings::hasTransientTouchInput() const +{ + return m_hasTransientTouchInput || m_tabletMode; +} + +QString Settings::style() const +{ + return m_style; +} + +void Settings::setStyle(const QString &style) +{ + m_style = style; +} + +int Settings::mouseWheelScrollLines() const +{ + return m_scrollLines; +} + +bool Settings::smoothScroll() const +{ + return m_smoothScroll; +} + +QStringList Settings::information() const +{ + return { +#ifndef KIRIGAMI_BUILD_TYPE_STATIC + tr("KDE Frameworks %1").arg(QStringLiteral(KIRIGAMIPLATFORM_VERSION_STRING)), +#endif + tr("The %1 windowing system").arg(QGuiApplication::platformName()), + tr("Qt %2 (built against %3)").arg(QString::fromLocal8Bit(qVersion()), QStringLiteral(QT_VERSION_STR))}; +} + +QVariant Settings::applicationWindowIcon() const +{ + const QIcon &windowIcon = qApp->windowIcon(); + if (windowIcon.isNull()) { + return QVariant(); + } + return windowIcon; +} + +bool Settings::hasPlatformMenuBar() const +{ + return m_hasPlatformMenuBar; +} + +} +} + +#include "moc_settings.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/settings.h b/local/recipes/kde/kirigami/source/src/platform/settings.h new file mode 100644 index 00000000..f44363d1 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/settings.h @@ -0,0 +1,157 @@ +/* + * SPDX-FileCopyrightText: 2016 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ +#ifndef SETTINGS_H +#define SETTINGS_H + +#include +#include +#include + +#include "kirigamiplatform_export.h" + +namespace Kirigami +{ +namespace Platform +{ +/** + * This class contains global kirigami settings about the current device setup + * It is exposed to QML as the singleton "Settings" + */ +class KIRIGAMIPLATFORM_EXPORT Settings : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + + /** + * This property holds whether the system can dynamically enter and exit tablet mode + * (or the device is actually a tablet). + * This is the case for foldable convertibles and transformable laptops that support + * keyboard detachment. + */ + Q_PROPERTY(bool tabletModeAvailable READ isTabletModeAvailable NOTIFY tabletModeAvailableChanged FINAL) + + /** + * This property holds whether the application is running on a small mobile device + * such as a mobile phone. This is used when we want to do specific adaptations to + * the UI for small screen form factors, such as having bigger touch areas. + */ + Q_PROPERTY(bool isMobile READ isMobile NOTIFY isMobileChanged FINAL) + + /** + * This property holds whether the application is running on a device that is + * behaving like a tablet. + * + * @note This doesn't mean exactly a tablet form factor, but + * that the preferred input mode for the device is the touch screen + * and that pointer and keyboard are either secondary or not available. + */ + Q_PROPERTY(bool tabletMode READ tabletMode NOTIFY tabletModeChanged FINAL) + + /** + * This property holds whether the system has a platform menu bar; e.g. a user is + * on macOS or has a global menu on KDE Plasma. + * + * @warning Android has a platform menu bar; which may not be what you expected. + */ + Q_PROPERTY(bool hasPlatformMenuBar READ hasPlatformMenuBar CONSTANT FINAL) + + /** + * This property holds whether the user in this moment is interacting with the app + * with the touch screen. + */ + Q_PROPERTY(bool hasTransientTouchInput READ hasTransientTouchInput NOTIFY hasTransientTouchInputChanged FINAL) + + /** + * This property holds the name of the QtQuickControls2 style the application is using, + * for instance org.kde.desktop, Plasma, Material, Universal etc + */ + Q_PROPERTY(QString style READ style CONSTANT FINAL) + + // TODO: make this adapt without file watchers? + /** + * This property holds the number of lines of text the mouse wheel should scroll. + */ + Q_PROPERTY(int mouseWheelScrollLines READ mouseWheelScrollLines CONSTANT FINAL) + + /** + * This property holds whether to display animated transitions when scrolling with a + * mouse wheel or the keyboard. + */ + Q_PROPERTY(bool smoothScroll READ smoothScroll NOTIFY smoothScrollChanged FINAL) + + /** + * This property holds the runtime information about the libraries in use. + * + * @since 5.52 + * @since org.kde.kirigami 2.6 + */ + Q_PROPERTY(QStringList information READ information CONSTANT FINAL) + + /** + * This property holds the name of the application window icon. + * @see QGuiApplication::windowIcon() + * + * @since 5.62 + * @since org.kde.kirigami 2.10 + */ + Q_PROPERTY(QVariant applicationWindowIcon READ applicationWindowIcon CONSTANT FINAL) + +public: + Settings(QObject *parent = nullptr); + ~Settings() override; + + void setTabletModeAvailable(bool mobile); + bool isTabletModeAvailable() const; + + void setIsMobile(bool mobile); + bool isMobile() const; + + void setTabletMode(bool tablet); + bool tabletMode() const; + + void setTransientTouchInput(bool touch); + bool hasTransientTouchInput() const; + + bool hasPlatformMenuBar() const; + + QString style() const; + void setStyle(const QString &style); + + int mouseWheelScrollLines() const; + + bool smoothScroll() const; + + QStringList information() const; + + QVariant applicationWindowIcon() const; + +protected: + bool eventFilter(QObject *watched, QEvent *event) override; + +Q_SIGNALS: + void tabletModeAvailableChanged(); + void tabletModeChanged(); + void isMobileChanged(); + void hasTransientTouchInputChanged(); + void smoothScrollChanged(); + +private: + QString m_style; + int m_scrollLines = 0; + bool m_smoothScroll : 1; + bool m_tabletModeAvailable : 1; + bool m_mobile : 1; + bool m_tabletMode : 1; + bool m_hasTouchScreen : 1; + bool m_hasTransientTouchInput : 1; + bool m_hasPlatformMenuBar : 1; +}; + +} +} + +#endif diff --git a/local/recipes/kde/kirigami/source/src/platform/smoothscrollwatcher.cpp b/local/recipes/kde/kirigami/source/src/platform/smoothscrollwatcher.cpp new file mode 100644 index 00000000..af724437 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/smoothscrollwatcher.cpp @@ -0,0 +1,56 @@ +/* + * SPDX-FileCopyrightText: 2024 Nathan Misner + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "smoothscrollwatcher.h" + +#ifdef KIRIGAMI_ENABLE_DBUS +#include +#endif + +#include "kirigamiplatform_logging.h" + +namespace Kirigami +{ +namespace Platform +{ +Q_GLOBAL_STATIC(SmoothScrollWatcher, smoothScrollWatcherSelf) + +SmoothScrollWatcher::SmoothScrollWatcher(QObject *parent) + : QObject(parent) +{ +#ifdef KIRIGAMI_ENABLE_DBUS + QDBusConnection::sessionBus().connect(QStringLiteral(""), + QStringLiteral("/SmoothScroll"), + QStringLiteral("org.kde.SmoothScroll"), + QStringLiteral("notifyChange"), + this, + SLOT(setEnabled(bool))); +#endif + m_enabled = true; +} + +SmoothScrollWatcher::~SmoothScrollWatcher() = default; + +bool SmoothScrollWatcher::enabled() const +{ + return m_enabled; +} + +SmoothScrollWatcher *SmoothScrollWatcher::self() +{ + return smoothScrollWatcherSelf(); +} + +void SmoothScrollWatcher::setEnabled(bool value) +{ + m_enabled = value; + Q_EMIT enabledChanged(value); +} + +} +} + +#include "moc_smoothscrollwatcher.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/smoothscrollwatcher.h b/local/recipes/kde/kirigami/source/src/platform/smoothscrollwatcher.h new file mode 100644 index 00000000..882d0492 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/smoothscrollwatcher.h @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: 2024 Nathan Misner + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef KIRIGAMI_SMOOTHSCROLLWATCHER_H +#define KIRIGAMI_SMOOTHSCROLLWATCHER_H + +#include + +#include "kirigamiplatform_export.h" + +namespace Kirigami +{ +namespace Platform +{ +/** + * @class SmoothScrollWatcher smoothscrollwatcher.h + * + * This class reports on the status of the SmoothScroll DBus interface, + * which sends a message when the smooth scroll setting gets changed. + */ +class KIRIGAMIPLATFORM_EXPORT SmoothScrollWatcher : public QObject +{ + Q_OBJECT + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged FINAL) + +public: + SmoothScrollWatcher(QObject *parent = nullptr); + ~SmoothScrollWatcher(); + + bool enabled() const; + + static SmoothScrollWatcher *self(); + +Q_SIGNALS: + void enabledChanged(bool value); + +private: + bool m_enabled; + +private Q_SLOTS: + void setEnabled(bool value); +}; + +} +} + +#endif // KIRIGAMI_SMOOTHSCROLLWATCHER_H diff --git a/local/recipes/kde/kirigami/source/src/platform/styleselector.cpp b/local/recipes/kde/kirigami/source/src/platform/styleselector.cpp new file mode 100644 index 00000000..cea86498 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/styleselector.cpp @@ -0,0 +1,115 @@ +/* + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "styleselector.h" + +#include +#include +#include +#include + +namespace Kirigami +{ +namespace Platform +{ + +QString StyleSelector::style() +{ + if (qEnvironmentVariableIntValue("KIRIGAMI_FORCE_STYLE") == 1) { + return QQuickStyle::name(); + } else { + return styleChain().first(); + } +} + +QStringList StyleSelector::styleChain() +{ + if (qEnvironmentVariableIntValue("KIRIGAMI_FORCE_STYLE") == 1) { + return {QQuickStyle::name()}; + } + + if (!s_styleChain.isEmpty()) { + return s_styleChain; + } + + auto style = QQuickStyle::name(); + +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + // org.kde.desktop.plasma is a couple of files that fall back to desktop by purpose + if (style.isEmpty() || style == QStringLiteral("org.kde.desktop.plasma")) { + auto path = resolveFilePath(QStringLiteral("/styles/org.kde.desktop")); + if (QFile::exists(path)) { + s_styleChain.prepend(QStringLiteral("org.kde.desktop")); + } + } +#elif defined(Q_OS_ANDROID) + s_styleChain.prepend(QStringLiteral("Material")); +#else // do we have an iOS specific style? + s_styleChain.prepend(QStringLiteral("Material")); +#endif + + auto stylePath = resolveFilePath(QStringLiteral("/styles/") + style); + if (!style.isEmpty() && QFile::exists(stylePath) && !s_styleChain.contains(style)) { + s_styleChain.prepend(style); + // if we have plasma deps installed, use them for extra integration + auto plasmaPath = resolveFilePath(QStringLiteral("/styles/org.kde.desktop.plasma")); + if (style == QStringLiteral("org.kde.desktop") && QFile::exists(plasmaPath)) { + s_styleChain.prepend(QStringLiteral("org.kde.desktop.plasma")); + } + } else { +#if !defined(Q_OS_ANDROID) && !defined(Q_OS_IOS) + s_styleChain.prepend(QStringLiteral("org.kde.desktop")); +#endif + } + + return s_styleChain; +} + +QUrl StyleSelector::componentUrl(const QString &fileName) +{ + const auto chain = styleChain(); + for (const QString &style : chain) { + const QString candidate = QStringLiteral("styles/") + style + QLatin1Char('/') + fileName; + if (QFile::exists(resolveFilePath(candidate))) { + return QUrl(resolveFileUrl(candidate)); + } + } + + if (!QFile::exists(resolveFilePath(fileName))) { + qCWarning(KirigamiPlatform) << "Requested an unexisting component" << fileName; + } + return QUrl(resolveFileUrl(fileName)); +} + +void StyleSelector::setBaseUrl(const QUrl &baseUrl) +{ + s_baseUrl = baseUrl; +} + +QString StyleSelector::resolveFilePath(const QString &path) +{ +#if defined(KIRIGAMI_BUILD_TYPE_STATIC) || defined(Q_OS_ANDROID) + return QStringLiteral(":/qt/qml/org/kde/kirigami/") + path; +#else + if (s_baseUrl.isValid()) { + return s_baseUrl.toLocalFile() + QLatin1Char('/') + path; + } else { + return QDir::currentPath() + QLatin1Char('/') + path; + } +#endif +} + +QString StyleSelector::resolveFileUrl(const QString &path) +{ +#if defined(KIRIGAMI_BUILD_TYPE_STATIC) || defined(Q_OS_ANDROID) + return QStringLiteral("qrc:/qt/qml/org/kde/kirigami/") + path; +#else + return s_baseUrl.toString() + QLatin1Char('/') + path; +#endif +} + +} +} diff --git a/local/recipes/kde/kirigami/source/src/platform/styleselector.h b/local/recipes/kde/kirigami/source/src/platform/styleselector.h new file mode 100644 index 00000000..bfac31ce --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/styleselector.h @@ -0,0 +1,43 @@ +/* + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef STYLESELECTOR_H +#define STYLESELECTOR_H + +#include +#include + +#include "kirigamiplatform_export.h" + +class QUrl; + +namespace Kirigami +{ +namespace Platform +{ + +class KIRIGAMIPLATFORM_EXPORT StyleSelector +{ +public: + static QString style(); + static QStringList styleChain(); + + static QUrl componentUrl(const QString &fileName); + + static void setBaseUrl(const QUrl &baseUrl); + + static QString resolveFilePath(const QString &path); + static QString resolveFileUrl(const QString &path); + +private: + inline static QUrl s_baseUrl; + inline static QStringList s_styleChain; +}; + +} +} + +#endif // STYLESELECTOR_H diff --git a/local/recipes/kde/kirigami/source/src/platform/tabletmodewatcher.cpp b/local/recipes/kde/kirigami/source/src/platform/tabletmodewatcher.cpp new file mode 100644 index 00000000..b1e6c976 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/tabletmodewatcher.cpp @@ -0,0 +1,156 @@ +/* + * SPDX-FileCopyrightText: 2018 Marco Martin + * SPDX-FileCopyrightText: 2023 Harald Sitter + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "tabletmodewatcher.h" +#include + +#if defined(KIRIGAMI_ENABLE_DBUS) +#include "settings_interface.h" +#include +#endif + +using namespace Qt::Literals::StringLiterals; + +// TODO: All the dbus stuff should be conditional, optional win32 support + +namespace Kirigami +{ +namespace Platform +{ + +class TabletModeWatcherSingleton +{ +public: + TabletModeWatcher self; +}; + +Q_GLOBAL_STATIC(TabletModeWatcherSingleton, privateTabletModeWatcherSelf) + +class TabletModeWatcherPrivate +{ + static constexpr auto PORTAL_GROUP = "org.kde.TabletMode"_L1; + static constexpr auto KEY_AVAILABLE = "available"_L1; + static constexpr auto KEY_ENABLED = "enabled"_L1; + +public: + TabletModeWatcherPrivate(TabletModeWatcher *watcher) + : q(watcher) + { + // Called here to avoid collisions with application event types so we should use + // registerEventType for generating the event types. + TabletModeChangedEvent::type = QEvent::Type(QEvent::registerEventType()); +#if !defined(KIRIGAMI_ENABLE_DBUS) && (defined(Q_OS_ANDROID) || defined(Q_OS_IOS)) + isTabletModeAvailable = true; + isTabletMode = true; +#elif defined(KIRIGAMI_ENABLE_DBUS) + // Mostly for debug purposes and for platforms which are always mobile, + // such as Plasma Mobile + if (qEnvironmentVariableIsSet("QT_QUICK_CONTROLS_MOBILE") || qEnvironmentVariableIsSet("KDE_KIRIGAMI_TABLET_MODE")) { + isTabletMode = (QString::fromLatin1(qgetenv("QT_QUICK_CONTROLS_MOBILE")) == QLatin1String("1") + || QString::fromLatin1(qgetenv("QT_QUICK_CONTROLS_MOBILE")) == QLatin1String("true")) + || (QString::fromLatin1(qgetenv("KDE_KIRIGAMI_TABLET_MODE")) == QLatin1String("1") + || QString::fromLatin1(qgetenv("KDE_KIRIGAMI_TABLET_MODE")) == QLatin1String("true")); + isTabletModeAvailable = isTabletMode; + } else if (qEnvironmentVariableIsSet("QT_NO_XDG_DESKTOP_PORTAL")) { + isTabletMode = false; + } else { + qDBusRegisterMetaType(); + auto portal = new OrgFreedesktopPortalSettingsInterface(u"org.freedesktop.portal.Desktop"_s, + u"/org/freedesktop/portal/desktop"_s, + QDBusConnection::sessionBus(), + q); + + QObject::connect(portal, + &OrgFreedesktopPortalSettingsInterface::SettingChanged, + q, + [this](const QString &group, const QString &key, const QDBusVariant &value) { + if (group != PORTAL_GROUP) { + return; + } + if (key == KEY_AVAILABLE) { + Q_EMIT q->tabletModeAvailableChanged(value.variant().toBool()); + } else if (key == KEY_ENABLED) { + setIsTablet(value.variant().toBool()); + } + }); + + const auto reply = portal->ReadAll({PORTAL_GROUP}); + auto watcher = new QDBusPendingCallWatcher(reply, q); + QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this, watcher]() { + watcher->deleteLater(); + QDBusPendingReply reply = *watcher; + const auto properties = reply.value().value(PORTAL_GROUP); + Q_EMIT q->tabletModeAvailableChanged(properties[KEY_AVAILABLE].toBool()); + setIsTablet(properties[KEY_ENABLED].toBool()); + }); + } +// TODO: case for Windows +#endif + } + ~TabletModeWatcherPrivate() = default; + void setIsTablet(bool tablet); + + TabletModeWatcher *q; + QList watchers; + bool isTabletModeAvailable = false; + bool isTabletMode = false; +}; + +void TabletModeWatcherPrivate::setIsTablet(bool tablet) +{ + if (isTabletMode == tablet) { + return; + } + + isTabletMode = tablet; + TabletModeChangedEvent event{tablet}; + Q_EMIT q->tabletModeChanged(tablet); + for (auto *w : watchers) { + QCoreApplication::sendEvent(w, &event); + } +} + +TabletModeWatcher::TabletModeWatcher(QObject *parent) + : QObject(parent) + , d(new TabletModeWatcherPrivate(this)) +{ +} + +TabletModeWatcher::~TabletModeWatcher() +{ + delete d; +} + +TabletModeWatcher *TabletModeWatcher::self() +{ + return &privateTabletModeWatcherSelf()->self; +} + +bool TabletModeWatcher::isTabletModeAvailable() const +{ + return d->isTabletModeAvailable; +} + +bool TabletModeWatcher::isTabletMode() const +{ + return d->isTabletMode; +} + +void TabletModeWatcher::addWatcher(QObject *watcher) +{ + d->watchers.append(watcher); +} + +void TabletModeWatcher::removeWatcher(QObject *watcher) +{ + d->watchers.removeAll(watcher); +} + +} +} + +#include "moc_tabletmodewatcher.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/tabletmodewatcher.h b/local/recipes/kde/kirigami/source/src/platform/tabletmodewatcher.h new file mode 100644 index 00000000..d405ae06 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/tabletmodewatcher.h @@ -0,0 +1,100 @@ +/* + * SPDX-FileCopyrightText: 2018 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef KIRIGAMI_TABLETMODEWATCHER_H +#define KIRIGAMI_TABLETMODEWATCHER_H + +#include +#include + +#include "kirigamiplatform_export.h" + +namespace Kirigami +{ +namespace Platform +{ +class TabletModeWatcherPrivate; + +class KIRIGAMIPLATFORM_EXPORT TabletModeChangedEvent : public QEvent +{ +public: + TabletModeChangedEvent(bool tablet) + : QEvent(TabletModeChangedEvent::type) + , tabletMode(tablet) + { + } + + bool tabletMode = false; + + inline static QEvent::Type type = QEvent::None; +}; + +/** + * @class TabletModeWatcher tabletmodewatcher.h + * + * This class reports on the status of certain transformable + * devices which can be both tablets and laptops at the same time, + * with a detachable keyboard. + * It reports whether the device supports a tablet mode and if + * the device is currently in such mode or not, emitting a signal + * when the user switches. + */ +class KIRIGAMIPLATFORM_EXPORT TabletModeWatcher : public QObject +{ + Q_OBJECT + + Q_PROPERTY(bool tabletModeAvailable READ isTabletModeAvailable NOTIFY tabletModeAvailableChanged FINAL) + Q_PROPERTY(bool tabletMode READ isTabletMode NOTIFY tabletModeChanged FINAL) + +public: + ~TabletModeWatcher() override; + static TabletModeWatcher *self(); + + /** + * @returns true if the device supports a tablet mode and has a switch + * to report when the device has been transformed. + * For debug purposes, if either the environment variable QT_QUICK_CONTROLS_MOBILE + * or KDE_KIRIGAMI_TABLET_MODE are set to true, isTabletModeAvailable will be true + */ + bool isTabletModeAvailable() const; + + /** + * @returns true if the machine is now in tablet mode, such as the + * laptop keyboard flipped away or detached. + * Note that this doesn't mean exactly a tablet form factor, but + * that the preferred input mode for the device is the touch screen + * and that pointer and keyboard are either secondary or not available. + * + * For debug purposes, if either the environment variable QT_QUICK_CONTROLS_MOBILE + * or KDE_KIRIGAMI_TABLET_MODE are set to true, isTabletMode will be true + */ + bool isTabletMode() const; + + /** + * Register an arbitrary QObject to send events from this. + * At the moment only one event will be sent: TabletModeChangedEvent + */ + void addWatcher(QObject *watcher); + + /* + * Unsubscribe watcher from receiving events from TabletModeWatcher. + */ + void removeWatcher(QObject *watcher); + +Q_SIGNALS: + void tabletModeAvailableChanged(bool tabletModeAvailable); + void tabletModeChanged(bool tabletMode); + +private: + KIRIGAMIPLATFORM_NO_EXPORT explicit TabletModeWatcher(QObject *parent = nullptr); + TabletModeWatcherPrivate *d; + friend class TabletModeWatcherSingleton; +}; + +} +} + +#endif // KIRIGAMI_TABLETMODEWATCHER diff --git a/local/recipes/kde/kirigami/source/src/platform/units.cpp b/local/recipes/kde/kirigami/source/src/platform/units.cpp new file mode 100644 index 00000000..0591e87a --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/units.cpp @@ -0,0 +1,361 @@ +/* + * SPDX-FileCopyrightText: 2020 Jonah Brüchert + * SPDX-FileCopyrightText: 2015 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "units.h" + +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "kirigamiplatform_logging.h" +#include "platformpluginfactory.h" + +namespace Kirigami +{ +namespace Platform +{ + +class UnitsPrivate +{ + Q_DISABLE_COPY(UnitsPrivate) + +public: + explicit UnitsPrivate(Units *units) + // Cache font so we don't have to go through QVariant and property every time + : fontMetrics(QFontMetricsF(QGuiApplication::font())) + , gridUnit(18) + , smallSpacing(4) + , mediumSpacing(6) + , largeSpacing(8) + , veryLongDuration(400) + , longDuration(200) + , shortDuration(100) + , veryShortDuration(50) + , humanMoment(2000) + , toolTipDelay(700) + , cornerRadius(5) + , iconSizes(new IconSizes(units)) + { + } + + // Font metrics used for Units. + // TextMetrics uses QFontMetricsF internally, so this should do the same + QFontMetricsF fontMetrics; + + // units + int gridUnit; + int smallSpacing; + int mediumSpacing; + int largeSpacing; + + // durations + int veryLongDuration; + int longDuration; + int shortDuration; + int veryShortDuration; + int humanMoment; + int toolTipDelay; + qreal cornerRadius; + + IconSizes *const iconSizes; + + // To prevent overriding custom set units if the font changes + bool customUnitsSet = false; +}; + +Units::~Units() = default; + +Units::Units(QObject *parent) + : QObject(parent) + , d(std::make_unique(this)) +{ + qGuiApp->installEventFilter(this); +} + +int Units::gridUnit() const +{ + return d->gridUnit; +} + +void Units::setGridUnit(int size) +{ + if (d->gridUnit == size) { + return; + } + + d->gridUnit = size; + d->customUnitsSet = true; + Q_EMIT gridUnitChanged(); +} + +int Units::smallSpacing() const +{ + return d->smallSpacing; +} + +void Units::setSmallSpacing(int size) +{ + if (d->smallSpacing == size) { + return; + } + + d->smallSpacing = size; + d->customUnitsSet = true; + Q_EMIT smallSpacingChanged(); +} + +int Units::mediumSpacing() const +{ + return d->mediumSpacing; +} + +void Units::setMediumSpacing(int size) +{ + if (d->mediumSpacing == size) { + return; + } + + d->mediumSpacing = size; + d->customUnitsSet = true; + Q_EMIT mediumSpacingChanged(); +} + +int Units::largeSpacing() const +{ + return d->largeSpacing; +} + +void Units::setLargeSpacing(int size) +{ + if (d->largeSpacing) { + return; + } + + d->largeSpacing = size; + d->customUnitsSet = true; + Q_EMIT largeSpacingChanged(); +} + +int Units::veryLongDuration() const +{ + return d->veryLongDuration; +} + +void Units::setVeryLongDuration(int duration) +{ + if (d->veryLongDuration == duration) { + return; + } + + d->veryLongDuration = duration; + Q_EMIT veryLongDurationChanged(); +} + +int Units::longDuration() const +{ + return d->longDuration; +} + +void Units::setLongDuration(int duration) +{ + if (d->longDuration == duration) { + return; + } + + d->longDuration = duration; + Q_EMIT longDurationChanged(); +} + +int Units::shortDuration() const +{ + return d->shortDuration; +} + +void Units::setShortDuration(int duration) +{ + if (d->shortDuration == duration) { + return; + } + + d->shortDuration = duration; + Q_EMIT shortDurationChanged(); +} + +int Units::veryShortDuration() const +{ + return d->veryShortDuration; +} + +void Units::setVeryShortDuration(int duration) +{ + if (d->veryShortDuration == duration) { + return; + } + + d->veryShortDuration = duration; + Q_EMIT veryShortDurationChanged(); +} + +int Units::humanMoment() const +{ + return d->humanMoment; +} + +void Units::setHumanMoment(int duration) +{ + if (d->humanMoment == duration) { + return; + } + + d->humanMoment = duration; + Q_EMIT humanMomentChanged(); +} + +int Units::toolTipDelay() const +{ + return d->toolTipDelay; +} + +void Units::setToolTipDelay(int delay) +{ + if (d->toolTipDelay == delay) { + return; + } + + d->toolTipDelay = delay; + Q_EMIT toolTipDelayChanged(); +} + +qreal Units::cornerRadius() const +{ + return d->cornerRadius; +} + +void Units::setcornerRadius(qreal cornerRadius) +{ + if (d->cornerRadius == cornerRadius) { + return; + } + + d->cornerRadius = cornerRadius; + Q_EMIT cornerRadiusChanged(); +} + +Units *Units::create(QQmlEngine *qmlEngine, [[maybe_unused]] QJSEngine *jsEngine) +{ +#ifndef KIRIGAMI_BUILD_TYPE_STATIC + const QString pluginName = qmlEngine->property("_kirigamiTheme").toString(); + + auto plugin = PlatformPluginFactory::findPlugin(pluginName); + if (!plugin && !pluginName.isEmpty()) { + plugin = PlatformPluginFactory::findPlugin(); + } + + if (plugin) { + return plugin->createUnits(qmlEngine); + } +#endif + // Fall back to the default units implementation + return new Units(qmlEngine); +} + +bool Units::eventFilter([[maybe_unused]] QObject *watched, QEvent *event) +{ + if (event->type() == QEvent::ApplicationFontChange) { + d->fontMetrics = QFontMetricsF(qGuiApp->font()); + + if (d->customUnitsSet) { + return false; + } + + Q_EMIT d->iconSizes->sizeForLabelsChanged(); + } + return false; +} + +IconSizes *Units::iconSizes() const +{ + return d->iconSizes; +} + +IconSizes::IconSizes(Units *units) + : QObject(units) + , m_units(units) +{ +} + +int IconSizes::roundedIconSize(int size) const +{ + if (size < 16) { + return size; + } + + if (size < 22) { + return 16; + } + + if (size < 32) { + return 22; + } + + if (size < 48) { + return 32; + } + + if (size < 64) { + return 48; + } + + return size; +} + +int IconSizes::sizeForLabels() const +{ + // gridUnit is the height of textMetrics + return roundedIconSize(m_units->d->fontMetrics.height()); +} + +int IconSizes::small() const +{ + return 16; +} + +int IconSizes::smallMedium() const +{ + return 22; +} + +int IconSizes::medium() const +{ + return 32; +} + +int IconSizes::large() const +{ + return 48; +} + +int IconSizes::huge() const +{ + return 64; +} + +int IconSizes::enormous() const +{ + return 128; +} +} +} + +#include "moc_units.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/units.h b/local/recipes/kde/kirigami/source/src/platform/units.h new file mode 100644 index 00000000..be12d7f9 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/units.h @@ -0,0 +1,265 @@ +/* + * SPDX-FileCopyrightText: 2021 Jonah Brüchert + * SPDX-FileCopyrightText: 2015 Marco Martin + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef KIRIGAMI_UNITS_H +#define KIRIGAMI_UNITS_H + +#include + +#include +#include + +#include "kirigamiplatform_export.h" + +class QQmlEngine; + +namespace Kirigami +{ +namespace Platform +{ +class Units; +class UnitsPrivate; + +/** + * @class IconSizes units.h + * + * Provides access to platform-dependent icon sizing + */ +class KIRIGAMIPLATFORM_EXPORT IconSizes : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_UNCREATABLE("Grouped Property") + + Q_PROPERTY(int sizeForLabels READ sizeForLabels NOTIFY sizeForLabelsChanged FINAL) + Q_PROPERTY(int small READ small NOTIFY smallChanged FINAL) + Q_PROPERTY(int smallMedium READ smallMedium NOTIFY smallMediumChanged FINAL) + Q_PROPERTY(int medium READ medium NOTIFY mediumChanged FINAL) + Q_PROPERTY(int large READ large NOTIFY largeChanged FINAL) + Q_PROPERTY(int huge READ huge NOTIFY hugeChanged FINAL) + Q_PROPERTY(int enormous READ enormous NOTIFY enormousChanged FINAL) + +public: + IconSizes(Units *units); + + int sizeForLabels() const; + int small() const; + int smallMedium() const; + int medium() const; + int large() const; + int huge() const; + int enormous() const; + + Q_INVOKABLE int roundedIconSize(int size) const; + +private: + KIRIGAMIPLATFORM_NO_EXPORT float iconScaleFactor() const; + + Units *m_units; + +Q_SIGNALS: + void sizeForLabelsChanged(); + void smallChanged(); + void smallMediumChanged(); + void mediumChanged(); + void largeChanged(); + void hugeChanged(); + void enormousChanged(); +}; + +/** + * @class Units units.h + * + * A set of values to define semantically sizes and durations. + */ +class KIRIGAMIPLATFORM_EXPORT Units : public QObject +{ + Q_OBJECT + QML_ELEMENT + QML_SINGLETON + + friend class IconSizes; + + /** + * The fundamental unit of space that should be used for sizes, expressed in pixels. + */ + Q_PROPERTY(int gridUnit READ gridUnit NOTIFY gridUnitChanged FINAL) + + /** + * units.iconSizes provides access to platform-dependent icon sizing + * + * The icon sizes provided are normalized for different DPI, so icons + * will scale depending on the DPI. + * + * * sizeForLabels (the largest icon size that fits within fontMetrics.height) @since 5.80 @since org.kde.kirigami 2.16 + * * small + * * smallMedium + * * medium + * * large + * * huge + * * enormous + */ + Q_PROPERTY(Kirigami::Platform::IconSizes *iconSizes READ iconSizes CONSTANT FINAL) + + /** + * This property holds the amount of spacing that should be used between smaller UI elements, + * such as a small icon and a label in a button. + */ + Q_PROPERTY(int smallSpacing READ smallSpacing NOTIFY smallSpacingChanged FINAL) + + /** + * This property holds the amount of spacing that should be used between medium UI elements, + * such as buttons and text fields in a toolbar. + */ + Q_PROPERTY(int mediumSpacing READ mediumSpacing NOTIFY mediumSpacingChanged FINAL) + + /** + * This property holds the amount of spacing that should be used between bigger UI elements, + * such as a large icon and a heading in a card. + */ + Q_PROPERTY(int largeSpacing READ largeSpacing NOTIFY largeSpacingChanged FINAL) + + /** + * units.veryLongDuration should be used for specialty animations that benefit + * from being even longer than longDuration. + */ + Q_PROPERTY(int veryLongDuration READ veryLongDuration NOTIFY veryLongDurationChanged FINAL) + + /** + * units.longDuration should be used for longer, screen-covering animations, for opening and + * closing of dialogs and other "not too small" animations + */ + Q_PROPERTY(int longDuration READ longDuration NOTIFY longDurationChanged FINAL) + + /** + * units.shortDuration should be used for short animations, such as accentuating a UI event, + * hover events, etc.. + */ + Q_PROPERTY(int shortDuration READ shortDuration NOTIFY shortDurationChanged FINAL) + + /** + * units.veryShortDuration should be used for elements that should have a hint of smoothness, + * but otherwise animate near instantly. + */ + Q_PROPERTY(int veryShortDuration READ veryShortDuration NOTIFY veryShortDurationChanged FINAL) + + /** + * Time in milliseconds equivalent to the theoretical human moment, which can be used + * to determine whether how long to wait until the user should be informed of something, + * or can be used as the limit for how long something should wait before being + * automatically initiated. + * + * Some examples: + * + * - When the user types text in a search field, wait no longer than this duration after + * the user completes typing before starting the search + * - When loading data which would commonly arrive rapidly enough to not require interaction, + * wait this long before showing a spinner + * + * This might seem an arbitrary number, but given the psychological effect that three + * seconds seems to be what humans consider a moment (and in the case of waiting for + * something to happen, a moment is that time when you think "this is taking a bit long, + * isn't it?"), the idea is to postpone for just before such a conceptual moment. The reason + * for the two seconds, rather than three, is to function as a middle ground: Not long enough + * that the user would think that something has taken too long, for also not so fast as to + * happen too soon. + * + * See also + * https://www.psychologytoday.com/blog/all-about-addiction/201101/tick-tock-tick-hugs-and-life-in-3-second-intervals + * (the actual paper is hidden behind an academic paywall and consequently not readily + * available to us, so the source will have to be the blog entry above) + * + * \note This should __not__ be used as an animation duration, as it is deliberately not scaled according + * to the animation settings. This is specifically for determining when something has taken too long and + * the user should expect some kind of feedback. See veryShortDuration, shortDuration, longDuration, and + * veryLongDuration for animation duration choices. + * + * @since 5.81 + * @since org.kde.kirigami 2.16 + */ + Q_PROPERTY(int humanMoment READ humanMoment NOTIFY humanMomentChanged FINAL) + + /** + * time in ms by which the display of tooltips will be delayed. + * + * @sa ToolTip.delay property + */ + Q_PROPERTY(int toolTipDelay READ toolTipDelay NOTIFY toolTipDelayChanged FINAL) + + /** + * Corner radius value shared by buttons and other rectangle elements + * + * @since 6.2 + */ + Q_PROPERTY(qreal cornerRadius READ cornerRadius NOTIFY cornerRadiusChanged FINAL) + +public: + ~Units() override; + + int gridUnit() const; + void setGridUnit(int size); + + int smallSpacing() const; + void setSmallSpacing(int size); + + int mediumSpacing() const; + void setMediumSpacing(int size); + + int largeSpacing() const; + void setLargeSpacing(int size); + + int veryLongDuration() const; + void setVeryLongDuration(int duration); + + int longDuration() const; + void setLongDuration(int duration); + + int shortDuration() const; + void setShortDuration(int duration); + + int veryShortDuration() const; + void setVeryShortDuration(int duration); + + int humanMoment() const; + void setHumanMoment(int duration); + + int toolTipDelay() const; + void setToolTipDelay(int delay); + + qreal cornerRadius() const; + void setcornerRadius(qreal cornerRadius); + + IconSizes *iconSizes() const; + + static Units *create(QQmlEngine *qmlEngine, QJSEngine *jsEngine); + +Q_SIGNALS: + void gridUnitChanged(); + void smallSpacingChanged(); + void mediumSpacingChanged(); + void largeSpacingChanged(); + void veryLongDurationChanged(); + void longDurationChanged(); + void shortDurationChanged(); + void veryShortDurationChanged(); + void humanMomentChanged(); + void toolTipDelayChanged(); + void wheelScrollLinesChanged(); + void cornerRadiusChanged(); + +protected: + explicit Units(QObject *parent = nullptr); + bool eventFilter(QObject *watched, QEvent *event) override; + +private: + std::unique_ptr d; +}; + +} +} + +#endif diff --git a/local/recipes/kde/kirigami/source/src/platform/virtualkeyboardwatcher.cpp b/local/recipes/kde/kirigami/source/src/platform/virtualkeyboardwatcher.cpp new file mode 100644 index 00000000..ff36de9a --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/virtualkeyboardwatcher.cpp @@ -0,0 +1,189 @@ +/* + * SPDX-FileCopyrightText: 2018 Marco Martin + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * SPDX-FileCopyrightText: 2023 Harald Sitter + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#include "virtualkeyboardwatcher.h" + +#ifdef KIRIGAMI_ENABLE_DBUS +#include "settings_interface.h" +#include +#include +#endif + +#include "kirigamiplatform_logging.h" + +using namespace Qt::Literals::StringLiterals; + +namespace Kirigami +{ +namespace Platform +{ +Q_GLOBAL_STATIC(VirtualKeyboardWatcher, virtualKeyboardWatcherSelf) + +class KIRIGAMIPLATFORM_NO_EXPORT VirtualKeyboardWatcher::Private +{ + static constexpr auto serviceName = "org.freedesktop.portal.Desktop"_L1; + static constexpr auto objectName = "/org/freedesktop/portal/desktop"_L1; + static constexpr auto interfaceName = "org.kde.kwin.VirtualKeyboard"_L1; + + static constexpr auto GROUP = "org.kde.VirtualKeyboard"_L1; + static constexpr auto KEY_AVAILABLE = "available"_L1; + static constexpr auto KEY_ENABLED = "enabled"_L1; + static constexpr auto KEY_ACTIVE = "active"_L1; + static constexpr auto KEY_VISIBLE = "visible"_L1; + static constexpr auto KEY_WILL_SHOW_ON_ACTIVE = "willShowOnActive"_L1; + +public: + Private(VirtualKeyboardWatcher *qq) + : q(qq) + { +#ifdef KIRIGAMI_ENABLE_DBUS + qDBusRegisterMetaType(); + settingsInterface = new OrgFreedesktopPortalSettingsInterface(serviceName, objectName, QDBusConnection::sessionBus(), q); + + QObject::connect(settingsInterface, + &OrgFreedesktopPortalSettingsInterface::SettingChanged, + q, + [this](const QString &group, const QString &key, const QDBusVariant &value) { + if (group != GROUP) { + return; + } + + if (key == KEY_AVAILABLE) { + available = value.variant().toBool(); + Q_EMIT q->availableChanged(); + } else if (key == KEY_ENABLED) { + enabled = value.variant().toBool(); + Q_EMIT q->enabledChanged(); + } else if (key == KEY_ACTIVE) { + active = value.variant().toBool(); + Q_EMIT q->activeChanged(); + } else if (key == KEY_VISIBLE) { + visible = value.variant().toBool(); + Q_EMIT q->visibleChanged(); + } else if (key == KEY_WILL_SHOW_ON_ACTIVE) { + willShowOnActive = value.variant().toBool(); + } + }); + + getAllProperties(); +#endif + } + + VirtualKeyboardWatcher *q; + +#ifdef KIRIGAMI_ENABLE_DBUS + void getAllProperties(); + void updateWillShowOnActive(); + + OrgFreedesktopPortalSettingsInterface *settingsInterface = nullptr; + + QDBusPendingCallWatcher *willShowOnActiveCall = nullptr; +#endif + + bool available = false; + bool enabled = false; + bool active = false; + bool visible = false; + bool willShowOnActive = false; +}; + +VirtualKeyboardWatcher::VirtualKeyboardWatcher(QObject *parent) + : QObject(parent) + , d(std::make_unique(this)) +{ +} + +VirtualKeyboardWatcher::~VirtualKeyboardWatcher() = default; + +bool VirtualKeyboardWatcher::available() const +{ + return d->available; +} + +bool VirtualKeyboardWatcher::enabled() const +{ + return d->enabled; +} + +bool VirtualKeyboardWatcher::active() const +{ + return d->active; +} + +bool VirtualKeyboardWatcher::visible() const +{ + return d->visible; +} + +bool VirtualKeyboardWatcher::willShowOnActive() const +{ +#ifdef KIRIGAMI_ENABLE_DBUS + d->updateWillShowOnActive(); +#endif + return d->willShowOnActive; +} + +VirtualKeyboardWatcher *VirtualKeyboardWatcher::self() +{ + return virtualKeyboardWatcherSelf(); +} + +#ifdef KIRIGAMI_ENABLE_DBUS + +void VirtualKeyboardWatcher::Private::updateWillShowOnActive() +{ + if (willShowOnActiveCall) { + return; + } + + willShowOnActiveCall = new QDBusPendingCallWatcher(settingsInterface->Read(GROUP, KEY_WILL_SHOW_ON_ACTIVE), q); + connect(willShowOnActiveCall, &QDBusPendingCallWatcher::finished, q, [this](auto call) { + QDBusPendingReply reply = *call; + if (reply.isError()) { + qCDebug(KirigamiPlatform) << reply.error().message(); + } else { + if (reply.value().toBool() != willShowOnActive) { + willShowOnActive = reply.value().toBool(); + Q_EMIT q->willShowOnActiveChanged(); + } + } + call->deleteLater(); + willShowOnActiveCall = nullptr; + }); +} + +void VirtualKeyboardWatcher::Private::getAllProperties() +{ + auto call = new QDBusPendingCallWatcher(settingsInterface->ReadAll({GROUP}), q); + connect(call, &QDBusPendingCallWatcher::finished, q, [this](auto call) { + QDBusPendingReply reply = *call; + if (reply.isError()) { + qCDebug(KirigamiPlatform) << reply.error().message(); + } else { + const auto groupValues = reply.value().value(GROUP); + available = groupValues.value(KEY_AVAILABLE).toBool(); + enabled = groupValues.value(KEY_ENABLED).toBool(); + active = groupValues.value(KEY_ACTIVE).toBool(); + visible = groupValues.value(KEY_VISIBLE).toBool(); + willShowOnActive = groupValues.value(KEY_WILL_SHOW_ON_ACTIVE).toBool(); + } + call->deleteLater(); + + Q_EMIT q->availableChanged(); + Q_EMIT q->enabledChanged(); + Q_EMIT q->activeChanged(); + Q_EMIT q->visibleChanged(); + }); +} + +#endif + +} +} + +#include "moc_virtualkeyboardwatcher.cpp" diff --git a/local/recipes/kde/kirigami/source/src/platform/virtualkeyboardwatcher.h b/local/recipes/kde/kirigami/source/src/platform/virtualkeyboardwatcher.h new file mode 100644 index 00000000..78045c84 --- /dev/null +++ b/local/recipes/kde/kirigami/source/src/platform/virtualkeyboardwatcher.h @@ -0,0 +1,66 @@ +/* + * SPDX-FileCopyrightText: 2018 Marco Martin + * SPDX-FileCopyrightText: 2021 Arjen Hiemstra + * + * SPDX-License-Identifier: LGPL-2.0-or-later + */ + +#ifndef KIRIGAMI_VIRTUALKEYBOARDWATCHER_H +#define KIRIGAMI_VIRTUALKEYBOARDWATCHER_H + +#include + +#include + +#include "kirigamiplatform_export.h" + +namespace Kirigami +{ +namespace Platform +{ +/** + * @class VirtualKeyboardWatcher virtualkeyboardwatcher.h + * + * This class reports on the status of KWin's VirtualKeyboard DBus interface. + * + * @since 5.91 + */ +class KIRIGAMIPLATFORM_EXPORT VirtualKeyboardWatcher : public QObject +{ + Q_OBJECT + +public: + VirtualKeyboardWatcher(QObject *parent = nullptr); + ~VirtualKeyboardWatcher(); + + Q_PROPERTY(bool available READ available NOTIFY availableChanged FINAL) + bool available() const; + Q_SIGNAL void availableChanged(); + + Q_PROPERTY(bool enabled READ enabled NOTIFY enabledChanged FINAL) + bool enabled() const; + Q_SIGNAL void enabledChanged(); + + Q_PROPERTY(bool active READ active NOTIFY activeChanged FINAL) + bool active() const; + Q_SIGNAL void activeChanged(); + + Q_PROPERTY(bool visible READ visible NOTIFY visibleChanged FINAL) + bool visible() const; + Q_SIGNAL void visibleChanged(); + + Q_PROPERTY(bool willShowOnActive READ willShowOnActive NOTIFY willShowOnActiveChanged FINAL) + bool willShowOnActive() const; + Q_SIGNAL void willShowOnActiveChanged(); + + static VirtualKeyboardWatcher *self(); + +private: + class Private; + const std::unique_ptr d; +}; + +} +} + +#endif // KIRIGAMI_VIRTUALKEYBOARDWATCHER diff --git a/local/recipes/kde/kirigami/source/src/primitives/icon.h b/local/recipes/kde/kirigami/source/src/primitives/icon.h index 0cf6a45a..83de81ea 100644 --- a/local/recipes/kde/kirigami/source/src/primitives/icon.h +++ b/local/recipes/kde/kirigami/source/src/primitives/icon.h @@ -34,7 +34,7 @@ class Units; class Icon : public QQuickItem { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT /** * The source of this icon. An `Icon` can pull from: diff --git a/local/recipes/kde/kirigami/source/src/primitives/shadowedrectangle.h b/local/recipes/kde/kirigami/source/src/primitives/shadowedrectangle.h index 49545524..ed1d8813 100644 --- a/local/recipes/kde/kirigami/source/src/primitives/shadowedrectangle.h +++ b/local/recipes/kde/kirigami/source/src/primitives/shadowedrectangle.h @@ -19,8 +19,8 @@ class PaintedRectangleItem; class BorderGroup : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("") + QML_ELEMENT + QML_UNCREATABLE("") /** * @brief This property holds the border's width in pixels. * @@ -63,8 +63,8 @@ private: class ShadowGroup : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("") + QML_ELEMENT + QML_UNCREATABLE("") /** * @brief This property holds the shadow's approximate size in pixels. * @note The actual shadow size can be less than this value due to falloff. @@ -123,8 +123,8 @@ private: class CornersGroup : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("") + QML_ELEMENT + QML_UNCREATABLE("") /** * @brief This property holds the top-left corner's radius in pixels. * @@ -201,7 +201,7 @@ private: class ShadowedRectangle : public QQuickItem { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT /** * @brief This property holds the radii of the rectangle's corners. * diff --git a/local/recipes/kde/kirigami/source/src/primitives/shadowedtexture.h b/local/recipes/kde/kirigami/source/src/primitives/shadowedtexture.h index 00e5614a..39798042 100644 --- a/local/recipes/kde/kirigami/source/src/primitives/shadowedtexture.h +++ b/local/recipes/kde/kirigami/source/src/primitives/shadowedtexture.h @@ -21,7 +21,7 @@ class ShadowedTexture : public ShadowedRectangle { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF + QML_ELEMENT /** * This property holds the source item that will get rendered with the diff --git a/local/recipes/kde/kirigami/source/src/scenepositionattached.h b/local/recipes/kde/kirigami/source/src/scenepositionattached.h index a964a980..afef25c5 100644 --- a/local/recipes/kde/kirigami/source/src/scenepositionattached.h +++ b/local/recipes/kde/kirigami/source/src/scenepositionattached.h @@ -26,10 +26,10 @@ class QQuickItem; class ScenePositionAttached : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(ScenePositionAttached) - QML_NAMED_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF(ScenePosition) - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("") + QML_ELEMENT + QML_ATTACHED(ScenePositionAttached) + QML_NAMED_ELEMENT(ScenePosition) + QML_UNCREATABLE("") /** * The global scene X position */ diff --git a/local/recipes/kde/kirigami/source/src/spellcheckattached.h b/local/recipes/kde/kirigami/source/src/spellcheckattached.h index 0c6d9575..fdfa8b64 100644 --- a/local/recipes/kde/kirigami/source/src/spellcheckattached.h +++ b/local/recipes/kde/kirigami/source/src/spellcheckattached.h @@ -32,10 +32,10 @@ class SpellCheckAttached : public QObject { Q_OBJECT - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF - QML_NAMED_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF(SpellCheck) - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("Attached property only") - QML_ATTACHED_OFF_OFF_OFF_OFF_OFF_OFF(SpellCheckAttached) + QML_ELEMENT + QML_NAMED_ELEMENT(SpellCheck) + QML_UNCREATABLE("Attached property only") + QML_ATTACHED(SpellCheckAttached) /** * This property holds whether the spell checking should be enabled on the diff --git a/local/recipes/kde/kirigami/source/src/wheelhandler.h b/local/recipes/kde/kirigami/source/src/wheelhandler.h index ed3b450c..70ff9d9c 100644 --- a/local/recipes/kde/kirigami/source/src/wheelhandler.h +++ b/local/recipes/kde/kirigami/source/src/wheelhandler.h @@ -27,8 +27,8 @@ class WheelHandler; class KirigamiWheelEvent : public QObject { Q_OBJECT - QML_NAMED_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF(WheelEvent) - QML_UNCREATABLE_OFF_OFF_OFF_OFF_OFF_OFF("") + QML_NAMED_ELEMENT(WheelEvent) + QML_UNCREATABLE("") /** * x: real @@ -179,8 +179,8 @@ public: class WheelHandler : public QObject, public QQmlParserStatus { Q_OBJECT - - QML_ELEMENT_OFF_OFF_OFF_OFF_OFF_OFF + Q_INTERFACES(QQmlParserStatus) + QML_ELEMENT /** * @brief This property holds the Qt Quick Flickable that the WheelHandler will control. diff --git a/local/scripts/test-network-qemu.sh b/local/scripts/test-network-qemu.sh new file mode 100755 index 00000000..f29eb79e --- /dev/null +++ b/local/scripts/test-network-qemu.sh @@ -0,0 +1,33 @@ +#!/bin/bash +# test-network-qemu.sh — bounded network driver validation in QEMU +# Phase 5.1 of CHANGELOG-DRIVER-IMPROVEMENT-PLAN.md +set -euo pipefail +IMAGE="${1:-build/x86_64/redbear-mini/harddrive.img}" +QEMU="${QEMU:-qemu-system-x86_64}" +OVMF_CODE="${OVMF_CODE:-/usr/share/edk2/x64/OVMF_CODE.4m.fd}" +TIMEOUT="${TIMEOUT:-90}" +SERIAL_LOG="/tmp/rbos-net-test-$$.log" + +[ -f "$IMAGE" ] || { echo "FAIL: image not found: $IMAGE"; exit 1; } +cp /usr/share/edk2/x64/OVMF_VARS.4m.fd /tmp/fw_vars_net.bin 2>/dev/null + +echo "=== Network Driver Validation ===" + +timeout "$TIMEOUT" "$QEMU" -M q35 -m 2G -smp 2 \ + -drive file="$IMAGE",format=raw \ + -drive if=pflash,format=raw,readonly=on,file="$OVMF_CODE" \ + -drive if=pflash,format=raw,file=/tmp/fw_vars_net.bin \ + -serial file:"$SERIAL_LOG" -display none -no-reboot \ + -netdev user,id=net0 -device virtio-net-pci,netdev=net0 \ + -netdev user,id=net1 -device e1000,netdev=net1 \ + -d guest_errors 2>/dev/null & +QEMU_PID=$! +sleep $((TIMEOUT - 10)) + +PASS=0 +grep -q 'virtio-net:.*MAC' "$SERIAL_LOG" && echo "PASS: virtio-net started" && PASS=$((PASS+1)) || echo "FAIL: virtio-net not started" +grep -q 'DHCP' "$SERIAL_LOG" && echo "PASS: DHCP client started" && PASS=$((PASS+1)) || echo "FAIL: DHCP not started" +kill $QEMU_PID 2>/dev/null; wait $QEMU_PID 2>/dev/null +echo "=== Results: $PASS/2 checks passed ===" +rm -f /tmp/fw_vars_net.bin "$SERIAL_LOG" +[ $PASS -ge 2 ] && exit 0 || exit 1 diff --git a/local/scripts/test-storage-qemu.sh b/local/scripts/test-storage-qemu.sh new file mode 100755 index 00000000..bcb6d3b3 --- /dev/null +++ b/local/scripts/test-storage-qemu.sh @@ -0,0 +1,36 @@ +#!/bin/bash +# test-storage-qemu.sh — bounded storage driver validation in QEMU +# Phase 5.1 of CHANGELOG-DRIVER-IMPROVEMENT-PLAN.md +set -euo pipefail +IMAGE="${1:-build/x86_64/redbear-mini/harddrive.img}" +QEMU="${QEMU:-qemu-system-x86_64}" +OVMF_CODE="${OVMF_CODE:-/usr/share/edk2/x64/OVMF_CODE.4m.fd}" +OVMF_VARS="${OVMF_VARS:-build/x86_64/redbear-mini/fw_vars.bin}" +TIMEOUT="${TIMEOUT:-90}" +SERIAL_LOG="/tmp/rbos-storage-test-$$.log" + +[ -f "$IMAGE" ] || { echo "FAIL: image not found: $IMAGE"; exit 1; } +cp "$OVMF_VARS" /tmp/fw_vars_test.bin 2>/dev/null || cp /usr/share/edk2/x64/OVMF_VARS.4m.fd /tmp/fw_vars_test.bin + +echo "=== Storage Driver Validation ===" +echo "Image: $IMAGE" +echo "" + +timeout "$TIMEOUT" "$QEMU" -M q35 -m 2G -smp 2 \ + -drive file="$IMAGE",format=raw \ + -drive if=pflash,format=raw,readonly=on,file="$OVMF_CODE" \ + -drive if=pflash,format=raw,file=/tmp/fw_vars_test.bin \ + -serial file:"$SERIAL_LOG" -display none -no-reboot \ + -netdev user,id=net0 -device virtio-net-pci,netdev=net0 \ + -d guest_errors 2>/dev/null & +QEMU_PID=$! +sleep $((TIMEOUT - 10)) + +PASS=0 +grep -q 'AHCI.*Serial:' "$SERIAL_LOG" && echo "PASS: AHCI disk detected" && PASS=$((PASS+1)) || echo "FAIL: AHCI disk not detected" +grep -q 'switchroot' "$SERIAL_LOG" && echo "PASS: switchroot to /usr" && PASS=$((PASS+1)) || echo "FAIL: no switchroot" +kill $QEMU_PID 2>/dev/null; wait $QEMU_PID 2>/dev/null +echo "" +echo "=== Results: $PASS/2 checks passed ===" +rm -f /tmp/fw_vars_test.bin "$SERIAL_LOG" +[ $PASS -ge 2 ] && exit 0 || exit 1 diff --git a/recipes/core/kernel/kernel/recipe.toml b/recipes/core/kernel/kernel/recipe.toml new file mode 100644 index 00000000..a43deb48 --- /dev/null +++ b/recipes/core/kernel/kernel/recipe.toml @@ -0,0 +1,11 @@ +[source] +git = "https://gitlab.redox-os.org/redox-os/kernel.git" +patches = ["redox.patch", "P0-canary.patch", "P1-memory-map-overflow.patch", "../../../local/patches/kernel/P4-supplementary-groups.patch"] + +[build] +template = "custom" +script = """ +make -f ${COOKBOOK_SOURCE}/Makefile +mkdir -pv "${COOKBOOK_STAGE}/usr/lib/boot" +cp -v kernel "${COOKBOOK_STAGE}/usr/lib/boot" +""" diff --git a/src/cook/fetch.rs b/src/cook/fetch.rs index 3b337405..9b5ee65f 100644 --- a/src/cook/fetch.rs +++ b/src/cook/fetch.rs @@ -1166,6 +1166,7 @@ pub(crate) fn fetch_apply_patches( command.arg("--strip=1"); command.arg("--batch"); command.arg("--fuzz=0"); + command.arg("--no-backup-if-mismatch"); run_command_stdin(command, patch_data.as_slice(), logger) .map_err(|e| format!("patch {patch_name} FAILED: {e}"))?;