Skip to content

Commit 2ab4dca

Browse files
picture-frames@KopfdesDaemons: Version 1.1.0 (#1789)
* refactor * organize settings in tabs and sections * translation * fix GIF animation after reboot
1 parent 270e8a6 commit 2ab4dca

8 files changed

Lines changed: 264 additions & 97 deletions

File tree

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# Changelog
2+
3+
## [1.1.0] 06.04.2026
4+
5+
- New Features
6+
- GIF support
7+
- Refactoring
8+
- Removed hard-coded local dir path
9+
- Changed desklet entry point to `on_desklet_added_to_desktop`
10+
- Added UUID to error logs
11+
- Added settings cleanup in `on_desklet_removed` function
12+
- Added default settings to constructor
13+
- Organized settings in tabs and sections
14+
15+
## [1.0.0] 09.12.2025
16+
17+
- Initial release

picture-frames@KopfdesDaemons/files/picture-frames@KopfdesDaemons/desklet.js

Lines changed: 101 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ const Cogl = imports.gi.Cogl;
1111
const Gio = imports.gi.Gio;
1212

1313
const UUID = "picture-frames@KopfdesDaemons";
14-
Gettext.bindtextdomain(UUID, GLib.get_home_dir() + "/.local/share/locale");
14+
Gettext.bindtextdomain(UUID, GLib.get_user_data_dir() + "/locale");
1515

1616
function _(str) {
1717
return Gettext.dgettext(UUID, str);
@@ -20,34 +20,75 @@ function _(str) {
2020
class MyDesklet extends Desklet.Desklet {
2121
constructor(metadata, deskletId) {
2222
super(metadata, deskletId);
23-
this.defaultImagePath = this.metadata.path + "/images/default.jpg";
23+
this.setHeader(_("Picture Frame"));
24+
25+
this._defaultImagePath = this.metadata.path + "/images/default.jpg";
26+
this._animationTimeoutId = null;
27+
this._isReloading = false;
28+
29+
// Default settings
30+
this.imagePath = "";
31+
this.shape = "wave";
32+
this.size = 250;
33+
this.showBorder = true;
34+
this.borderColor = "rgba(33, 170, 70, 0.5)";
35+
this.borderWidth = 15;
36+
this.wavesNumber = 10;
37+
this.waveDepth = 5;
38+
this.spikesNumber = 10;
39+
this.spikesDepth = 10;
40+
this.alignY = 50;
41+
this.alignX = 50;
2442

2543
// Setup settings and bind them to properties
26-
const settings = new Settings.DeskletSettings(this, metadata["uuid"], deskletId);
27-
settings.bindProperty(Settings.BindingDirection.IN, "image-path", "imagePath", this._initUI.bind(this));
28-
settings.bindProperty(Settings.BindingDirection.IN, "shape", "shape", this._initUI.bind(this));
29-
settings.bindProperty(Settings.BindingDirection.IN, "size", "size", this._initUI.bind(this));
30-
settings.bindProperty(Settings.BindingDirection.IN, "show-border", "showBorder", this._initUI.bind(this));
31-
settings.bindProperty(Settings.BindingDirection.IN, "border-color", "borderColor", this._initUI.bind(this));
32-
settings.bindProperty(Settings.BindingDirection.IN, "border-width", "borderWidth", this._initUI.bind(this));
33-
settings.bindProperty(Settings.BindingDirection.IN, "waves-number", "wavesNumber", this._initUI.bind(this));
34-
settings.bindProperty(Settings.BindingDirection.IN, "spikes-number", "spikesNumber", this._initUI.bind(this));
35-
settings.bindProperty(Settings.BindingDirection.IN, "wave-depth", "waveDepth", this._initUI.bind(this));
36-
settings.bindProperty(Settings.BindingDirection.IN, "spikes-depth", "spikesDepth", this._initUI.bind(this));
37-
settings.bindProperty(Settings.BindingDirection.IN, "align-x", "alignX", this._initUI.bind(this));
38-
settings.bindProperty(Settings.BindingDirection.IN, "align-y", "alignY", this._initUI.bind(this));
44+
this.settings = new Settings.DeskletSettings(this, metadata["uuid"], deskletId);
45+
this.settings.bindProperty(Settings.BindingDirection.IN, "image-path", "imagePath", this._initUI.bind(this));
46+
this.settings.bindProperty(Settings.BindingDirection.IN, "shape", "shape", this._initUI.bind(this));
47+
this.settings.bindProperty(Settings.BindingDirection.IN, "size", "size", this._initUI.bind(this));
48+
this.settings.bindProperty(Settings.BindingDirection.IN, "show-border", "showBorder", this._initUI.bind(this));
49+
this.settings.bindProperty(Settings.BindingDirection.IN, "border-color", "borderColor", this._initUI.bind(this));
50+
this.settings.bindProperty(Settings.BindingDirection.IN, "border-width", "borderWidth", this._initUI.bind(this));
51+
this.settings.bindProperty(Settings.BindingDirection.IN, "waves-number", "wavesNumber", this._initUI.bind(this));
52+
this.settings.bindProperty(Settings.BindingDirection.IN, "spikes-number", "spikesNumber", this._initUI.bind(this));
53+
this.settings.bindProperty(Settings.BindingDirection.IN, "wave-depth", "waveDepth", this._initUI.bind(this));
54+
this.settings.bindProperty(Settings.BindingDirection.IN, "spikes-depth", "spikesDepth", this._initUI.bind(this));
55+
this.settings.bindProperty(Settings.BindingDirection.IN, "align-x", "alignX", this._initUI.bind(this));
56+
this.settings.bindProperty(Settings.BindingDirection.IN, "align-y", "alignY", this._initUI.bind(this));
57+
}
3958

40-
this.setHeader(_("Picture Frame"));
59+
on_desklet_added_to_desktop() {
4160
this._initUI();
4261
}
4362

63+
on_desklet_removed() {
64+
if (this._animationTimeoutId) {
65+
GLib.source_remove(this._animationTimeoutId);
66+
this._animationTimeoutId = null;
67+
}
68+
if (this.settings && !this._isReloading) {
69+
this.settings.finalize();
70+
}
71+
}
72+
73+
on_desklet_reloaded() {
74+
this._isReloading = true;
75+
}
76+
4477
_initUI() {
78+
this._loadId = (this._loadId || 0) + 1;
79+
const currentLoadId = this._loadId;
80+
81+
if (this._animationTimeoutId) {
82+
GLib.source_remove(this._animationTimeoutId);
83+
this._animationTimeoutId = null;
84+
}
85+
4586
const mainContainer = new St.BoxLayout({ vertical: true });
46-
if (!this.imagePath) this.imagePath = this.defaultImagePath;
87+
if (!this.imagePath) this.imagePath = this._defaultImagePath;
4788

4889
const size = this.size;
4990
const finalImagePath = decodeURIComponent(this.imagePath.replace("file://", ""));
50-
const imageActor = this._createShapedImageActor(finalImagePath, size);
91+
const imageActor = this._createShapedImageActor(finalImagePath, size, currentLoadId);
5192

5293
mainContainer.add_child(imageActor);
5394
this.setContent(mainContainer);
@@ -129,12 +170,12 @@ class MyDesklet extends Desklet.Desklet {
129170
cr.closePath();
130171
}
131172

132-
_createShapedImageActor(imagePath, size) {
173+
_createShapedImageActor(imagePath, size, loadId) {
133174
const canvas = new Clutter.Canvas();
134175
canvas.set_size(size, size);
135176
const actor = new Clutter.Actor({ width: size, height: size, content: canvas });
136177
const file = Gio.file_new_for_path(imagePath);
137-
let pixbuf = null;
178+
let iter = null;
138179

139180
canvas.connect("draw", (canvas, cr, width, height) => {
140181
// Clear the canvas
@@ -144,7 +185,7 @@ class MyDesklet extends Desklet.Desklet {
144185
cr.restore();
145186
cr.setOperator(Cairo.Operator.OVER);
146187

147-
if (pixbuf === null) {
188+
if (iter === null) {
148189
// Draw loading text
149190
cr.setSourceRGBA(1.0, 1.0, 1.0, 0.7); // Semi-transparent white
150191
cr.selectFontFace("sans-serif", Cairo.FontSlant.NORMAL, Cairo.FontWeight.NORMAL);
@@ -155,25 +196,51 @@ class MyDesklet extends Desklet.Desklet {
155196
cr.showText(text);
156197
} else {
157198
// Draw the shaped image once pixbuf is loaded
158-
this._drawFinalImage(cr, pixbuf, width, height);
199+
const currentPixbuf = iter.get_pixbuf();
200+
this._drawFinalImage(cr, currentPixbuf, width, height);
159201
}
160202
return true;
161203
});
162204
canvas.invalidate(); // Initial draw with "Loading..."
163205

164206
file.read_async(GLib.PRIORITY_DEFAULT, null, (source, res) => {
207+
// Check if this load operation is still relevant
208+
if (this._loadId !== loadId) return;
165209
try {
166210
const stream = source.read_finish(res);
167-
GdkPixbuf.Pixbuf.new_from_stream_async(stream, null, (source, res) => {
211+
GdkPixbuf.PixbufAnimation.new_from_stream_async(stream, null, (source, res) => {
212+
if (this._loadId !== loadId) return;
168213
try {
169-
pixbuf = GdkPixbuf.Pixbuf.new_from_stream_finish(res);
170-
canvas.invalidate(); // Force a redraw now that the pixbuf is loaded
214+
const anim = GdkPixbuf.PixbufAnimation.new_from_stream_finish(res);
215+
iter = anim.get_iter(null);
216+
217+
const updateAnimation = () => {
218+
if (this._loadId !== loadId) return;
219+
if (this._animationTimeoutId) {
220+
GLib.source_remove(this._animationTimeoutId);
221+
this._animationTimeoutId = null;
222+
}
223+
const delay = iter.get_delay_time();
224+
if (delay >= 0) {
225+
// -1 means static image, no animation
226+
this._animationTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, delay > 0 ? delay : 100, () => {
227+
if (this._loadId !== loadId) return GLib.SOURCE_REMOVE;
228+
iter.advance(null);
229+
canvas.invalidate();
230+
updateAnimation();
231+
return GLib.SOURCE_REMOVE;
232+
});
233+
}
234+
};
235+
236+
canvas.invalidate();
237+
updateAnimation();
171238
} catch (e) {
172-
global.logError(`Error creating pixbuf from stream: ${e}`);
239+
global.logError(`${UUID}: Error creating pixbuf animation from stream: ${e}`);
173240
}
174241
});
175242
} catch (e) {
176-
global.logError(`Error reading file async: ${e}`);
243+
global.logError(`${UUID}: Error reading file async: ${e}`);
177244
}
178245
});
179246
return actor;
@@ -197,16 +264,17 @@ class MyDesklet extends Desklet.Desklet {
197264
newHeight = width / aspect;
198265
}
199266

200-
const scaledPixbuf = pixbuf.scale_simple(newWidth, newHeight, GdkPixbuf.InterpType.BILINEAR);
201-
const pixbufWithAlpha = scaledPixbuf.add_alpha(false, 0, 0, 0);
202-
203267
cr.save();
204268
this._drawShapePath(cr, this.shape, width / 2, height / 2, width / 2);
205269
cr.clip();
206270

207271
const drawX = (width - newWidth) * (this.alignX / 100);
208272
const drawY = (height - newHeight) * (this.alignY / 100);
209-
Gdk.cairo_set_source_pixbuf(cr, pixbufWithAlpha, drawX, drawY);
273+
274+
cr.translate(drawX, drawY);
275+
cr.scale(newWidth / originalWidth, newHeight / originalHeight);
276+
277+
Gdk.cairo_set_source_pixbuf(cr, pixbuf, 0, 0);
210278
cr.paint();
211279
cr.restore();
212280

@@ -225,7 +293,7 @@ class MyDesklet extends Desklet.Desklet {
225293
cr.stroke();
226294
}
227295
} catch (e) {
228-
global.logError(`Error drawing shaped image: ${e}`);
296+
global.logError(`${UUID}: Error drawing shaped image: ${e}`);
229297
}
230298
}
231299

@@ -247,7 +315,7 @@ class MyDesklet extends Desklet.Desklet {
247315
const pixBuf = GdkPixbuf.Pixbuf.new_from_file_at_size(imageFileName, requestedWidth, requestedHeight);
248316
return this._createActorFromPixbuf(pixBuf);
249317
} catch (e) {
250-
global.logError(`Error loading image ${imageFileName}: ${e}`);
318+
global.logError(`${UUID}: Error loading image ${imageFileName}: ${e}`);
251319
return new St.Label({ text: "Error" + e.message, style_class: "picture-frame-error-label" });
252320
}
253321
}

picture-frames@KopfdesDaemons/files/picture-frames@KopfdesDaemons/metadata.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
"uuid": "picture-frames@KopfdesDaemons",
33
"name": "Picture frames",
44
"description": "Displays photos in different picture frames.",
5-
"version": "1.0",
5+
"version": "1.1.0",
66
"prevent-decorations": true,
77
"max-instances": "50"
88
}

picture-frames@KopfdesDaemons/files/picture-frames@KopfdesDaemons/po/de.po

Lines changed: 27 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgstr ""
88
"Project-Id-Version: picture-frames@KopfdesDaemons 1.0\n"
99
"Report-Msgid-Bugs-To: https://github.com/linuxmint/cinnamon-spices-desklets/"
1010
"issues\n"
11-
"POT-Creation-Date: 2025-10-19 11:16+0200\n"
11+
"POT-Creation-Date: 2026-04-05 23:38+0200\n"
1212
"PO-Revision-Date: \n"
1313
"Last-Translator: \n"
1414
"Language-Team: \n"
@@ -18,11 +18,11 @@ msgstr ""
1818
"Content-Transfer-Encoding: 8bit\n"
1919
"X-Generator: Poedit 3.4.2\n"
2020

21-
#. desklet.js:40
21+
#. desklet.js:23
2222
msgid "Picture Frame"
2323
msgstr "Bilderrahmen"
2424

25-
#. desklet.js:152
25+
#. desklet.js:190
2626
msgid "Loading..."
2727
msgstr "Lade..."
2828

@@ -34,9 +34,30 @@ msgstr "Bilderrahmen"
3434
msgid "Displays photos in different picture frames."
3535
msgstr "Zeigt Bilder in verschiedenen Bilderrahmen an."
3636

37-
#. settings-schema.json->head1->description
38-
msgid "Image Settings"
39-
msgstr "Bildeinstellungen"
37+
#. settings-schema.json->image-page->title
38+
msgid "Image"
39+
msgstr "Bild"
40+
41+
#. settings-schema.json->frame-page->title
42+
msgid "Frame"
43+
msgstr "Rahmen"
44+
45+
#. settings-schema.json->image-general-section->title
46+
msgid "General"
47+
msgstr "Allgemein"
48+
49+
#. settings-schema.json->image-alignment-section->title
50+
msgid "Alignment"
51+
msgstr "Ausrichtung"
52+
53+
#. settings-schema.json->frame-shape-section->title
54+
#. settings-schema.json->shape->description
55+
msgid "Shape"
56+
msgstr "Form"
57+
58+
#. settings-schema.json->frame-border-section->title
59+
msgid "Border"
60+
msgstr "Rand"
4061

4162
#. settings-schema.json->image-path->description
4263
msgid "Image path"
@@ -58,14 +79,6 @@ msgstr "Horizontale Ausrichtung"
5879
msgid "Vertical alignment"
5980
msgstr "Vertikale Ausrichtung"
6081

61-
#. settings-schema.json->head2->description
62-
msgid "Frame Settings"
63-
msgstr "Rahmeneinstellungen"
64-
65-
#. settings-schema.json->shape->description
66-
msgid "Shape"
67-
msgstr "Form"
68-
6982
#. settings-schema.json->shape->options
7083
msgid "Circle"
7184
msgstr "Kreis"

picture-frames@KopfdesDaemons/files/picture-frames@KopfdesDaemons/po/es.po

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ msgstr ""
88
"Project-Id-Version: picture-frames@KopfdesDaemons 1.0\n"
99
"Report-Msgid-Bugs-To: https://github.com/linuxmint/cinnamon-spices-desklets/"
1010
"issues\n"
11-
"POT-Creation-Date: 2025-10-19 11:16+0200\n"
11+
"POT-Creation-Date: 2026-04-05 23:38+0200\n"
1212
"PO-Revision-Date: \n"
1313
"Last-Translator: \n"
1414
"Language-Team: \n"
@@ -18,11 +18,11 @@ msgstr ""
1818
"Content-Transfer-Encoding: 8bit\n"
1919
"X-Generator: Poedit 3.8\n"
2020

21-
#. desklet.js:40
21+
#. desklet.js:23
2222
msgid "Picture Frame"
2323
msgstr "Marco de fotos"
2424

25-
#. desklet.js:152
25+
#. desklet.js:190
2626
msgid "Loading..."
2727
msgstr "Cargando..."
2828

@@ -34,9 +34,32 @@ msgstr "Marco de fotos"
3434
msgid "Displays photos in different picture frames."
3535
msgstr "Muestra fotos en diferentes marcos de fotos."
3636

37-
#. settings-schema.json->head1->description
38-
msgid "Image Settings"
39-
msgstr "Ajustes de imagen"
37+
#. settings-schema.json->image-page->title
38+
#, fuzzy
39+
msgid "Image"
40+
msgstr "Ruta de la imagen"
41+
42+
#. settings-schema.json->frame-page->title
43+
msgid "Frame"
44+
msgstr ""
45+
46+
#. settings-schema.json->image-general-section->title
47+
msgid "General"
48+
msgstr ""
49+
50+
#. settings-schema.json->image-alignment-section->title
51+
msgid "Alignment"
52+
msgstr ""
53+
54+
#. settings-schema.json->frame-shape-section->title
55+
#. settings-schema.json->shape->description
56+
msgid "Shape"
57+
msgstr "Forma"
58+
59+
#. settings-schema.json->frame-border-section->title
60+
#, fuzzy
61+
msgid "Border"
62+
msgstr "Color del borde"
4063

4164
#. settings-schema.json->image-path->description
4265
msgid "Image path"
@@ -58,14 +81,6 @@ msgstr "Alineación horizontal"
5881
msgid "Vertical alignment"
5982
msgstr "Alineación vertical"
6083

61-
#. settings-schema.json->head2->description
62-
msgid "Frame Settings"
63-
msgstr "Configuración del marco"
64-
65-
#. settings-schema.json->shape->description
66-
msgid "Shape"
67-
msgstr "Forma"
68-
6984
#. settings-schema.json->shape->options
7085
msgid "Circle"
7186
msgstr "Círculo"

0 commit comments

Comments
 (0)