Skip to content

Commit fcf3a10

Browse files
committed
fix(memory): memory growth app side
1 parent 7f8649d commit fcf3a10

9 files changed

Lines changed: 132 additions & 22 deletions

File tree

Cargo.lock

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

crates/wayle-shell/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ build = "build.rs"
1313
gtk4-layer-shell.workspace = true
1414
gtk4.workspace = true
1515
gdk4.workspace = true
16+
gdk-pixbuf.workspace = true
1617
bytesize.workspace = true
1718
chrono.workspace = true
1819
console.workspace = true

crates/wayle-shell/src/shell/bar/dropdowns/notification/notification_item/methods.rs

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,11 @@ use wayle_notification::core::types::Action;
55
use super::NotificationItem;
66
use crate::{
77
i18n::t,
8-
shell::notification_popup::helpers::{RelativeTime, ResolvedIcon},
8+
shell::notification_popup::helpers::{RelativeTime, ResolvedIcon, load_scaled_file_icon},
99
};
1010

1111
const MAX_ACTIONS_PER_ROW: usize = 3;
12+
const DROPDOWN_ICON_TEXTURE_SIZE_PX: i32 = 48;
1213

1314
impl NotificationItem {
1415
pub(super) fn apply_icon(&self, icon: &gtk::Image, icon_container: &gtk::Box) {
@@ -21,8 +22,13 @@ impl NotificationItem {
2122
}
2223

2324
ResolvedIcon::File(path) => {
24-
icon.set_from_file(Some(path));
25-
icon_container.add_css_class("file-icon");
25+
if let Some(texture) = load_scaled_file_icon(path, DROPDOWN_ICON_TEXTURE_SIZE_PX)
26+
{
27+
icon.set_paintable(Some(&texture));
28+
icon_container.add_css_class("file-icon");
29+
} else {
30+
icon.set_icon_name(Some("ld-bell-symbolic"));
31+
}
2632
}
2733
}
2834
}

crates/wayle-shell/src/shell/bar/modules/systray/item/helpers.rs

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::{collections::hash_map::DefaultHasher, hash::Hasher, path::Path};
2+
13
use gtk4::{gdk, glib, prelude::Cast};
24
use wayle_systray::types::item::IconPixmap;
35

@@ -25,21 +27,43 @@ pub(super) fn create_texture_from_pixmap(pixmap: &IconPixmap) -> Option<gdk::Tex
2527
.into()
2628
}
2729

