mirror of
https://github.com/ilya-zlobintsev/LACT.git
synced 2025-02-25 18:55:26 -06:00
Formatting and cleanup
This commit is contained in:
@@ -1,5 +1,8 @@
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::{collections::{BTreeMap, HashMap}, fs, io, path::PathBuf};
|
||||
use std::collections::{BTreeMap, HashMap};
|
||||
use std::fs;
|
||||
use std::io;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::gpu_controller::PowerProfile;
|
||||
|
||||
@@ -31,7 +34,9 @@ pub struct GpuIdentifier {
|
||||
|
||||
impl PartialEq for GpuIdentifier {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
self.pci_id == other.pci_id && self.gpu_model == other.gpu_model && self.card_model == other.card_model
|
||||
self.pci_id == other.pci_id
|
||||
&& self.gpu_model == other.gpu_model
|
||||
&& self.card_model == other.card_model
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -119,7 +119,12 @@ impl DaemonConnection {
|
||||
}
|
||||
}*/
|
||||
|
||||
pub fn set_gpu_max_power_state(&self, gpu_id: u32, clockspeed: i64, voltage: Option<i64>) -> Result<(), DaemonError> {
|
||||
pub fn set_gpu_max_power_state(
|
||||
&self,
|
||||
gpu_id: u32,
|
||||
clockspeed: i64,
|
||||
voltage: Option<i64>,
|
||||
) -> Result<(), DaemonError> {
|
||||
match self.send_action(Action::SetGPUMaxPowerState(gpu_id, clockspeed, voltage))? {
|
||||
DaemonResponse::OK => Ok(()),
|
||||
_ => unreachable!(),
|
||||
@@ -146,14 +151,14 @@ impl DaemonConnection {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn get_gpus(&self) -> Result<HashMap<u32, Option<String>>, DaemonError> {
|
||||
match self.send_action(Action::GetGpus)? {
|
||||
DaemonResponse::Gpus(gpus) => Ok(gpus),
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn shutdown(&self) {
|
||||
let mut s = UnixStream::connect(SOCK_PATH).unwrap();
|
||||
s.write_all(&bincode::serialize(&Action::Shutdown).unwrap())
|
||||
|
||||
@@ -813,7 +813,7 @@ impl GpuController {
|
||||
self.config.gpu_max_clock = clockspeed;
|
||||
self.config.gpu_max_voltage = voltage;
|
||||
}
|
||||
ClocksTable::New(clocks_table) => {
|
||||
ClocksTable::New(_) => {
|
||||
let s_line = format!("s 1 {}\n", clockspeed);
|
||||
|
||||
fs::write(self.hw_path.join("pp_od_clk_voltage"), s_line)?;
|
||||
@@ -847,7 +847,7 @@ impl GpuController {
|
||||
// .gpu_power_states
|
||||
// .insert(*profile, (clockspeed, voltage.unwrap()));
|
||||
}
|
||||
ClocksTable::New(clocks_table) => {
|
||||
ClocksTable::New(_) => {
|
||||
let s_line = format!("m 1 {}\n", clockspeed);
|
||||
|
||||
fs::write(self.hw_path.join("pp_od_clk_voltage"), s_line)?;
|
||||
|
||||
@@ -44,12 +44,15 @@ impl HWMon {
|
||||
mon.start_fan_control().unwrap();
|
||||
}
|
||||
if let Some(cap) = power_cap {
|
||||
mon.set_power_cap(cap);
|
||||
#[allow(unused_must_use)]
|
||||
{
|
||||
mon.set_power_cap(cap);
|
||||
}
|
||||
}
|
||||
|
||||
mon
|
||||
}
|
||||
|
||||
|
||||
pub fn get_fan_max_speed(&self) -> Option<i64> {
|
||||
match fs::read_to_string(self.hwmon_path.join("fan1_max")) {
|
||||
Ok(speed) => Some(speed.trim().parse().unwrap()),
|
||||
@@ -84,12 +87,7 @@ impl HWMon {
|
||||
let filename = self.hwmon_path.join("freq2_input");
|
||||
|
||||
match fs::read_to_string(filename) {
|
||||
Ok(freq) => Some(freq
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
.unwrap()
|
||||
/ 1000
|
||||
/ 1000),
|
||||
Ok(freq) => Some(freq.trim().parse::<i64>().unwrap() / 1000 / 1000),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
@@ -98,12 +96,7 @@ impl HWMon {
|
||||
let filename = self.hwmon_path.join("freq1_input");
|
||||
|
||||
match fs::read_to_string(filename) {
|
||||
Ok(freq) => Some(freq
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
.unwrap()
|
||||
/ 1000
|
||||
/ 1000),
|
||||
Ok(freq) => Some(freq.trim().parse::<i64>().unwrap() / 1000 / 1000),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
@@ -112,18 +105,14 @@ impl HWMon {
|
||||
let filename = self.hwmon_path.join("temp1_input");
|
||||
|
||||
match fs::read_to_string(filename) {
|
||||
Ok(temp) => Some(temp
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
.unwrap()
|
||||
/ 1000),
|
||||
Ok(temp) => Some(temp.trim().parse::<i64>().unwrap() / 1000),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_voltage(&self) -> Option<i64> {
|
||||
let filename = self.hwmon_path.join("in0_input");
|
||||
|
||||
|
||||
match fs::read_to_string(filename) {
|
||||
Ok(voltage) => Some(voltage.trim().parse::<i64>().unwrap()),
|
||||
Err(_) => None,
|
||||
@@ -134,11 +123,7 @@ impl HWMon {
|
||||
let filename = self.hwmon_path.join("power1_cap_max");
|
||||
|
||||
match fs::read_to_string(filename) {
|
||||
Ok(power_cap) => Some(power_cap
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
.unwrap()
|
||||
/ 1000000),
|
||||
Ok(power_cap) => Some(power_cap.trim().parse::<i64>().unwrap() / 1000000),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
@@ -147,20 +132,20 @@ impl HWMon {
|
||||
let filename = self.hwmon_path.join("power1_cap");
|
||||
|
||||
match fs::read_to_string(filename) {
|
||||
Ok(a) => Some(a
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
.unwrap()
|
||||
/ 1000000),
|
||||
Ok(a) => Some(a.trim().parse::<i64>().unwrap() / 1000000),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_power_cap(&mut self, cap: i64) -> Result<(), HWMonError> {
|
||||
if cap > self.get_power_cap_max().ok_or_else(|| HWMonError::Unsupported)? {
|
||||
if cap
|
||||
> self
|
||||
.get_power_cap_max()
|
||||
.ok_or_else(|| HWMonError::Unsupported)?
|
||||
{
|
||||
return Err(HWMonError::InvalidValue);
|
||||
}
|
||||
|
||||
|
||||
let cap = cap * 1000000;
|
||||
log::trace!("setting power cap to {}", cap);
|
||||
|
||||
@@ -174,11 +159,7 @@ impl HWMon {
|
||||
let filename = self.hwmon_path.join("power1_average");
|
||||
|
||||
match fs::read_to_string(filename) {
|
||||
Ok(a) => Some(a
|
||||
.trim()
|
||||
.parse::<i64>()
|
||||
.unwrap()
|
||||
/ 1000000),
|
||||
Ok(a) => Some(a.trim().parse::<i64>().unwrap() / 1000000),
|
||||
Err(_) => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -62,7 +62,7 @@ impl Daemon {
|
||||
.arg(SOCK_PATH)
|
||||
.output()
|
||||
.expect("Failed to chmod");
|
||||
|
||||
|
||||
Command::new("chown")
|
||||
.arg("nobody:wheel")
|
||||
.arg(SOCK_PATH)
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
use std::thread;
|
||||
|
||||
use daemon::{Daemon, daemon_connection::DaemonConnection};
|
||||
use signal_hook::iterator::Signals;
|
||||
use daemon::{daemon_connection::DaemonConnection, Daemon};
|
||||
use signal_hook::consts::{SIGINT, SIGTERM};
|
||||
use signal_hook::iterator::Signals;
|
||||
|
||||
fn main() {
|
||||
env_logger::init();
|
||||
let d = Daemon::new(false);
|
||||
let mut signals = Signals::new(&[SIGTERM, SIGINT]).unwrap();
|
||||
|
||||
|
||||
thread::spawn(move || {
|
||||
for _ in signals.forever() {
|
||||
log::info!("Shutting down");
|
||||
@@ -16,6 +16,6 @@ fn main() {
|
||||
d.shutdown();
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
d.listen();
|
||||
}
|
||||
|
||||
@@ -1,8 +1,7 @@
|
||||
use std::{collections::HashMap, env};
|
||||
|
||||
use gtk::prelude::{ComboBoxExtManual, ObjectExt};
|
||||
use gtk::*;
|
||||
use pango::EllipsizeMode;
|
||||
use std::collections::HashMap;
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct Header {
|
||||
@@ -14,12 +13,10 @@ pub struct Header {
|
||||
impl Header {
|
||||
pub fn new() -> Self {
|
||||
let container = HeaderBar::new();
|
||||
|
||||
|
||||
container.set_custom_title(Some(&Grid::new())); // Bad workaround to hide the title
|
||||
|
||||
// if env::var("XDG_CURRENT_DESKTOP") == Ok("GNOME".to_string()) {
|
||||
container.set_show_close_button(true);
|
||||
// }
|
||||
container.set_show_close_button(true);
|
||||
|
||||
let gpu_selector = ComboBoxText::new();
|
||||
container.pack_start(&gpu_selector);
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
mod info_page;
|
||||
mod thermals_page;
|
||||
mod oc_page;
|
||||
mod thermals_page;
|
||||
|
||||
use gtk::*;
|
||||
|
||||
@@ -27,11 +27,10 @@ impl RootStack {
|
||||
let oc_page = OcPage::new();
|
||||
|
||||
container.add_titled(&oc_page.container, "oc_page", "OC");
|
||||
|
||||
|
||||
let thermals_page = ThermalsPage::new();
|
||||
|
||||
container.add_titled(&thermals_page.container, "thermals_page", "Thermals");
|
||||
|
||||
|
||||
Self {
|
||||
container,
|
||||
|
||||
@@ -68,27 +68,22 @@ impl VulkanInfoFrame {
|
||||
|
||||
grid.attach(&version_label, 2, 1, 3, 1);
|
||||
|
||||
|
||||
let features_expander = Expander::new(Some("Feature support"));
|
||||
|
||||
grid.attach(&features_expander, 0, 2, 5, 1);
|
||||
|
||||
|
||||
let features_scrolled_window = ScrolledWindow::new(NONE_ADJUSTMENT, NONE_ADJUSTMENT);
|
||||
|
||||
|
||||
features_scrolled_window.set_vexpand(true);
|
||||
|
||||
|
||||
let features_box = Box::new(Orientation::Vertical, 5);
|
||||
|
||||
|
||||
features_box.set_halign(Align::Center);
|
||||
|
||||
|
||||
features_scrolled_window.add(&features_box);
|
||||
|
||||
|
||||
features_expander.add(&features_scrolled_window);
|
||||
|
||||
|
||||
container.add(&grid);
|
||||
|
||||
Self {
|
||||
@@ -107,19 +102,18 @@ impl VulkanInfoFrame {
|
||||
self.version_label
|
||||
.set_markup(&format!("<b>{}</b>", vulkan_info.api_version));
|
||||
|
||||
|
||||
for (feature, supported) in vulkan_info.features.iter() {
|
||||
let vbox = Box::new(Orientation::Horizontal, 5);
|
||||
|
||||
|
||||
let feature_name_label = Label::new(Some(feature));
|
||||
|
||||
|
||||
vbox.pack_start(&feature_name_label, false, false, 0);
|
||||
|
||||
let feature_supported_checkbutton = CheckButton::new();
|
||||
|
||||
|
||||
feature_supported_checkbutton.set_sensitive(false);
|
||||
feature_supported_checkbutton.set_active(*supported);
|
||||
|
||||
|
||||
vbox.pack_start(&feature_supported_checkbutton, false, false, 0);
|
||||
|
||||
self.features_box.pack_end(&vbox, false, false, 0);
|
||||
|
||||
@@ -156,20 +156,20 @@ impl ClocksFrame {
|
||||
.set_upper(clocks_table.gpu_clocks_range.1 as f64);
|
||||
|
||||
/* self.gpu_voltage_adjustment
|
||||
.set_lower(clocks_table.voltage_range.0 as f64 / 1000.0);
|
||||
self.gpu_voltage_adjustment
|
||||
.set_upper(clocks_table.voltage_range.1 as f64 / 1000.0);*/
|
||||
.set_lower(clocks_table.voltage_range.0 as f64 / 1000.0);
|
||||
self.gpu_voltage_adjustment
|
||||
.set_upper(clocks_table.voltage_range.1 as f64 / 1000.0);*/
|
||||
|
||||
self.vram_clock_adjustment
|
||||
.set_lower(clocks_table.mem_clocks_range.0 as f64);
|
||||
self.vram_clock_adjustment
|
||||
.set_upper(clocks_table.mem_clocks_range.1 as f64);
|
||||
|
||||
|
||||
self.gpu_clock_adjustment.set_value(clocks_table.current_gpu_clocks.1 as f64);
|
||||
self.gpu_clock_adjustment
|
||||
.set_value(clocks_table.current_gpu_clocks.1 as f64);
|
||||
|
||||
// self.gpu_voltage_adjustment
|
||||
// .set_value(*clocks_table.gpu_voltage as f64 / 1000.0);
|
||||
// .set_value(*clocks_table.gpu_voltage as f64 / 1000.0);
|
||||
|
||||
self.vram_clock_adjustment
|
||||
.set_value(clocks_table.current_max_mem_clock as f64);
|
||||
|
||||
@@ -80,7 +80,7 @@ impl PowerProfileFrame {
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn show(&self) {
|
||||
self.container.set_visible(true);
|
||||
}
|
||||
@@ -88,7 +88,7 @@ impl PowerProfileFrame {
|
||||
pub fn hide(&self) {
|
||||
self.container.set_visible(false);
|
||||
}
|
||||
|
||||
|
||||
pub fn get_visibility(&self) -> bool {
|
||||
self.container.get_visible()
|
||||
}
|
||||
|
||||
@@ -43,59 +43,59 @@ impl StatsGrid {
|
||||
let gpu_clock_label = Label::new(None);
|
||||
{
|
||||
let gpu_clock_box = Box::new(Orientation::Horizontal, 5);
|
||||
|
||||
|
||||
gpu_clock_box.pack_start(&Label::new(Some("GPU Clock:")), false, false, 2);
|
||||
|
||||
gpu_clock_label.set_markup("<b>0MHz</b>");
|
||||
|
||||
gpu_clock_box.pack_start(&gpu_clock_label, false, false, 2);
|
||||
|
||||
|
||||
gpu_clock_box.set_halign(Align::Center);
|
||||
|
||||
|
||||
container.attach(&gpu_clock_box, 0, 1, 1, 1);
|
||||
}
|
||||
|
||||
let vram_clock_label = Label::new(None);
|
||||
{
|
||||
let vram_clock_box = Box::new(Orientation::Horizontal, 5);
|
||||
|
||||
|
||||
vram_clock_box.pack_start(&Label::new(Some("VRAM Clock:")), false, false, 2);
|
||||
|
||||
vram_clock_label.set_markup("<b>0MHz</b>");
|
||||
|
||||
|
||||
vram_clock_box.pack_start(&vram_clock_label, false, false, 2);
|
||||
|
||||
|
||||
vram_clock_box.set_halign(Align::Center);
|
||||
|
||||
|
||||
container.attach(&vram_clock_box, 1, 1, 1, 1);
|
||||
}
|
||||
let gpu_voltage_label = Label::new(None);
|
||||
{
|
||||
let gpu_voltage_box = Box::new(Orientation::Horizontal, 5);
|
||||
|
||||
|
||||
gpu_voltage_box.pack_start(&Label::new(Some("GPU Voltage:")), false, false, 2);
|
||||
|
||||
gpu_voltage_label.set_markup("<b>0.000V</b>");
|
||||
|
||||
gpu_voltage_box.pack_start(&gpu_voltage_label, false, false, 2);
|
||||
|
||||
|
||||
gpu_voltage_box.set_halign(Align::Center);
|
||||
|
||||
|
||||
container.attach(&gpu_voltage_box, 0, 2, 1, 1);
|
||||
}
|
||||
|
||||
let power_usage_label = Label::new(None);
|
||||
{
|
||||
let power_usage_box = Box::new(Orientation::Horizontal, 5);
|
||||
|
||||
|
||||
power_usage_box.pack_start(&Label::new(Some("Power Usage:")), false, false, 2);
|
||||
|
||||
power_usage_label.set_markup("<b>00/000W</b>");
|
||||
|
||||
|
||||
power_usage_box.pack_start(&power_usage_label, false, false, 2);
|
||||
|
||||
|
||||
power_usage_box.set_halign(Align::Center);
|
||||
|
||||
|
||||
container.attach(&power_usage_box, 1, 2, 1, 1);
|
||||
}
|
||||
|
||||
@@ -109,17 +109,37 @@ impl StatsGrid {
|
||||
power_usage_label,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
pub fn set_stats(&self, stats: &GpuStats) {
|
||||
self.vram_usage_bar.set_value(stats.mem_used.unwrap_or_else(|| 0) as f64 / stats.mem_total.unwrap_or_else(|| 0) as f64);
|
||||
self.vram_usage_label.set_text(&format!("{}/{} MiB", stats.mem_used.unwrap_or_else(|| 0), stats.mem_total.unwrap_or_else(|| 0)));
|
||||
|
||||
self.gpu_clock_label.set_markup(&format!("<b>{}MHz</b>", stats.gpu_freq.unwrap_or_else(|| 0)));
|
||||
|
||||
self.vram_clock_label.set_markup(&format!("<b>{}MHz</b>", stats.mem_freq.unwrap_or_else(|| 0)));
|
||||
|
||||
self.gpu_voltage_label.set_markup(&format!("<b>{}V</b>", stats.voltage.unwrap_or_else(|| 0) as f64 / 1000f64));
|
||||
|
||||
self.power_usage_label.set_markup(&format!("<b>{}/{}W</b>", stats.power_avg.unwrap_or_else(|| 0), stats.power_cap.unwrap_or_else(|| 0)));
|
||||
self.vram_usage_bar.set_value(
|
||||
stats.mem_used.unwrap_or_else(|| 0) as f64
|
||||
/ stats.mem_total.unwrap_or_else(|| 0) as f64,
|
||||
);
|
||||
self.vram_usage_label.set_text(&format!(
|
||||
"{}/{} MiB",
|
||||
stats.mem_used.unwrap_or_else(|| 0),
|
||||
stats.mem_total.unwrap_or_else(|| 0)
|
||||
));
|
||||
|
||||
self.gpu_clock_label.set_markup(&format!(
|
||||
"<b>{}MHz</b>",
|
||||
stats.gpu_freq.unwrap_or_else(|| 0)
|
||||
));
|
||||
|
||||
self.vram_clock_label.set_markup(&format!(
|
||||
"<b>{}MHz</b>",
|
||||
stats.mem_freq.unwrap_or_else(|| 0)
|
||||
));
|
||||
|
||||
self.gpu_voltage_label.set_markup(&format!(
|
||||
"<b>{}V</b>",
|
||||
stats.voltage.unwrap_or_else(|| 0) as f64 / 1000f64
|
||||
));
|
||||
|
||||
self.power_usage_label.set_markup(&format!(
|
||||
"<b>{}/{}W</b>",
|
||||
stats.power_avg.unwrap_or_else(|| 0),
|
||||
stats.power_cap.unwrap_or_else(|| 0)
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,3 @@
|
||||
use std::fs;
|
||||
|
||||
use gtk::*;
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -10,20 +8,20 @@ pub struct WarningFrame {
|
||||
impl WarningFrame {
|
||||
pub fn new() -> Self {
|
||||
let container = Frame::new(Some("Overclocking information"));
|
||||
|
||||
|
||||
container.set_label_align(0.3, 0.5);
|
||||
|
||||
|
||||
let warning_label = Label::new(None);
|
||||
|
||||
warning_label.set_line_wrap(true);
|
||||
warning_label.set_markup("Overclocking support is not enabled! To enable overclocking support, you need to add <b>amdgpu.ppfeaturemask=0xffffffff</b> to your kernel boot options. Look for the documentation of your distro.");
|
||||
warning_label.set_selectable(true);
|
||||
|
||||
|
||||
container.add(&warning_label);
|
||||
|
||||
|
||||
Self { container }
|
||||
}
|
||||
|
||||
|
||||
pub fn show(&self) {
|
||||
self.container.set_visible(true);
|
||||
}
|
||||
@@ -31,4 +29,4 @@ impl WarningFrame {
|
||||
pub fn hide(&self) {
|
||||
self.container.set_visible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,10 +1,9 @@
|
||||
mod fan_curve_frame;
|
||||
|
||||
use std::{collections::BTreeMap, thread};
|
||||
|
||||
use daemon::gpu_controller::{FanControlInfo, GpuStats};
|
||||
use gtk::prelude::*;
|
||||
use gtk::*;
|
||||
use std::collections::BTreeMap;
|
||||
|
||||
use fan_curve_frame::FanCurveFrame;
|
||||
|
||||
@@ -108,10 +107,10 @@ impl ThermalsPage {
|
||||
let diag = MessageDialog::new(None::<&Window>, DialogFlags::empty(), MessageType::Warning, ButtonsType::Ok,
|
||||
"Warning! Due to a driver bug, a reboot may be required for fan control to properly switch back to automatic.");
|
||||
diag.run();
|
||||
diag.hide();
|
||||
diag.hide();
|
||||
glib::Continue(false)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
fan_curve_frame.hide();
|
||||
} else {
|
||||
@@ -152,11 +151,10 @@ impl ThermalsPage {
|
||||
|
||||
self.fan_control_enabled_switch
|
||||
.set_active(!fan_control_info.enabled);
|
||||
|
||||
|
||||
if !fan_control_info.enabled {
|
||||
self.fan_curve_frame.hide();
|
||||
}
|
||||
else {
|
||||
} else {
|
||||
self.fan_curve_frame.show();
|
||||
}
|
||||
|
||||
|
||||
@@ -174,10 +174,10 @@ impl FanCurveFrame {
|
||||
self.adjustment_4.set_value(*curve.get(&80).unwrap());
|
||||
self.adjustment_5.set_value(*curve.get(&100).unwrap());
|
||||
}
|
||||
|
||||
|
||||
pub fn get_curve(&self) -> BTreeMap<i64, f64> {
|
||||
let mut curve = BTreeMap::new();
|
||||
|
||||
|
||||
curve.insert(20, self.adjustment_1.get_value());
|
||||
curve.insert(40, self.adjustment_2.get_value());
|
||||
curve.insert(60, self.adjustment_3.get_value());
|
||||
|
||||
Reference in New Issue
Block a user