Skip to content

Commit e1d447a

Browse files
authored
fix(users): on setting user icons, shrink icons larger than 1 MB
1 parent 8bc944a commit e1d447a

File tree

1 file changed

+72
-2
lines changed
  • cosmic-settings/src/pages/system/users

1 file changed

+72
-2
lines changed

cosmic-settings/src/pages/system/users/mod.rs

Lines changed: 72 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ use cosmic::{
1111
widget::{self, Space, column, icon, row, settings, text},
1212
};
1313
use cosmic_settings_page::{self as page, Section, section};
14+
use image::GenericImageView;
1415
use pwhash::{bcrypt, md5_crypt, sha256_crypt, sha512_crypt};
1516
use regex::Regex;
1617
use slab::Slab;
@@ -20,7 +21,7 @@ use std::{
2021
fs::File,
2122
future::Future,
2223
io::{BufRead, BufReader},
23-
path::PathBuf,
24+
path::{Path, PathBuf},
2425
sync::Arc,
2526
};
2627
use url::Url;
@@ -29,6 +30,14 @@ use zbus_polkit::policykit1::CheckAuthorizationFlags;
2930
const DEFAULT_ICON_FILE: &str = "/usr/share/pixmaps/faces/pop-robot.png";
3031
const USERS_ADMIN_POLKIT_POLICY_ID: &str = "com.system76.CosmicSettings.Users.Admin";
3132

33+
// AccountsService has a hard limit of 1MB for icon files
34+
// https://gitlab.freedesktop.org/accountsservice/accountsservice/-/blob/main/src/user.c#L3131
35+
const MAX_ICON_SIZE_BYTES: u64 = 1_048_576;
36+
// Use a smaller threshold to ensure compressed images stay under the limit
37+
const ICON_SIZE_THRESHOLD: u64 = 900_000; // 900KB
38+
// Target dimensions for resized profile icons
39+
const TARGET_ICON_SIZE: u32 = 512;
40+
3241
#[derive(Clone, Debug, Default)]
3342
pub struct User {
3443
id: u64,
@@ -124,6 +133,58 @@ impl From<Message> for crate::pages::Message {
124133
}
125134
}
126135

136+
fn prepare_icon_file(path: &Path) -> Result<PathBuf, Box<dyn std::error::Error>> {
137+
let metadata = std::fs::metadata(path)?;
138+
let file_size = metadata.len();
139+
140+
tracing::debug!("Icon file size: {} bytes", file_size);
141+
142+
if file_size <= ICON_SIZE_THRESHOLD {
143+
tracing::debug!("File size is acceptable, using original file");
144+
return Ok(path.to_path_buf());
145+
}
146+
147+
tracing::info!(
148+
"Icon file is {} bytes, resizing to fit under 1MB limit",
149+
file_size
150+
);
151+
152+
let img = image::open(path)?;
153+
let (width, height) = img.dimensions();
154+
155+
tracing::debug!("Original image dimensions: {}x{}", width, height);
156+
157+
let (new_width, new_height) = if width > height {
158+
let ratio = TARGET_ICON_SIZE as f32 / width as f32;
159+
(TARGET_ICON_SIZE, (height as f32 * ratio) as u32)
160+
} else {
161+
let ratio = TARGET_ICON_SIZE as f32 / height as f32;
162+
((width as f32 * ratio) as u32, TARGET_ICON_SIZE)
163+
};
164+
165+
tracing::debug!("Resizing to {}x{}", new_width, new_height);
166+
167+
let resized = img.resize(new_width, new_height, image::imageops::FilterType::Lanczos3);
168+
169+
// Create a temporary file for the resized icon
170+
let temp_dir = std::env::temp_dir();
171+
let temp_filename = format!("cosmic-settings-icon-{}.png", std::process::id());
172+
let temp_path = temp_dir.join(temp_filename);
173+
174+
tracing::debug!("Saving resized icon to: {:?}", temp_path);
175+
176+
resized.save(&temp_path)?;
177+
178+
let new_size = std::fs::metadata(&temp_path)?.len();
179+
tracing::info!("Resized icon file size: {} bytes", new_size);
180+
181+
if new_size > MAX_ICON_SIZE_BYTES {
182+
tracing::warn!("Resized file is still too large, but attempting anyway");
183+
}
184+
185+
Ok(temp_path)
186+
}
187+
127188
impl page::Page<crate::pages::Message> for Page {
128189
fn set_id(&mut self, entity: page::Entity) {
129190
self.entity = entity;
@@ -466,8 +527,17 @@ impl Page {
466527
return Message::None;
467528
};
468529

530+
// Prepare the icon file, resizing if necessary to fit within accountsservice's 1MB limit
531+
let icon_path = match prepare_icon_file(&path) {
532+
Ok(p) => p,
533+
Err(why) => {
534+
tracing::error!(?why, "failed to prepare icon file");
535+
return Message::None;
536+
}
537+
};
538+
469539
let result = request_permission_on_denial(&conn, || {
470-
user.set_icon_file(path.to_str().unwrap())
540+
user.set_icon_file(icon_path.to_str().unwrap())
471541
})
472542
.await;
473543

0 commit comments

Comments
 (0)