28-
pub(super) fn load_icon_from_theme_path(theme_path: &str, icon_name: &str) -> Option<gdk::Texture> {
30+
pub(super) fn hash_pixmaps(pixmaps: &[IconPixmap]) -> u64 {
31+
let mut hasher = DefaultHasher::new();
32+
33+
for pixmap in pixmaps {
34+
hasher.write_i32(pixmap.width);
35+
hasher.write_i32(pixmap.height);
36+
hasher.write_usize(pixmap.data.len());
37+
hasher.write(&pixmap.data);
38+
}
39+
40+
hasher.finish()
41+
}
42+
43+
pub(super) fn load_scaled_texture_from_file(path: &str) -> Option<gdk::Texture> {
44+
let pixbuf = gdk_pixbuf::Pixbuf::from_file_at_scale(path, TARGET_ICON_SIZE, TARGET_ICON_SIZE, true).ok()?;
45+
Some(gdk::Texture::for_pixbuf(&pixbuf))
46+
}
47+
48+
pub(super) fn find_icon_in_theme_path(theme_path: &str, icon_name: &str) -> Option<String> {
2949
if theme_path.is_empty() {
3050
return None;
3151
}
3252

3353
for ext in ICON_EXTENSIONS {
3454
let file_path = format!("{theme_path}/{icon_name}.{ext}");
35-
if let Ok(texture) = gdk::Texture::from_filename(&file_path) {
36-
return Some(texture);
55+
if Path::new(&file_path).is_file() {
56+
return Some(file_path);
3757
}
3858
}
3959

4060
None
4161
}
4262

63+
pub(super) fn load_icon_from_theme_path(theme_path: &str, icon_name: &str) -> Option<gdk::Texture> {
64+
find_icon_in_theme_path(theme_path, icon_name).as_deref().and_then(load_scaled_texture_from_file)
65+
}
66+
4367
fn argb_to_rgba(argb: &[u8]) -> Vec<u8> {
4468
argb.chunks_exact(4)
4569
.flat_map(|chunk| {

crates/wayle-shell/src/shell/bar/modules/systray/item/methods.rs

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
use std::path::Path;
2+
13
#[allow(deprecated)]
24
use gtk4::prelude::StyleContextExt;
35
use gtk4::{gdk, gio, glib::idle_add_local_once};
@@ -12,8 +14,11 @@ use wayle_systray::{
1214
};
1315

1416
use super::{
15-
SystrayItem, SystrayItemMsg,
16-
helpers::{create_texture_from_pixmap, load_icon_from_theme_path, select_best_pixmap},
17+
IconSignature, SystrayItem, SystrayItemMsg,
18+
helpers::{
19+
create_texture_from_pixmap, find_icon_in_theme_path, hash_pixmaps,
20+
load_icon_from_theme_path, load_scaled_texture_from_file, select_best_pixmap,
21+
},
1722
};
1823
use crate::shell::bar::modules::systray::helpers::find_override;
1924

@@ -165,7 +170,12 @@ impl SystrayItem {
165170
.and_then(|entry| entry.icon.clone())
166171
.or_else(|| self.item.icon_name.get());
167172

168-
self.apply_icon(image, icon_name.as_deref());
173+
let icon_signature = self.icon_signature(icon_name.as_deref());
174+
175+
if self.icon_signature.as_ref() != Some(&icon_signature) {
176+
self.apply_icon(image, icon_name.as_deref());
177+
self.icon_signature = Some(icon_signature);
178+
}
169179

170180
if let Some(color) = override_match.and_then(|entry| entry.color.clone()) {
171181
self.apply_icon_color(image, &color.to_css());
@@ -174,6 +184,32 @@ impl SystrayItem {
174184
}
175185
}
176186

187+
fn icon_signature(&self, icon_name: Option<&str>) -> IconSignature {
188+
if let Some(name) = icon_name {
189+
let theme_path = self.item.icon_theme_path.get();
190+
191+
if let Some(file_path) = theme_path
192+
.as_deref()
193+
.and_then(|path| find_icon_in_theme_path(path, name))
194+
{
195+
return IconSignature::File(file_path);
196+
}
197+
198+
if Path::new(name).is_file() {
199+
return IconSignature::File(name.to_owned());
200+
}
201+
202+
return IconSignature::Named(name.to_owned());
203+
}
204+
205+
let pixmaps = self.item.icon_pixmap.get();
206+
if pixmaps.is_empty() {
207+
return IconSignature::Fallback;
208+
}
209+
210+
IconSignature::Pixmap(hash_pixmaps(&pixmaps))
211+
}
212+
177213
fn apply_icon_color(&mut self, image: &gtk::Image, css_color: &str) {
178214
let provider = self
179215
.icon_color_provider
@@ -182,20 +218,29 @@ impl SystrayItem {
182218
let css = format!("image {{ color: {css_color}; }}");
183219
provider.load_from_string(&css);
184220

185-
#[allow(deprecated)]
186-
image
187-
.style_context()
188-
.add_provider(provider, gtk::STYLE_PROVIDER_PRIORITY_USER + 1);
221+
if !self.icon_color_provider_attached {
222+
#[allow(deprecated)]
223+
image
224+
.style_context()
225+
.add_provider(provider, gtk::STYLE_PROVIDER_PRIORITY_USER + 1);
226+
self.icon_color_provider_attached = true;
227+
}
189228
}
190229

191230
fn clear_icon_color(&mut self, image: &gtk::Image) {
192-
if let Some(provider) = self.icon_color_provider.take() {
231+
if self.icon_color_provider_attached
232+
&& let Some(provider) = self.icon_color_provider.as_ref()
233+
{
193234
#[allow(deprecated)]
194-
image.style_context().remove_provider(&provider);
235+
image.style_context().remove_provider(provider);
236+
self.icon_color_provider_attached = false;
195237
}
196238
}
197239

198240
fn apply_icon(&self, image: &gtk::Image, icon_name: Option<&str>) {
241+
image.set_icon_name(None);
242+
image.set_paintable(None::<&gdk::Texture>);
243+
199244
if let Some(name) = icon_name {
200245
let theme_path = self.item.icon_theme_path.get();
201246
if let Some(texture) = theme_path
@@ -206,7 +251,7 @@ impl SystrayItem {
206251
return;
207252
}
208253

209-
if let Ok(texture) = gdk::Texture::from_filename(name) {
254+
if let Some(texture) = load_scaled_texture_from_file(name) {
210255
image.set_paintable(Some(&texture));
211256
return;
212257
}

crates/wayle-shell/src/shell/bar/modules/systray/item/mod.rs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,23 @@ pub(super) struct SystrayItem {
2424
config: Arc<ConfigService>,
2525
button: Option<gtk::Button>,
2626
icon: Option<gtk::Image>,
27+
icon_signature: Option<IconSignature>,
2728
icon_color_provider: Option<gtk::CssProvider>,
29+
icon_color_provider_attached: bool,
2830
popover: Option<gtk::PopoverMenu>,
2931
action_group: Option<SimpleActionGroup>,
3032
registered_accels: Vec<String>,
3133
cancel_token: CancellationToken,
3234
}
3335

36+
#[derive(Clone, Debug, PartialEq, Eq)]
37+
pub(super) enum IconSignature {
38+
File(String),
39+
Named(String),
40+
Pixmap(u64),
41+
Fallback,
42+
}
43+
3444
#[derive(Debug)]
3545
#[allow(clippy::enum_variant_names)]
3646
pub(super) enum SystrayItemMsg {
@@ -74,7 +84,9 @@ impl FactoryComponent for SystrayItem {
7484
config: init.config,
7585
button: None,
7686
icon: None,
87+
icon_signature: None,
7788
icon_color_provider: None,
89+
icon_color_provider_attached: false,
7890
popover: None,
7991
action_group: None,
8092
registered_accels: Vec::new(),

crates/wayle-shell/src/shell/notification_popup/card/methods.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,13 @@ use wayle_notification::core::types::Action;
66
use super::NotificationPopupCard;
77
use crate::{
88
i18n::t,
9-
shell::notification_popup::helpers::{RelativeTime, ResolvedIcon, urgency_bar_visible},
9+
shell::notification_popup::helpers::{
10+
RelativeTime, ResolvedIcon, load_scaled_file_icon, urgency_bar_visible,
11+
},
1012
};
1113

14+
const POPUP_ICON_TEXTURE_SIZE_PX: i32 = 64;
15+
1216
impl NotificationPopupCard {
1317
pub(super) fn apply_css_classes(
1418
&self,
@@ -35,8 +39,12 @@ impl NotificationPopupCard {
3539
}
3640

3741
ResolvedIcon::File(path) => {
38-
icon.set_from_file(Some(path));
39-
icon_container.add_css_class("file-icon");
42+
if let Some(texture) = load_scaled_file_icon(path, POPUP_ICON_TEXTURE_SIZE_PX) {
43+
icon.set_paintable(Some(&texture));
44+
icon_container.add_css_class("file-icon");
45+
} else {
46+
icon.set_icon_name(Some("ld-bell-symbolic"));
47+
}
4048
}
4149
}
4250
}

crates/wayle-shell/src/shell/notification_popup/helpers.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use chrono::{DateTime, Utc};
2-
use relm4::gtk::{glib, pango};
2+
use relm4::gtk::{gdk, glib, pango};
33
use wayle_config::schemas::modules::notification::{IconSource, UrgencyBarThreshold};
44
use wayle_notification::types::Urgency;
55

@@ -131,6 +131,17 @@ fn mapped_icon(app_name: &Option<String>) -> ResolvedIcon {
131131
ResolvedIcon::Named(String::from(name))
132132
}
133133

134+
/// Loads a file-based icon as a scaled texture to avoid keeping oversized
135+
/// image allocations alive when notifications provide large images.
136+
pub(crate) fn load_scaled_file_icon(path: &str, target_px: i32) -> Option<gdk::Texture> {
137+
if path.is_empty() {
138+
return None;
139+
}
140+
141+
let pixbuf = gdk_pixbuf::Pixbuf::from_file_at_scale(path, target_px, target_px, true).ok()?;
142+
Some(gdk::Texture::for_pixbuf(&pixbuf))
143+
}
144+
134145
#[cfg(test)]
135146
#[allow(clippy::panic)]
136147
mod tests {

crates/wayle-widgets/src/components/bar_buttons/component.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -237,8 +237,10 @@ impl Component for BarButton {
237237
}
238238
BarButtonInput::ConfigChanged => {}
239239
BarButtonInput::SetThresholdColors(colors) => {
240-
self.threshold_overrides = colors;
241-
self.reload_css();
240+
if colors != self.threshold_overrides {
241+
self.threshold_overrides = colors;
242+
self.reload_css();
243+
}
242244
}
243245
}
244246
}

0 commit comments

Comments
 (0)