mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2026-11-03 12:50:31 -06:00
Merge remote-tracking branch 'origin/master' into uma-carveout
This commit is contained in:
commit
ae0934195a
24 files changed
+553
-259
No files matched your search
@@ -15,6 +15,6 @@ runs:
|
||||
with:
|
||||
cache-targets: false
|
||||
- name: Run sccache-cache
|
||||
uses: mozilla-actions/sccache-action@v0.0.9
|
||||
uses: mozilla-actions/sccache-action@v0.0.11
|
||||
with:
|
||||
version: "v0.10.0"
|
||||
@@ -20,10 +20,10 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
|
||||
- name: Docker Cache
|
||||
uses: AndreKurait/docker-cache@eb0683e3972ed3066587b7199d3a0e9bd114bd3c #0.6.0
|
||||
uses: AndreKurait/docker-cache@7a3887908bdb97935395833df69b060cfcca0f7f # 0.7.0
|
||||
with:
|
||||
key: docker-${{ matrix.target-os }}-${{ hashFiles('pkg/images/') }}
|
||||
|
||||
@@ -51,7 +51,7 @@ jobs:
|
||||
chmod +x /usr/local/bin/pkger
|
||||
|
||||
# A GitHub Action that implements smart caching for rust/cargo projects with sensible defaults.
|
||||
- uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2
|
||||
- uses: Swatinem/rust-cache@6323deb102c322ba6fcbdcafc7e3dddab59af2b6 # v2
|
||||
|
||||
- name: Build packages (with signing)
|
||||
if: ${{ (contains(matrix.target-os, 'fedora') || contains(matrix.target-os, 'opensuse') || matrix.target-os == 'rhel-9') && env.GPG_KEY_PASSWORD != '' }}
|
||||
@@ -91,7 +91,7 @@ jobs:
|
||||
gpg --armor --export > $PWD/release-artifacts/lact.pubkey
|
||||
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: ${{ matrix.target-os }}-${{ matrix.recipe }}
|
||||
path: release-artifacts/*
|
||||
@@ -102,14 +102,14 @@ jobs:
|
||||
image: bilelmoussaoui/flatpak-github-actions:gnome-47
|
||||
options: --privileged
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@92ae9851ad316786193b1fd3f40c4b51eb5cb101 # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: flatpak/flatpak-github-actions/flatpak-builder@79327416609af08178ad73b352877e51450790b3 # v6
|
||||
with:
|
||||
bundle: io.github.ilya_zlobintsev.LACT.flatpak
|
||||
manifest-path: flatpak/io.github.ilya_zlobintsev.LACT.yaml
|
||||
cache-key: flatpak-builder-${{ github.sha }}
|
||||
- name: Upload artifacts
|
||||
uses: actions/upload-artifact@bbbca2ddaa5d8feaa63e36b76fdaad77386f024f # v7
|
||||
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7
|
||||
with:
|
||||
name: flatpak
|
||||
path: io.github.ilya_zlobintsev.LACT.flatpak
|
||||
@@ -119,7 +119,7 @@ jobs:
|
||||
steps:
|
||||
- name: Log into GHCR
|
||||
if: github.event_name != 'pull_request'
|
||||
uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 # v4.0.0
|
||||
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.actor }}
|
||||
@@ -127,15 +127,15 @@ jobs:
|
||||
|
||||
- name: Extract Docker image metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf # v6
|
||||
uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6
|
||||
with:
|
||||
images: ghcr.io/${{ github.repository }}
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4
|
||||
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 # v7
|
||||
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7
|
||||
with:
|
||||
file: pkg/docker/Dockerfile
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
@@ -151,7 +151,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
@@ -184,7 +184,7 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
|
||||
- name: Download artifacts
|
||||
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8
|
||||
|
||||
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- name: Install yq
|
||||
run: |
|
||||
sudo curl -L https://github.com/mikefarah/yq/releases/download/v4.34.1/yq_linux_amd64 -o /usr/local/bin/yq
|
||||
@@ -40,7 +40,7 @@ jobs:
|
||||
git commit -m "chore: Update spec files for release ${{ github.event.release.tag_name }}"
|
||||
git push origin $branch_name
|
||||
- name: Create Pull Request
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9
|
||||
with:
|
||||
script: |
|
||||
const { repo, owner } = context.repo;
|
||||
|
||||
@@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v6
|
||||
uses: actions/checkout@v7
|
||||
|
||||
- name: Generate token
|
||||
uses: actions/create-github-app-token@v3
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
private-key: ${{ secrets.RENOVATE_PRIVATE_KEY }}
|
||||
|
||||
- name: Self-hosted Renovate
|
||||
uses: renovatebot/github-action@v46.1.5
|
||||
uses: renovatebot/github-action@v46.3.6
|
||||
with:
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
env:
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: ./.github/actions/setup-environment
|
||||
- name: Build
|
||||
run: cargo build
|
||||
@@ -28,7 +28,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- name: Install rustfmt
|
||||
run: rustup component add rustfmt
|
||||
- name: Check formatting
|
||||
@@ -38,7 +38,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: ./.github/actions/setup-environment
|
||||
- name: Install clippy
|
||||
run: rustup component add clippy
|
||||
@@ -49,7 +49,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: ./.github/actions/setup-environment
|
||||
- name: Install cargo-audit from crates.io
|
||||
uses: baptiste0928/cargo-install@v3
|
||||
@@ -63,7 +63,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'push' || !github.event.pull_request.draft }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: ./.github/actions/setup-environment
|
||||
- name: Install clang
|
||||
uses: awalsh128/cache-apt-pkgs-action@latest
|
||||
@@ -83,7 +83,7 @@ jobs:
|
||||
cargo llvm-cov --no-fail-fast --include-ffi --workspace --lcov --output-path lcov.info
|
||||
|
||||
- name: Upload to Codecov
|
||||
uses: codecov/codecov-action@v5.5.3
|
||||
uses: codecov/codecov-action@v7.1.1
|
||||
continue-on-error: true
|
||||
with:
|
||||
files: lcov.info
|
||||
@@ -95,7 +95,7 @@ jobs:
|
||||
runs-on: ubuntu-24.04
|
||||
if: ${{ github.event_name == 'pull_request' && !github.event.pull_request.draft }}
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: ./.github/actions/setup-environment
|
||||
- name: Install Nightly Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -122,7 +122,7 @@ jobs:
|
||||
LSAN_OPTIONS: fast_unwind_on_malloc=0:max_leak_stack_depth=50:print_suppressions=1
|
||||
ASAN_OPTIONS: detect_leaks=1:detect_stack_use_after_return=1:check_initialization_order=1:strict_init_order=1
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: ./.github/actions/setup-environment
|
||||
- name: Install Nightly Rust toolchain
|
||||
uses: actions-rs/toolchain@v1
|
||||
@@ -146,7 +146,7 @@ jobs:
|
||||
- check --feature-powerset --no-dev-deps --all
|
||||
- test --feature-powerset --all
|
||||
steps:
|
||||
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||
- uses: ./.github/actions/setup-environment
|
||||
- uses: taiki-e/install-action@cargo-hack
|
||||
- name: Run cargo hack ${{ matrix.command }}
|
||||
|
||||
@@ -9,7 +9,7 @@ use crate::{
|
||||
nvidia::nvapi::{
|
||||
CLOCK_CLIENT_CLK_VF_POINT_TYPE_PROG, ClockClientClkVfPointInfoV1,
|
||||
ClockClientClkVfPointsControlV1, ClockClientClkVfPointsInfoV1,
|
||||
ClockClientClkVfPointsStatusV3, NvGpuClockDomainId,
|
||||
ClockClientClkVfPointsStatusV3, NvApiThermChannelInfo, NvGpuClockDomainId,
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -66,7 +66,7 @@ pub struct NvidiaGpuController {
|
||||
|
||||
nvapi: Option<(Rc<NvApi>, NvPhysicalGpuHandle)>,
|
||||
driver_handle: Option<DriverHandle>,
|
||||
nvapi_therm_channel_mask: Option<i32>,
|
||||
nvapi_thermals_info: Option<NvApiThermChannelInfo>,
|
||||
|
||||
last_util_timestamp: Cell<Option<u64>>,
|
||||
// Store last applied offsets as a workaround when the driver doesn't tell us the current offset
|
||||
@@ -93,7 +93,7 @@ impl NvidiaGpuController {
|
||||
)
|
||||
})?;
|
||||
|
||||
let (nvapi_handle, nvapi_therm_channel_mask) = match nvapi.as_ref() {
|
||||
let (nvapi_handle, nvapi_thermals_info) = match nvapi.as_ref() {
|
||||
Some(nvapi) => {
|
||||
let bus_id = common.get_slot_info()?.bus;
|
||||
let gpu_handle = nvapi
|
||||
@@ -102,19 +102,24 @@ impl NvidiaGpuController {
|
||||
.ok()
|
||||
.flatten();
|
||||
|
||||
let therm_channel_mask = gpu_handle.and_then(|handle| unsafe {
|
||||
let thermals_info = gpu_handle.and_then(|handle| unsafe {
|
||||
nvapi
|
||||
.calculate_therm_channel_mask(handle)
|
||||
.inspect(|mask| {
|
||||
debug!("calculated NvAPI therm channel mask {mask:x}");
|
||||
.therm_channel_get_info(handle)
|
||||
.inspect(|info| {
|
||||
debug!(
|
||||
"NvAPI therm channel mask {:x}, hotspot channel {:?}, vram channel {:?}",
|
||||
info.mask(),
|
||||
info.hotspot_channel(),
|
||||
info.vram_channel()
|
||||
);
|
||||
})
|
||||
.inspect_err(|err| {
|
||||
error!("could not calculate NvAPI therm channel mask: {err:#}");
|
||||
error!("could not get NvAPI therm channel info: {err:#}");
|
||||
})
|
||||
.ok()
|
||||
});
|
||||
|
||||
(gpu_handle, therm_channel_mask)
|
||||
(gpu_handle, thermals_info)
|
||||
}
|
||||
None => (None, None),
|
||||
};
|
||||
@@ -142,7 +147,7 @@ impl NvidiaGpuController {
|
||||
nvapi: nvapi.zip(nvapi_handle),
|
||||
common,
|
||||
driver_handle,
|
||||
nvapi_therm_channel_mask,
|
||||
nvapi_thermals_info,
|
||||
initial_target_temp: target_temp,
|
||||
last_util_timestamp: Cell::new(None),
|
||||
fan_control_handle: RefCell::new(None),
|
||||
@@ -912,10 +917,16 @@ impl GpuController for NvidiaGpuController {
|
||||
let arch = device.architecture().ok();
|
||||
|
||||
unsafe {
|
||||
if let Some(mask) = self.nvapi_therm_channel_mask
|
||||
&& let Ok(thermals) = nvapi.therm_channel_get_status(*handle, mask)
|
||||
if let Some(thermals_info) = &self.nvapi_thermals_info
|
||||
&& let Ok(thermals) =
|
||||
nvapi.therm_channel_get_status(*handle, thermals_info.mask())
|
||||
{
|
||||
if let Some(hotspot) = nvapi.read_hotspot(&thermals, *handle, arch.as_ref()) {
|
||||
if let Some(hotspot) = nvapi.read_hotspot(
|
||||
&thermals,
|
||||
thermals_info.hotspot_channel(),
|
||||
*handle,
|
||||
arch.as_ref(),
|
||||
) {
|
||||
temps.insert(
|
||||
"GPU Hotspot".to_owned(),
|
||||
TemperatureEntry {
|
||||
@@ -935,7 +946,10 @@ impl GpuController for NvidiaGpuController {
|
||||
.as_ref()
|
||||
.and_then(|driver| driver.get_ram_type().ok());
|
||||
|
||||
if let Some(vram) = thermals.vram(vram_type) {
|
||||
if let Some(vram) = thermals_info
|
||||
.vram_channel()
|
||||
.and_then(|channel| thermals.get_value(channel))
|
||||
{
|
||||
temps.insert(
|
||||
"VRAM".to_owned(),
|
||||
TemperatureEntry {
|
||||
|
||||
@@ -27,6 +27,7 @@ const QUERY_NVAPI_ENUM_PHYSICAL_GPUS: u32 = 0xe5ac921f;
|
||||
const QUERY_NVAPI_GPU_GET_BUS_ID: u32 = 0x1be0b8e5;
|
||||
const QUERY_NVAPI_GET_ERROR_MESSAGE: u32 = 0x6c2d048c;
|
||||
// Undocumented calls
|
||||
const QUERY_NVAPI_GPU_THERM_CHANNEL_GET_INFO: u32 = 0x0bc8163d;
|
||||
const QUERY_NVAPI_GPU_THERM_CHANNEL_GET_STATUS: u32 = 0x65fe3aad;
|
||||
const QUERY_NVAPI_GPU_CLIENT_VOLT_RAILS_GET_STATUS: u32 = 0x465f9bcf;
|
||||
const QUERY_NVAPI_GPU_CLIENT_VOLT_RAILS_GET_CONTROL: u32 = 0x9df23ca1;
|
||||
@@ -89,26 +90,34 @@ impl NvApi {
|
||||
Ok(None)
|
||||
}
|
||||
|
||||
pub unsafe fn therm_channel_get_info(
|
||||
&self,
|
||||
handle: NvPhysicalGpuHandle,
|
||||
) -> anyhow::Result<NvApiThermChannelInfo> {
|
||||
let mut thermals_info = NvApiThermChannelInfo::default();
|
||||
self.physical_gpu_query(
|
||||
handle,
|
||||
&mut thermals_info,
|
||||
QUERY_NVAPI_GPU_THERM_CHANNEL_GET_INFO,
|
||||
)?;
|
||||
Ok(thermals_info)
|
||||
}
|
||||
|
||||
pub unsafe fn therm_channel_get_status(
|
||||
&self,
|
||||
handle: NvPhysicalGpuHandle,
|
||||
mask: i32,
|
||||
) -> anyhow::Result<NvApiThermals> {
|
||||
let f = self.query_interface(QUERY_NVAPI_GPU_THERM_CHANNEL_GET_STATUS)?;
|
||||
let f: unsafe extern "C" fn(
|
||||
handle: NvPhysicalGpuHandle,
|
||||
sensors: &mut NvApiThermals,
|
||||
) -> NvAPI_Status = transmute(f);
|
||||
|
||||
let mut sensors = NvApiThermals {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
version: make_version::<NvApiThermals>(2),
|
||||
mask,
|
||||
values: [0; 40],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let status = f(handle, &mut sensors);
|
||||
self.handle_status(status)?;
|
||||
self.physical_gpu_query(
|
||||
handle,
|
||||
&mut sensors,
|
||||
QUERY_NVAPI_GPU_THERM_CHANNEL_GET_STATUS,
|
||||
)?;
|
||||
|
||||
Ok(sensors)
|
||||
}
|
||||
@@ -236,37 +245,6 @@ impl NvApi {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub unsafe fn calculate_therm_channel_mask(
|
||||
&self,
|
||||
handle: NvPhysicalGpuHandle,
|
||||
) -> anyhow::Result<i32> {
|
||||
let f = self.query_interface(QUERY_NVAPI_GPU_THERM_CHANNEL_GET_STATUS)?;
|
||||
let f: unsafe extern "C" fn(
|
||||
handle: NvPhysicalGpuHandle,
|
||||
sensors: &mut NvApiThermals,
|
||||
) -> NvAPI_Status = transmute(f);
|
||||
|
||||
let mut sensors = NvApiThermals {
|
||||
#[allow(clippy::cast_possible_truncation)]
|
||||
version: (mem::size_of::<NvApiThermals>() | (2 << 16)) as u32,
|
||||
mask: 1,
|
||||
values: [0; 40],
|
||||
};
|
||||
|
||||
let initial_status = f(handle, &mut sensors);
|
||||
self.handle_status(initial_status)?;
|
||||
|
||||
for bit in 0..32 {
|
||||
sensors.mask = 1 << bit;
|
||||
let status = f(handle, &mut sensors);
|
||||
if status != 0 {
|
||||
return Ok(sensors.mask - 1);
|
||||
}
|
||||
}
|
||||
|
||||
bail!("Could not find suitable mask");
|
||||
}
|
||||
|
||||
unsafe fn enum_physical_gpus(&self) -> anyhow::Result<Vec<NvPhysicalGpuHandle>> {
|
||||
let f = self.query_interface(QUERY_NVAPI_ENUM_PHYSICAL_GPUS)?;
|
||||
let f: unsafe extern "C" fn(
|
||||
@@ -480,6 +458,7 @@ impl NvApi {
|
||||
pub fn read_hotspot(
|
||||
&self,
|
||||
thermals: &NvApiThermals,
|
||||
channel: Option<usize>,
|
||||
handle: NvPhysicalGpuHandle,
|
||||
arch: Option<&DeviceArchitecture>,
|
||||
) -> Option<i32> {
|
||||
@@ -490,7 +469,7 @@ impl NvApi {
|
||||
.and_then(|value| value.try_into().ok())
|
||||
}
|
||||
} else {
|
||||
thermals.get_value(9)
|
||||
channel.and_then(|channel| thermals.get_value(channel))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -513,21 +492,90 @@ impl Drop for NvApi {
|
||||
pub struct NvApiThermals {
|
||||
version: u32,
|
||||
mask: i32,
|
||||
values: [i32; 40],
|
||||
rsvd: [NvU8; 32],
|
||||
temps: [i32; 32],
|
||||
}
|
||||
|
||||
impl Default for NvApiThermals {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: make_version::<Self>(2),
|
||||
mask: 0,
|
||||
rsvd: [0; 32],
|
||||
temps: [0; 32],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl NvApiThermals {
|
||||
fn get_value(&self, index: usize) -> Option<i32> {
|
||||
self.values
|
||||
.get(index)
|
||||
pub fn get_value(&self, channel: usize) -> Option<i32> {
|
||||
self.temps
|
||||
.get(channel)
|
||||
.map(|&value| value / 256)
|
||||
.filter(|&value| value > 0 && value < 255)
|
||||
}
|
||||
}
|
||||
|
||||
pub fn vram(&self, vram_type: Option<&str>) -> Option<i32> {
|
||||
match vram_type {
|
||||
Some("GDDR7") => self.get_value(10),
|
||||
_ => self.get_value(15),
|
||||
const THERM_CHANNEL_TYPE_HOTSPOT: usize = 1;
|
||||
const THERM_CHANNEL_TYPE_MEMORY: usize = 3;
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug, Copy, Clone, Default)]
|
||||
struct NvApiThermChannel {
|
||||
class: NvU32,
|
||||
channel_type: NvU32,
|
||||
rel_loc: NvU32,
|
||||
target_gpu: NvU32,
|
||||
scaling: NvS32,
|
||||
offset_sw: NvS32,
|
||||
min_temp: NvS32,
|
||||
max_temp: NvS32,
|
||||
is_temp_sim_supported: NvU8,
|
||||
flags: NvU8,
|
||||
offset_hw: NvS32,
|
||||
rsvd: [NvU8; 28],
|
||||
data: [NvU8; 16],
|
||||
}
|
||||
|
||||
#[repr(C)]
|
||||
#[derive(Debug)]
|
||||
pub struct NvApiThermChannelInfo {
|
||||
version: NvU32,
|
||||
mask: i32,
|
||||
rsvd: [NvU8; 32],
|
||||
channels: [NvApiThermChannel; 32],
|
||||
primary_channel_idx: [NvU8; 5],
|
||||
}
|
||||
|
||||
impl NvApiThermChannelInfo {
|
||||
pub fn mask(&self) -> i32 {
|
||||
self.mask
|
||||
}
|
||||
|
||||
pub fn hotspot_channel(&self) -> Option<usize> {
|
||||
self.primary_channel(THERM_CHANNEL_TYPE_HOTSPOT)
|
||||
}
|
||||
|
||||
pub fn vram_channel(&self) -> Option<usize> {
|
||||
self.primary_channel(THERM_CHANNEL_TYPE_MEMORY)
|
||||
}
|
||||
|
||||
fn primary_channel(&self, channel_type: usize) -> Option<usize> {
|
||||
self.primary_channel_idx
|
||||
.get(channel_type)
|
||||
.map(|&channel| usize::from(channel))
|
||||
.filter(|&channel| channel < 32 && self.mask & (1 << channel) != 0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for NvApiThermChannelInfo {
|
||||
fn default() -> Self {
|
||||
Self {
|
||||
version: make_version::<Self>(2),
|
||||
mask: 0,
|
||||
rsvd: [0; 32],
|
||||
channels: [NvApiThermChannel::default(); 32],
|
||||
primary_channel_idx: [u8::MAX; 5],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,10 @@ language = Language
|
||||
language-system-default = System Default
|
||||
language-restart-notice = Restart the LACT GUI to apply language changes.
|
||||
|
||||
experimental-features = Experimental Features
|
||||
nvidia-pstate-offsets = NVIDIA per-P-state offsets
|
||||
nvidia-pstate-offsets-description = Expose offsets for non-P0 states. Disabled by default because no known device/driver version supports it.
|
||||
|
||||
info-page = Hardware Info
|
||||
oc-page = Overclocking
|
||||
thermals-page = Thermals
|
||||
@@ -217,6 +221,7 @@ pstate-clock-offset = P-State {$pstate} Clock Offset
|
||||
pstate-clock = P-State {$pstate} Clock
|
||||
pstate-clock-voltage = P-State {$pstate} Voltage
|
||||
|
||||
pstate = Power State
|
||||
pstates = Power States
|
||||
gpu-pstates = GPU Power States
|
||||
vram-pstates = VRAM Power States
|
||||
|
||||
+18
-4
@@ -670,6 +670,9 @@ impl AppModel {
|
||||
AppMsg::ShowPreferencesDialog => {
|
||||
self.preferences_dialog.emit(PreferencesDialogMsg::Show);
|
||||
}
|
||||
AppMsg::ExperimentalFeaturesChanged => {
|
||||
sender.input(AppMsg::ReloadData { full: false });
|
||||
}
|
||||
AppMsg::ShowAboutDialog => {
|
||||
self.about_dialog.emit(AboutDialogMsg::Show);
|
||||
}
|
||||
@@ -1268,6 +1271,12 @@ impl AppModel {
|
||||
.model()
|
||||
.apply_clocks_config(&mut gpu_config.clocks_configuration);
|
||||
|
||||
if self.device_driver == "nvidia" && !CONFIG.read().experimental_nvidia_pstate_offsets {
|
||||
let clocks = &mut gpu_config.clocks_configuration;
|
||||
clocks.gpu_clock_offsets.retain(|pstate, _| *pstate == 0);
|
||||
clocks.mem_clock_offsets.retain(|pstate, _| *pstate == 0);
|
||||
}
|
||||
|
||||
let enabled_power_states = self.oc_page.model().get_enabled_power_states();
|
||||
gpu_config.power_states = enabled_power_states;
|
||||
|
||||
@@ -1284,19 +1293,24 @@ impl AppModel {
|
||||
.set_gpu_config(&gpu_id, gpu_config)
|
||||
.await
|
||||
.context("Could not apply settings")?;
|
||||
self.ask_settings_confirmation(delay, root, sender);
|
||||
self.ask_settings_confirmation(
|
||||
delay,
|
||||
&self
|
||||
.application
|
||||
.active_window()
|
||||
.unwrap_or_else(|| root.clone().upcast()),
|
||||
sender,
|
||||
);
|
||||
|
||||
sender.input(AppMsg::ReloadData { full: false });
|
||||
|
||||
root.present();
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn ask_settings_confirmation(
|
||||
&self,
|
||||
mut delay: u64,
|
||||
window: &adw::ApplicationWindow,
|
||||
window: >k::Window,
|
||||
sender: &AsyncComponentSender<AppModel>,
|
||||
) {
|
||||
let dialog = adw::AlertDialog::builder()
|
||||
|
||||
@@ -1,15 +1,29 @@
|
||||
.adjustment-list > row {
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.adjustment-list > row.adjustment-row {
|
||||
padding: 5px 10px;
|
||||
}
|
||||
|
||||
.adjustment-list > row:first-child {
|
||||
.adjustment-list > row.adjustment-row:first-child {
|
||||
padding-top: 10px;
|
||||
}
|
||||
|
||||
.adjustment-list > row:last-child {
|
||||
.adjustment-list > row.adjustment-row:last-child {
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
/* The card owns row spacing, including Adwaita action rows. */
|
||||
.adjustment-list > row > box.header {
|
||||
margin: 0;
|
||||
min-height: 0;
|
||||
}
|
||||
|
||||
.adjustment-list > row > box.header > box.title {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.adjustment-row scale {
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
@@ -75,11 +75,10 @@ impl<Key: 'static> FactoryComponent for AdjustmentRow<Key> {
|
||||
gtk::ListBoxRow {
|
||||
set_activatable: false,
|
||||
set_selectable: false,
|
||||
add_css_class: "adjustment-row",
|
||||
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Vertical,
|
||||
set_spacing: 5,
|
||||
add_css_class: "adjustment-row",
|
||||
|
||||
gtk::Box {
|
||||
set_spacing: 12,
|
||||
|
||||
@@ -27,6 +27,7 @@ pub enum AppMsg {
|
||||
DumpVBios,
|
||||
DebugSnapshot,
|
||||
ShowPreferencesDialog,
|
||||
ExperimentalFeaturesChanged,
|
||||
ShowAboutDialog,
|
||||
ShowOverdriveDialog,
|
||||
ShowServiceSetupDialog,
|
||||
|
||||
@@ -2,3 +2,14 @@
|
||||
min-width: 400px;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.power-states-dialog-card list:not(.boxed-list) {
|
||||
/* Embedded lists use the surrounding card's background, not the view background. */
|
||||
background: none;
|
||||
border-radius: 12px;
|
||||
}
|
||||
|
||||
.power-states-dialog-card .page-section-children-box,
|
||||
.power-states-dialog-card .adjustment-card-advanced-features {
|
||||
margin: 0;
|
||||
}
|
||||
@@ -26,7 +26,7 @@ use lact_schema::{ClocksTable, DeviceInfo, PowerStates};
|
||||
use nvml_wrapper::enums::device::PowerMizerMode;
|
||||
use performance_frame::PerformanceFrameMsg;
|
||||
use power_frame::{PowerFrame, PowerFrameMsg};
|
||||
use power_states::power_states_frame::{PowerStatesFrame, PowerStatesFrameMsg};
|
||||
use power_states::power_states_dialog::{PowerStatesDialog, PowerStatesDialogMsg};
|
||||
use relm4::binding::BoolBinding;
|
||||
use relm4::{ComponentController, ComponentParts, ComponentSender, RelmWidgetExt};
|
||||
use std::collections::HashSet;
|
||||
@@ -39,7 +39,7 @@ pub struct OcPage {
|
||||
device_info: Option<Arc<DeviceInfo>>,
|
||||
|
||||
power_frame: relm4::Controller<PowerFrame>,
|
||||
power_states_frame: relm4::Controller<PowerStatesFrame>,
|
||||
power_states_dialog: relm4::Controller<PowerStatesDialog>,
|
||||
gpu_clocks_frame: relm4::Controller<ClocksFrame>,
|
||||
vram_clocks_frame: relm4::Controller<ClocksFrame>,
|
||||
igpu_frame: relm4::Controller<IGpuFrame>,
|
||||
@@ -65,6 +65,7 @@ pub enum OcPageMsg {
|
||||
PerformanceLevelChanged,
|
||||
SetPerformanceLevel(PerformanceLevel),
|
||||
EnablePstateConfig,
|
||||
ShowPowerStates(ClockDomain),
|
||||
ShowVfCurveEditor,
|
||||
VfCurveEditingToggled(bool),
|
||||
}
|
||||
@@ -90,7 +91,6 @@ impl relm4::Component for OcPage {
|
||||
ColumnBias::Left, // Core
|
||||
ColumnBias::Right, // VRAM
|
||||
ColumnBias::Left, // Power
|
||||
ColumnBias::Right, // Power States
|
||||
ColumnBias::Right, // Integrated Graphics
|
||||
|
||||
])),
|
||||
@@ -108,10 +108,6 @@ impl relm4::Component for OcPage {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
|
||||
model.power_states_frame.widget() {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
|
||||
model.igpu_frame.widget() {
|
||||
add_css_class: "oc-page-section",
|
||||
},
|
||||
@@ -153,8 +149,8 @@ impl relm4::Component for OcPage {
|
||||
show_all_pstates: BoolBinding::new(false),
|
||||
})
|
||||
.forward(sender.input_sender(), |msg| msg);
|
||||
let power_states_frame =
|
||||
PowerStatesFrame::launch_default().forward(sender.input_sender(), |msg| msg);
|
||||
let power_states_dialog =
|
||||
PowerStatesDialog::launch_default().forward(sender.input_sender(), |msg| msg);
|
||||
let power_frame = PowerFrame::launch_default().forward(sender.input_sender(), |msg| msg);
|
||||
let igpu_frame = IGpuFrame::launch_default().forward(sender.input_sender(), |msg| msg);
|
||||
|
||||
@@ -167,7 +163,7 @@ impl relm4::Component for OcPage {
|
||||
stats_section,
|
||||
device_info: None,
|
||||
power_frame,
|
||||
power_states_frame,
|
||||
power_states_dialog,
|
||||
gpu_clocks_frame,
|
||||
vram_clocks_frame,
|
||||
igpu_frame,
|
||||
@@ -184,13 +180,17 @@ impl relm4::Component for OcPage {
|
||||
widgets: &mut Self::Widgets,
|
||||
msg: Self::Input,
|
||||
sender: ComponentSender<Self>,
|
||||
_root: &Self::Root,
|
||||
root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
OcPageMsg::Update { update, initial } => match &update {
|
||||
PageUpdate::Stats(stats) => {
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::Stats(stats.clone()));
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::Stats(stats.clone()));
|
||||
self.gpu_clocks_frame
|
||||
.emit(ClocksFrameMsg::Stats(stats.clone()));
|
||||
self.vram_clocks_frame
|
||||
.emit(ClocksFrameMsg::Stats(stats.clone()));
|
||||
|
||||
self.stats_section
|
||||
.emit(GpuStatsSectionMsg::Stats(stats.clone()));
|
||||
@@ -218,8 +218,8 @@ impl relm4::Component for OcPage {
|
||||
self.device_info = Some(info.clone());
|
||||
self.stats_section
|
||||
.emit(GpuStatsSectionMsg::Info(info.clone()));
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::VramClockRatio(vram_clock_ratio));
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::VramClockRatio(vram_clock_ratio));
|
||||
self.vram_clocks_frame
|
||||
.emit(ClocksFrameMsg::VramRatio(vram_clock_ratio));
|
||||
|
||||
@@ -257,11 +257,19 @@ impl relm4::Component for OcPage {
|
||||
pstates,
|
||||
configured,
|
||||
} => {
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::PowerStates {
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::PowerStates {
|
||||
pstates: pstates.clone(),
|
||||
configured,
|
||||
});
|
||||
self.gpu_clocks_frame
|
||||
.emit(ClocksFrameMsg::PowerStatesAvailable(
|
||||
!pstates.core.is_empty(),
|
||||
));
|
||||
self.vram_clocks_frame
|
||||
.emit(ClocksFrameMsg::PowerStatesAvailable(
|
||||
!pstates.vram.is_empty(),
|
||||
));
|
||||
self.stats_section
|
||||
.emit(GpuStatsSectionMsg::PowerStates(Arc::new(pstates)));
|
||||
sender.input(OcPageMsg::PerformanceLevelChanged);
|
||||
@@ -269,13 +277,13 @@ impl relm4::Component for OcPage {
|
||||
OcPageMsg::PerformanceLevelChanged => {
|
||||
let custom_pstates_configurable =
|
||||
self.get_performance_level() == Some(PerformanceLevel::Manual);
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::Configurable(
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::Configurable(
|
||||
custom_pstates_configurable,
|
||||
));
|
||||
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::PerformanceLevel(
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::PerformanceLevel(
|
||||
self.get_performance_level(),
|
||||
));
|
||||
}
|
||||
@@ -286,8 +294,14 @@ impl relm4::Component for OcPage {
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
}
|
||||
OcPageMsg::EnablePstateConfig => {
|
||||
self.power_states_frame
|
||||
.emit(PowerStatesFrameMsg::EnableWithManualPerformanceLevel);
|
||||
self.power_states_dialog
|
||||
.emit(PowerStatesDialogMsg::EnableWithManualPerformanceLevel);
|
||||
}
|
||||
OcPageMsg::ShowPowerStates(domain) => {
|
||||
self.power_states_dialog.emit(PowerStatesDialogMsg::Show {
|
||||
domain,
|
||||
parent: root.clone().upcast(),
|
||||
});
|
||||
}
|
||||
OcPageMsg::ShowVfCurveEditor => {
|
||||
self.vf_curve_editor.emit(VfCurveEditorMsg::Show);
|
||||
@@ -346,7 +360,7 @@ impl OcPage {
|
||||
|
||||
pub fn get_enabled_power_states(&self) -> IndexMap<PowerLevelKind, Vec<u8>> {
|
||||
if self.get_performance_level() == Some(PerformanceLevel::Manual) {
|
||||
self.power_states_frame.model().get_enabled_power_states()
|
||||
self.power_states_dialog.model().get_enabled_power_states()
|
||||
} else {
|
||||
IndexMap::new()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.adjustment-list > row.pstates {
|
||||
padding-top: 15px;
|
||||
padding-bottom: 15px;
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
use crate::{
|
||||
APP_BROKER, I18N,
|
||||
APP_BROKER, CONFIG, I18N,
|
||||
app::{
|
||||
components::{
|
||||
adjustment_card::AdjustmentCard,
|
||||
@@ -11,10 +11,10 @@ use crate::{
|
||||
},
|
||||
};
|
||||
use adw::prelude::*;
|
||||
use amdgpu_sysfs::gpu_handle::overdrive::ClocksTableGen as AmdClocksTable;
|
||||
use amdgpu_sysfs::gpu_handle::{PowerLevelId, overdrive::ClocksTableGen as AmdClocksTable};
|
||||
use i18n_embed_fl::fl;
|
||||
use lact_schema::{
|
||||
ClocksTable, IntelClocksTable, NvidiaClockOffset, NvidiaClocksTable,
|
||||
ClocksTable, DeviceStats, IntelClocksTable, NvidiaClockOffset, NvidiaClocksTable,
|
||||
request::{ClockspeedType, SetClocksCommand},
|
||||
};
|
||||
use relm4::{
|
||||
@@ -35,6 +35,9 @@ pub struct ClocksFrame {
|
||||
show_all_pstates: BoolBinding,
|
||||
vf_curve_editing: BoolBinding,
|
||||
enable_locked_clocks: BoolBinding,
|
||||
has_power_states: bool,
|
||||
active_power_state: Option<PowerLevelId>,
|
||||
current_clock: Option<u64>,
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
@@ -99,6 +102,8 @@ pub enum ClocksFrameMsg {
|
||||
vf_curve_is_configured: bool,
|
||||
},
|
||||
VramRatio(f64),
|
||||
PowerStatesAvailable(bool),
|
||||
Stats(Arc<DeviceStats>),
|
||||
TogglePStatesVisibility,
|
||||
ResetGpuClockOffsets,
|
||||
}
|
||||
@@ -119,7 +124,9 @@ impl relm4::Component for ClocksFrame {
|
||||
ClockDomain::Vram => fl!(I18N, "vram-section"),
|
||||
},
|
||||
#[watch]
|
||||
set_visible: model.domain == ClockDomain::Gpu || model.has_any_clocks(),
|
||||
set_visible: model.domain == ClockDomain::Gpu
|
||||
|| model.has_any_clocks()
|
||||
|| model.has_power_states,
|
||||
|
||||
append_header = >k::Box {
|
||||
set_spacing: 10,
|
||||
@@ -167,7 +174,8 @@ impl relm4::Component for ClocksFrame {
|
||||
controls {
|
||||
append = >k::ToggleButton {
|
||||
#[watch]
|
||||
set_visible: model.has_secondary_p_states(),
|
||||
set_visible: model.has_secondary_p_states()
|
||||
&& model.secondary_p_states_enabled(),
|
||||
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
add_binding["active"]: &model.show_all_pstates,
|
||||
@@ -244,6 +252,43 @@ impl relm4::Component for ClocksFrame {
|
||||
set_halign: gtk::Align::Start,
|
||||
},
|
||||
},
|
||||
|
||||
adw::ActionRow {
|
||||
set_title: &fl!(I18N, "pstate"),
|
||||
set_activatable: true,
|
||||
add_css_class: "pstates",
|
||||
#[watch]
|
||||
set_visible: model.has_power_states,
|
||||
connect_activated[sender, domain = model.domain] => move |_| {
|
||||
sender.output(OcPageMsg::ShowPowerStates(domain)).unwrap();
|
||||
},
|
||||
|
||||
add_suffix = >k::Box {
|
||||
set_spacing: 4,
|
||||
set_valign: gtk::Align::Center,
|
||||
|
||||
gtk::Label {
|
||||
add_css_class: css::HEADING,
|
||||
#[watch]
|
||||
set_label: &match model.active_power_state {
|
||||
Some(PowerLevelId::Index(index)) => format!("P{index}"),
|
||||
Some(PowerLevelId::Sleep) => "S".to_owned(),
|
||||
None => "N/A".to_owned(),
|
||||
},
|
||||
},
|
||||
|
||||
gtk::Label {
|
||||
add_css_class: css::DIM_LABEL,
|
||||
#[watch]
|
||||
set_label: &model.power_state_clock_label(),
|
||||
},
|
||||
},
|
||||
|
||||
add_suffix = >k::Image {
|
||||
set_icon_name: Some("go-next-symbolic"),
|
||||
add_css_class: css::DIM_LABEL,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -271,6 +316,9 @@ impl relm4::Component for ClocksFrame {
|
||||
show_all_pstates,
|
||||
vf_curve_editing,
|
||||
enable_locked_clocks: BoolBinding::new(false),
|
||||
has_power_states: false,
|
||||
active_power_state: None,
|
||||
current_clock: None,
|
||||
};
|
||||
|
||||
for binding in [
|
||||
@@ -286,6 +334,13 @@ impl relm4::Component for ClocksFrame {
|
||||
|
||||
let widgets = view_output!();
|
||||
|
||||
model.adjustments.widget().set_sort_func(|left, right| {
|
||||
// Keep the pstates row after dynamically inserted clock controls.
|
||||
left.has_css_class("pstates")
|
||||
.cmp(&right.has_css_class("pstates"))
|
||||
.into()
|
||||
});
|
||||
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
@@ -362,7 +417,20 @@ impl relm4::Component for ClocksFrame {
|
||||
self.vram_clock_ratio = vram_ratio;
|
||||
self.update_vram_clock_ratio();
|
||||
}
|
||||
ClocksFrameMsg::PowerStatesAvailable(available) => {
|
||||
self.has_power_states = available;
|
||||
}
|
||||
ClocksFrameMsg::Stats(stats) => {
|
||||
let active_states = stats.active_power_states.unwrap_or_default();
|
||||
(self.active_power_state, self.current_clock) = match self.domain {
|
||||
ClockDomain::Gpu => (active_states.core, stats.clockspeed.gpu_clockspeed),
|
||||
ClockDomain::Vram => (active_states.memory, stats.clockspeed.vram_clockspeed),
|
||||
};
|
||||
}
|
||||
ClocksFrameMsg::TogglePStatesVisibility => {
|
||||
if !self.secondary_p_states_enabled() && self.show_all_pstates.value() {
|
||||
self.show_all_pstates.set_value(false);
|
||||
}
|
||||
for clock_type in self.adjustments.keys() {
|
||||
let visible = match clock_type {
|
||||
ClockspeedType::MaxCoreClock
|
||||
@@ -380,7 +448,8 @@ impl relm4::Component for ClocksFrame {
|
||||
}
|
||||
_ => {
|
||||
!self.secondary_p_state_clocks.contains(clock_type)
|
||||
|| self.show_all_pstates.value()
|
||||
|| (self.secondary_p_states_enabled()
|
||||
&& self.show_all_pstates.value())
|
||||
}
|
||||
};
|
||||
self.adjustments
|
||||
@@ -394,6 +463,23 @@ impl relm4::Component for ClocksFrame {
|
||||
}
|
||||
|
||||
impl ClocksFrame {
|
||||
fn power_state_clock_label(&self) -> String {
|
||||
match self.current_clock {
|
||||
Some(clock) => {
|
||||
let ratio = match self.domain {
|
||||
ClockDomain::Gpu => 1.0,
|
||||
ClockDomain::Vram => self.vram_clock_ratio,
|
||||
};
|
||||
format!("{:.0} {}", clock as f64 * ratio, fl!(I18N, "mhz"))
|
||||
}
|
||||
None => "N/A".to_owned(),
|
||||
}
|
||||
}
|
||||
|
||||
fn secondary_p_states_enabled(&self) -> bool {
|
||||
!self.show_nvidia_options || CONFIG.read().experimental_nvidia_pstate_offsets
|
||||
}
|
||||
|
||||
fn set_clock(&mut self, clock_type: ClockspeedType, data: ClocksData) {
|
||||
if !self.domain.matches(clock_type) {
|
||||
return;
|
||||
@@ -712,6 +798,11 @@ impl ClocksFrame {
|
||||
self.adjustments
|
||||
.iter()
|
||||
.filter_map(|(clock_type, row)| {
|
||||
if !self.secondary_p_states_enabled()
|
||||
&& self.secondary_p_state_clocks.contains(clock_type)
|
||||
{
|
||||
return None;
|
||||
}
|
||||
let configured_value = row.get_changed_value().map(|value| value as i32);
|
||||
// If nvidia options are enabled, we always set locked clocks to None or Some
|
||||
let value = if self.show_nvidia_options {
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
pub mod power_states_frame;
|
||||
pub mod power_states_dialog;
|
||||
mod power_states_list;
|
||||
mod power_states_row;
|
||||
+110
-78
@@ -5,7 +5,7 @@ use crate::{
|
||||
app::{
|
||||
components::{adjustment_card::AdjustmentCard, page_section::PageSection},
|
||||
msg::AppMsg,
|
||||
pages::oc_page::OcPageMsg,
|
||||
pages::oc_page::{OcPageMsg, clocks_frame::ClockDomain},
|
||||
utils::ext::RelmLaunchable as _,
|
||||
},
|
||||
};
|
||||
@@ -16,12 +16,13 @@ use i18n_embed_fl::fl;
|
||||
use indexmap::IndexMap;
|
||||
use lact_schema::{DeviceStats, PowerStates};
|
||||
use relm4::{
|
||||
ComponentController, ComponentParts, ComponentSender,
|
||||
ComponentController, ComponentParts, ComponentSender, RelmWidgetExt,
|
||||
binding::{Binding, BoolBinding},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
|
||||
pub struct PowerStatesFrame {
|
||||
pub struct PowerStatesDialog {
|
||||
domain: ClockDomain,
|
||||
core_states_list: relm4::Controller<PowerStatesList>,
|
||||
vram_states_list: relm4::Controller<PowerStatesList>,
|
||||
states_configurable: BoolBinding,
|
||||
@@ -34,7 +35,11 @@ pub struct PowerStatesFrame {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum PowerStatesFrameMsg {
|
||||
pub enum PowerStatesDialogMsg {
|
||||
Show {
|
||||
domain: ClockDomain,
|
||||
parent: gtk::Widget,
|
||||
},
|
||||
PowerStates {
|
||||
pstates: PowerStates,
|
||||
configured: bool,
|
||||
@@ -51,69 +56,83 @@ pub enum PowerStatesFrameMsg {
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
impl relm4::Component for PowerStatesDialog {
|
||||
type Init = ();
|
||||
type Input = PowerStatesFrameMsg;
|
||||
type Input = PowerStatesDialogMsg;
|
||||
type Output = OcPageMsg;
|
||||
type CommandOutput = ();
|
||||
|
||||
view! {
|
||||
PageSection {
|
||||
set_name: fl!(I18N, "pstates"),
|
||||
set_hide_visible_container: true,
|
||||
#[template]
|
||||
append_child = &AdjustmentCard {
|
||||
#[template_child]
|
||||
advanced_features {
|
||||
#[watch]
|
||||
set_visible: model.performance_level.is_some(),
|
||||
},
|
||||
adw::Dialog {
|
||||
set_content_width: 420,
|
||||
set_follows_content_size: true,
|
||||
#[watch]
|
||||
set_title: &match model.domain {
|
||||
ClockDomain::Gpu => fl!(I18N, "gpu-pstates"),
|
||||
ClockDomain::Vram => fl!(I18N, "vram-pstates"),
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
controls {
|
||||
gtk::ToggleButton {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
#[wrap(Some)]
|
||||
set_child = &adw::ToolbarView {
|
||||
add_top_bar = &adw::HeaderBar {},
|
||||
|
||||
#[watch]
|
||||
#[block_signal(configured_toggled_handler)]
|
||||
set_active: model.states_configuration_enabled.value(),
|
||||
#[wrap(Some)]
|
||||
set_content = >k::ScrolledWindow {
|
||||
set_hscrollbar_policy: gtk::PolicyType::Never,
|
||||
set_propagate_natural_height: true,
|
||||
set_max_content_height: 500,
|
||||
|
||||
connect_toggled[sender] => move |button| {
|
||||
sender.input(PowerStatesFrameMsg::ConfiguredToggled {
|
||||
configured: button.is_active(),
|
||||
});
|
||||
} @ configured_toggled_handler,
|
||||
PageSection {
|
||||
set_hide_visible_container: true,
|
||||
set_margin_all: 15,
|
||||
add_css_class: "power-states-dialog-card",
|
||||
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
gtk::Label {
|
||||
set_label: &fl!(I18N, "enable-pstate-config"),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
content {
|
||||
gtk::ListBoxRow {
|
||||
set_activatable: false,
|
||||
set_selectable: false,
|
||||
|
||||
gtk::Box {
|
||||
set_spacing: 10,
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_homogeneous: true,
|
||||
|
||||
gtk::Box {
|
||||
#[template]
|
||||
append_child = &AdjustmentCard {
|
||||
#[template_child]
|
||||
advanced_features {
|
||||
#[watch]
|
||||
set_visible: model.has_core_states,
|
||||
append = model.core_states_list.widget(),
|
||||
set_visible: model.performance_level.is_some(),
|
||||
},
|
||||
|
||||
gtk::Box {
|
||||
#[watch]
|
||||
set_visible: model.has_vram_states,
|
||||
append = model.vram_states_list.widget(),
|
||||
#[template_child]
|
||||
controls {
|
||||
gtk::ToggleButton {
|
||||
set_halign: gtk::Align::Start,
|
||||
add_css_class: "adjustment-card-option-toggle",
|
||||
set_label: &fl!(I18N, "enable-pstate-config"),
|
||||
|
||||
#[watch]
|
||||
#[block_signal(configured_toggled_handler)]
|
||||
set_active: model.states_configuration_enabled.value(),
|
||||
|
||||
connect_toggled[sender] => move |button| {
|
||||
sender.input(PowerStatesDialogMsg::ConfiguredToggled {
|
||||
configured: button.is_active(),
|
||||
});
|
||||
} @ configured_toggled_handler,
|
||||
},
|
||||
},
|
||||
|
||||
#[template_child]
|
||||
content {
|
||||
gtk::ListBoxRow {
|
||||
set_activatable: false,
|
||||
set_selectable: false,
|
||||
#[watch]
|
||||
set_visible: model.domain == ClockDomain::Gpu && model.has_core_states,
|
||||
#[wrap(Some)]
|
||||
set_child = model.core_states_list.widget(),
|
||||
},
|
||||
|
||||
gtk::ListBoxRow {
|
||||
set_activatable: false,
|
||||
set_selectable: false,
|
||||
#[watch]
|
||||
set_visible: model.domain == ClockDomain::Vram && model.has_vram_states,
|
||||
#[wrap(Some)]
|
||||
set_child = model.vram_states_list.widget(),
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
@@ -128,11 +147,9 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
sender: ComponentSender<Self>,
|
||||
) -> ComponentParts<Self> {
|
||||
let core_states_list = PowerStatesList::detach(PowerStatesListOptions {
|
||||
title: fl!(I18N, "gpu-pstates"),
|
||||
value_suffix: fl!(I18N, "mhz"),
|
||||
});
|
||||
let vram_states_list = PowerStatesList::detach(PowerStatesListOptions {
|
||||
title: fl!(I18N, "vram-pstates"),
|
||||
value_suffix: fl!(I18N, "mhz"),
|
||||
});
|
||||
|
||||
@@ -141,13 +158,14 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
let configured_sender = sender.clone();
|
||||
let configured_signal =
|
||||
states_configuration_enabled.connect_value_notify(move |states_configured| {
|
||||
configured_sender.input(PowerStatesFrameMsg::InternalConfigurableChanged(
|
||||
configured_sender.input(PowerStatesDialogMsg::InternalConfigurableChanged(
|
||||
states_configured.get(),
|
||||
));
|
||||
APP_BROKER.send(AppMsg::SettingsChanged);
|
||||
});
|
||||
|
||||
let model = Self {
|
||||
domain: ClockDomain::Gpu,
|
||||
core_states_list,
|
||||
vram_states_list,
|
||||
states_configurable: BoolBinding::new(false),
|
||||
@@ -164,9 +182,20 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
ComponentParts { model, widgets }
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, sender: ComponentSender<Self>) {
|
||||
fn update_with_view(
|
||||
&mut self,
|
||||
widgets: &mut Self::Widgets,
|
||||
msg: Self::Input,
|
||||
sender: ComponentSender<Self>,
|
||||
root: &Self::Root,
|
||||
) {
|
||||
match msg {
|
||||
PowerStatesFrameMsg::PowerStates {
|
||||
PowerStatesDialogMsg::Show { domain, parent } => {
|
||||
self.domain = domain;
|
||||
self.update_view(widgets, sender.clone());
|
||||
root.present(Some(&parent));
|
||||
}
|
||||
PowerStatesDialogMsg::PowerStates {
|
||||
pstates,
|
||||
configured,
|
||||
} => {
|
||||
@@ -186,7 +215,7 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
self.vram_clock_ratio,
|
||||
));
|
||||
}
|
||||
PowerStatesFrameMsg::Stats(stats) => {
|
||||
PowerStatesDialogMsg::Stats(stats) => {
|
||||
self.core_states_list.emit(PowerStatesListMsg::ActiveState(
|
||||
stats.active_power_states.and_then(|states| states.core),
|
||||
));
|
||||
@@ -194,10 +223,12 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
stats.active_power_states.and_then(|states| states.memory),
|
||||
));
|
||||
}
|
||||
PowerStatesFrameMsg::VramClockRatio(ratio) => {
|
||||
PowerStatesDialogMsg::VramClockRatio(ratio) => {
|
||||
self.vram_clock_ratio = ratio;
|
||||
self.vram_states_list
|
||||
.emit(PowerStatesListMsg::ValueRatio(ratio));
|
||||
}
|
||||
PowerStatesFrameMsg::Configurable(is_plvl_manual) => {
|
||||
PowerStatesDialogMsg::Configurable(is_plvl_manual) => {
|
||||
let configurable = is_plvl_manual && (self.has_core_states || self.has_vram_states);
|
||||
self.states_configurable.set_value(configurable);
|
||||
|
||||
@@ -216,33 +247,34 @@ impl relm4::SimpleComponent for PowerStatesFrame {
|
||||
configurable && self.states_configuration_enabled.value(),
|
||||
));
|
||||
}
|
||||
PowerStatesFrameMsg::PerformanceLevel(level) => {
|
||||
PowerStatesDialogMsg::PerformanceLevel(level) => {
|
||||
self.performance_level = level;
|
||||
}
|
||||
PowerStatesFrameMsg::ConfiguredToggled { configured } => {
|
||||
PowerStatesDialogMsg::ConfiguredToggled { configured } => {
|
||||
if !configured || self.performance_level == Some(PerformanceLevel::Manual) {
|
||||
self.states_configuration_enabled.set_value(configured);
|
||||
} else {
|
||||
APP_BROKER.send(AppMsg::EnablePstateConfig);
|
||||
}
|
||||
}
|
||||
PowerStatesFrameMsg::EnableWithManualPerformanceLevel => {
|
||||
PowerStatesDialogMsg::EnableWithManualPerformanceLevel => {
|
||||
sender
|
||||
.output(OcPageMsg::SetPerformanceLevel(PerformanceLevel::Manual))
|
||||
.unwrap();
|
||||
self.states_configuration_enabled.set_value(true);
|
||||
}
|
||||
PowerStatesFrameMsg::InternalConfigurableChanged(configurable) => {
|
||||
PowerStatesDialogMsg::InternalConfigurableChanged(configurable) => {
|
||||
self.core_states_list
|
||||
.emit(PowerStatesListMsg::Configurable(configurable));
|
||||
self.vram_states_list
|
||||
.emit(PowerStatesListMsg::Configurable(configurable));
|
||||
}
|
||||
}
|
||||
self.update_view(widgets, sender);
|
||||
}
|
||||
}
|
||||
|
||||
impl PowerStatesFrame {
|
||||
impl PowerStatesDialog {
|
||||
pub fn get_enabled_power_states(&self) -> IndexMap<PowerLevelKind, Vec<u8>> {
|
||||
if self.states_configuration_enabled.value() {
|
||||
let state_types = [
|
||||
@@ -279,7 +311,7 @@ mod tests {
|
||||
adw::init().unwrap();
|
||||
let context = gtk::glib::MainContext::default();
|
||||
let _guard = context.acquire().unwrap();
|
||||
let frame = PowerStatesFrame::detach(());
|
||||
let dialog = PowerStatesDialog::detach(());
|
||||
let states = PowerStates {
|
||||
core: vec![],
|
||||
vram: (0..4)
|
||||
@@ -293,30 +325,30 @@ mod tests {
|
||||
};
|
||||
|
||||
// Queue the startup messages together, before child components can update
|
||||
frame.emit(PowerStatesFrameMsg::PowerStates {
|
||||
dialog.emit(PowerStatesDialogMsg::PowerStates {
|
||||
pstates: states,
|
||||
configured: true,
|
||||
});
|
||||
frame.emit(PowerStatesFrameMsg::Configurable(true));
|
||||
dialog.emit(PowerStatesDialogMsg::Configurable(true));
|
||||
while context.pending() {
|
||||
context.iteration(false);
|
||||
}
|
||||
assert!(frame.model().states_configuration_enabled.value());
|
||||
assert!(dialog.model().states_configuration_enabled.value());
|
||||
assert_eq!(
|
||||
frame.model().get_enabled_power_states()[&PowerLevelKind::MemoryClock],
|
||||
dialog.model().get_enabled_power_states()[&PowerLevelKind::MemoryClock],
|
||||
vec![3],
|
||||
);
|
||||
|
||||
// Changing to a GPU without power states must clear the previous capability
|
||||
frame.emit(PowerStatesFrameMsg::PowerStates {
|
||||
dialog.emit(PowerStatesDialogMsg::PowerStates {
|
||||
pstates: PowerStates::default(),
|
||||
configured: false,
|
||||
});
|
||||
frame.emit(PowerStatesFrameMsg::Configurable(true));
|
||||
dialog.emit(PowerStatesDialogMsg::Configurable(true));
|
||||
while context.pending() {
|
||||
context.iteration(false);
|
||||
}
|
||||
assert!(!frame.model().states_configurable.value());
|
||||
assert!(frame.model().get_enabled_power_states().is_empty());
|
||||
assert!(!dialog.model().states_configurable.value());
|
||||
assert!(dialog.model().get_enabled_power_states().is_empty());
|
||||
}
|
||||
}
|
||||
@@ -1,21 +1,18 @@
|
||||
use super::power_states_row::{PowerStateRow, PowerStateRowMsg, PowerStateRowOptions};
|
||||
use amdgpu_sysfs::gpu_handle::PowerLevelId;
|
||||
use gtk::prelude::{FrameExt, WidgetExt};
|
||||
use gtk::prelude::WidgetExt;
|
||||
use lact_schema::PowerState;
|
||||
use relm4::{
|
||||
ComponentParts, ComponentSender, RelmWidgetExt, binding::BoolBinding, css,
|
||||
prelude::FactoryVecDeque,
|
||||
};
|
||||
use relm4::{ComponentParts, ComponentSender, binding::BoolBinding, prelude::FactoryVecDeque};
|
||||
|
||||
pub struct PowerStatesList {
|
||||
states: FactoryVecDeque<PowerStateRow>,
|
||||
value_suffix: String,
|
||||
is_active_indicator_visible: BoolBinding,
|
||||
configurable: BoolBinding,
|
||||
active_state: Option<PowerLevelId>,
|
||||
}
|
||||
|
||||
pub struct PowerStatesListOptions {
|
||||
pub title: String,
|
||||
pub value_suffix: String,
|
||||
}
|
||||
|
||||
@@ -24,6 +21,7 @@ pub enum PowerStatesListMsg {
|
||||
PowerStates(Vec<PowerState>, f64),
|
||||
ActiveState(Option<PowerLevelId>),
|
||||
Configurable(bool),
|
||||
ValueRatio(f64),
|
||||
}
|
||||
|
||||
#[relm4::component(pub)]
|
||||
@@ -33,16 +31,11 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
type Output = ();
|
||||
|
||||
view! {
|
||||
gtk::Frame {
|
||||
gtk::Box {
|
||||
set_hexpand: true,
|
||||
#[wrap(Some)]
|
||||
set_label_widget = >k::Label {
|
||||
set_label: &opts.title,
|
||||
set_margin_horizontal: 5,
|
||||
add_css_class: css::CAPTION_HEADING,
|
||||
},
|
||||
#[local_ref]
|
||||
states_widget -> gtk::ListBox {
|
||||
set_hexpand: true,
|
||||
set_selection_mode: gtk::SelectionMode::None,
|
||||
},
|
||||
}
|
||||
@@ -60,6 +53,7 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
value_suffix: opts.value_suffix,
|
||||
is_active_indicator_visible: BoolBinding::new(false),
|
||||
configurable: BoolBinding::new(true),
|
||||
active_state: None,
|
||||
};
|
||||
|
||||
let states_widget = model.states.widget();
|
||||
@@ -75,12 +69,12 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
let mut states = self.states.guard();
|
||||
states.clear();
|
||||
|
||||
for mut power_state in new_pstates {
|
||||
power_state.value = (power_state.value as f64 * value_ratio) as u64;
|
||||
for power_state in new_pstates {
|
||||
let opts = PowerStateRowOptions {
|
||||
power_state,
|
||||
value_suffix: self.value_suffix.clone(),
|
||||
active: false,
|
||||
value_ratio,
|
||||
active: self.active_state.is_some() && power_state.id == self.active_state,
|
||||
show_active_indicator: self.is_active_indicator_visible.clone(),
|
||||
configurable: self.configurable.clone(),
|
||||
};
|
||||
@@ -88,10 +82,11 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
}
|
||||
}
|
||||
PowerStatesListMsg::ActiveState(active_idx) => {
|
||||
self.active_state = active_idx;
|
||||
self.is_active_indicator_visible
|
||||
.set_value(active_idx.is_some());
|
||||
for (i, row) in self.states.iter().enumerate() {
|
||||
let is_active = row.power_state.id == active_idx;
|
||||
let is_active = active_idx.is_some() && row.power_state.id == active_idx;
|
||||
|
||||
self.states.send(i, PowerStateRowMsg::Active(is_active));
|
||||
}
|
||||
@@ -99,6 +94,9 @@ impl relm4::SimpleComponent for PowerStatesList {
|
||||
PowerStatesListMsg::Configurable(configurable) => {
|
||||
self.configurable.set_value(configurable);
|
||||
}
|
||||
PowerStatesListMsg::ValueRatio(ratio) => {
|
||||
self.states.broadcast(PowerStateRowMsg::ValueRatio(ratio));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use crate::{APP_BROKER, app::msg::AppMsg};
|
||||
use amdgpu_sysfs::gpu_handle::PowerLevelId;
|
||||
use gtk::prelude::{BoxExt, OrientableExt, WidgetExt};
|
||||
use gtk::prelude::{BoxExt, ListBoxRowExt, OrientableExt, WidgetExt};
|
||||
use lact_schema::PowerState;
|
||||
use relm4::{RelmObjectExt, RelmWidgetExt, binding::BoolBinding, css};
|
||||
|
||||
@@ -9,6 +9,7 @@ pub struct PowerStateRow {
|
||||
pub(super) enabled: BoolBinding,
|
||||
pub(super) power_state: PowerState,
|
||||
value_suffix: String,
|
||||
value_ratio: f64,
|
||||
configurable: BoolBinding,
|
||||
show_active_indicator: BoolBinding,
|
||||
}
|
||||
@@ -16,6 +17,7 @@ pub struct PowerStateRow {
|
||||
pub struct PowerStateRowOptions {
|
||||
pub power_state: PowerState,
|
||||
pub value_suffix: String,
|
||||
pub value_ratio: f64,
|
||||
pub active: bool,
|
||||
pub show_active_indicator: BoolBinding,
|
||||
pub configurable: BoolBinding,
|
||||
@@ -24,6 +26,7 @@ pub struct PowerStateRowOptions {
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum PowerStateRowMsg {
|
||||
Active(bool),
|
||||
ValueRatio(f64),
|
||||
}
|
||||
|
||||
#[relm4::factory(pub)]
|
||||
@@ -35,40 +38,60 @@ impl relm4::factory::FactoryComponent for PowerStateRow {
|
||||
type Init = PowerStateRowOptions;
|
||||
|
||||
view! {
|
||||
gtk::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
set_margin_vertical: 2,
|
||||
set_margin_horizontal: 5,
|
||||
gtk::ListBoxRow {
|
||||
set_selectable: false,
|
||||
set_activatable: false,
|
||||
set_focusable: false,
|
||||
|
||||
append: image = >k::Image {
|
||||
set_icon_name: Some("pan-end-symbolic"),
|
||||
add_binding: (&self.show_active_indicator, "visible"),
|
||||
#[watch]
|
||||
set_opacity: if self.active.value() { 1.0 } else { 0.0 },
|
||||
},
|
||||
#[wrap(Some)]
|
||||
set_child = >k::Box {
|
||||
set_orientation: gtk::Orientation::Horizontal,
|
||||
set_spacing: 5,
|
||||
set_margin_vertical: 2,
|
||||
set_margin_horizontal: 5,
|
||||
|
||||
append = >k::CheckButton {
|
||||
add_binding: (&self.enabled, "active"),
|
||||
add_binding: (&self.configurable, "visible"),
|
||||
set_sensitive: matches!(self.power_state.id, Some(PowerLevelId::Index(_))),
|
||||
},
|
||||
append: image = >k::Image {
|
||||
set_icon_name: Some("pan-end-symbolic"),
|
||||
add_binding: (&self.show_active_indicator, "visible"),
|
||||
#[watch]
|
||||
set_opacity: if self.active.value() { 1.0 } else { 0.0 },
|
||||
},
|
||||
|
||||
append = >k::Label {
|
||||
add_css_class: css::MONOSPACE,
|
||||
#[watch]
|
||||
set_class_active: (css::DIM_LABEL, !self.active.value()),
|
||||
set_label: &{
|
||||
let index_text = match self.power_state.id {
|
||||
Some(PowerLevelId::Index(index)) => index.to_string(),
|
||||
Some(PowerLevelId::Sleep) => "S".to_owned(),
|
||||
append = >k::CheckButton {
|
||||
add_binding: (&self.enabled, "active"),
|
||||
add_binding: (&self.configurable, "visible"),
|
||||
set_sensitive: matches!(self.power_state.id, Some(PowerLevelId::Index(_))),
|
||||
},
|
||||
|
||||
append = >k::Label {
|
||||
add_css_class: css::MONOSPACE,
|
||||
#[watch]
|
||||
set_class_active: (css::DIM_LABEL, !self.active.value()),
|
||||
set_label: &match self.power_state.id {
|
||||
Some(PowerLevelId::Index(index)) => format!("{index}:"),
|
||||
Some(PowerLevelId::Sleep) => "S:".to_owned(),
|
||||
None => String::new(),
|
||||
};
|
||||
let value_text = match self.power_state.min_value {
|
||||
Some(min) if min != self.power_state.value => format!("{min}-{}", self.power_state.value),
|
||||
_ => self.power_state.value.to_string(),
|
||||
};
|
||||
format!("{index_text}: {value_text} {}", self.value_suffix)
|
||||
},
|
||||
},
|
||||
|
||||
append = >k::Label {
|
||||
add_css_class: css::MONOSPACE,
|
||||
set_hexpand: true,
|
||||
set_xalign: 1.0,
|
||||
#[watch]
|
||||
set_class_active: (css::DIM_LABEL, !self.active.value()),
|
||||
#[watch]
|
||||
set_label: &{
|
||||
let value = (self.power_state.value as f64 * self.value_ratio) as u64;
|
||||
let value_text = match self.power_state.min_value {
|
||||
Some(min) if min != self.power_state.value => {
|
||||
let min = (min as f64 * self.value_ratio) as u64;
|
||||
format!("{min}-{value}")
|
||||
}
|
||||
_ => value.to_string(),
|
||||
};
|
||||
format!("{value_text} {}", self.value_suffix)
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
@@ -87,13 +110,16 @@ impl relm4::factory::FactoryComponent for PowerStateRow {
|
||||
active: BoolBinding::new(opts.active),
|
||||
power_state: opts.power_state,
|
||||
value_suffix: opts.value_suffix,
|
||||
value_ratio: opts.value_ratio,
|
||||
configurable: opts.configurable,
|
||||
show_active_indicator: opts.show_active_indicator,
|
||||
}
|
||||
}
|
||||
|
||||
fn update(&mut self, msg: Self::Input, _: relm4::FactorySender<Self>) {
|
||||
let PowerStateRowMsg::Active(active) = msg;
|
||||
self.active.set_value(active);
|
||||
match msg {
|
||||
PowerStateRowMsg::Active(active) => self.active.set_value(active),
|
||||
PowerStateRowMsg::ValueRatio(ratio) => self.value_ratio = ratio,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +92,7 @@ impl relm4::Component for VfCurveEditor {
|
||||
view! {
|
||||
#[root]
|
||||
adw::Window {
|
||||
set_application: Some(&relm4::main_application()),
|
||||
set_hide_on_close: true,
|
||||
set_default_size: (1100, 700),
|
||||
set_title: Some(&fl!(I18N, "vf-curve-editor")),
|
||||
|
||||
@@ -172,6 +172,22 @@ impl relm4::Component for PreferencesDialog {
|
||||
},
|
||||
},
|
||||
|
||||
add = &adw::PreferencesGroup {
|
||||
set_title: &fl!(I18N, "experimental-features"),
|
||||
|
||||
adw::SwitchRow {
|
||||
set_title: &fl!(I18N, "nvidia-pstate-offsets"),
|
||||
set_subtitle: &fl!(I18N, "nvidia-pstate-offsets-description"),
|
||||
set_active: CONFIG.read().experimental_nvidia_pstate_offsets,
|
||||
connect_active_notify => move |row| {
|
||||
CONFIG.write().edit(|config| {
|
||||
config.experimental_nvidia_pstate_offsets = row.is_active();
|
||||
});
|
||||
APP_BROKER.send(AppMsg::ExperimentalFeaturesChanged);
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
add = &adw::PreferencesGroup {
|
||||
set_title: &fl!(I18N, "daemon"),
|
||||
|
||||
|
||||
@@ -30,6 +30,8 @@ pub struct UiConfig {
|
||||
pub theme: AppTheme,
|
||||
#[serde(default)]
|
||||
pub color_scheme: AppColorScheme,
|
||||
#[serde(default)]
|
||||
pub experimental_nvidia_pstate_offsets: bool,
|
||||
pub language: Option<String>,
|
||||
pub window_size: Option<WindowSize>,
|
||||
}
|
||||
@@ -45,6 +47,7 @@ impl Default for UiConfig {
|
||||
gpus: HashMap::new(),
|
||||
theme: AppTheme::Automatic,
|
||||
color_scheme: AppColorScheme::default(),
|
||||
experimental_nvidia_pstate_offsets: false,
|
||||
language: None,
|
||||
window_size: None,
|
||||
}
|
||||
|
||||
@@ -218,6 +218,9 @@
|
||||
"1458": {
|
||||
"2409": "GIGABYTE RX 6950 XT Gaming OC"
|
||||
},
|
||||
"148c": {
|
||||
"2420": "PowerColor Red Devil RX 6950 XT"
|
||||
},
|
||||
"1849": {
|
||||
"5230": "ASRock RX 6950 XT OC Formula"
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user