Skip to content

Commit 4ea706d

Browse files
desklets: add 'Swatch Internet Time' (swatchtime@kdawson) — initial release
1 parent 02ba529 commit 4ea706d

File tree

8 files changed

+358
-0
lines changed

8 files changed

+358
-0
lines changed

swatchtime@kdawson/README.md

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
# Swatch Internet Time Desklet
2+
3+
**version 1.0**
4+
5+
Last updated: Nov. 24, 2025
6+
7+
This Cinnamon desklet displays the Swatch Internet Time in "beats". Swatch Internet Time is a decimal time concept introduced by the Swatch corporation in 1998. It divides the day into 1,000 "beats" instead of hours, minutes, and seconds. Each beat is equivalent to 1 minute and 26.4 seconds. The day starts at midnight BMT (Biel Mean Time, UTC+1). You can learn more about Swatch Internet Time here:
8+
9+
- https://www.swatch.com/en-us/internet-time.html
10+
11+
- https://en.wikipedia.org/wiki/Swatch_Internet_Time
12+
13+
Tested and developed on Linux Mint Debian Edition (LMDE) 7 using Cinnamon 6.4.13
14+
15+
## Features:
16+
17+
- Displays Swatch Internet Time in beats
18+
- Display centibeats (e.g. @000.00)
19+
- Adjustable background color and opacity
20+
- Adjustable font-size
21+
- Show/hide logo
22+
23+
## Author:
24+
25+
Ken Dawson (https://github.com/kendawson-online)
26+
27+
GitHub: https://github.com/swatchtime/
28+
29+
Report issues: https://github.com/swatchtime/cinnamon-desklet/issues
Lines changed: 210 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,210 @@
1+
const Gio = imports.gi.Gio;
2+
const St = imports.gi.St;
3+
const Desklet = imports.ui.desklet;
4+
const Mainloop = imports.mainloop;
5+
const GLib = imports.gi.GLib;
6+
const Gettext = imports.gettext;
7+
const Settings = imports.ui.settings;
8+
const Cinnamon = imports.gi.Cinnamon;
9+
const Lang = imports.lang;
10+
const Main = imports.ui.main;
11+
const Clutter = imports.gi.Clutter;
12+
const GdkPixbuf = imports.gi.GdkPixbuf;
13+
const Cogl = imports.gi.Cogl;
14+
15+
const UUID = "swatchtime@kdawson";
16+
const DESKLET_ROOT = imports.ui.deskletManager.deskletMeta[UUID].path;
17+
18+
19+
function MyDesklet(metadata, desklet_id) {
20+
this._init(metadata, desklet_id);
21+
}
22+
23+
MyDesklet.prototype = {
24+
__proto__: Desklet.Desklet.prototype,
25+
26+
_init: function(metadata, desklet_id) {
27+
Desklet.Desklet.prototype._init.call(this, metadata, desklet_id);
28+
this.metadata = metadata;
29+
30+
this.settings = new Settings.DeskletSettings(this, this.metadata.uuid, desklet_id);
31+
this.settings.bind('show-centibeats', 'showCentibeats', this._onSettingsChanged);
32+
this.settings.bind('show-logo', 'showLogo', this._onSettingsChanged);
33+
this.settings.bind('bg-color', 'bgColor', this._onSettingsChanged);
34+
this.settings.bind('bg-opacity', 'bgOpacity', this._onSettingsChanged);
35+
this.settings.bind('font-color', 'fontColor', this._onSettingsChanged);
36+
this.settings.bind('font-size', 'fontSize', this._onSettingsChanged);
37+
38+
this._onSettingsChanged();
39+
this.setupUI();
40+
this._startTimer();
41+
},
42+
43+
setupUI: function() {
44+
// outer container
45+
this.container = new St.Bin({ reactive: true });
46+
47+
// background box with padding and rounded style via inline CSS
48+
this.bg = new St.BoxLayout({ style_class: 'swatch-bg', vertical: false });
49+
50+
// flag icon
51+
this.flag = null;
52+
if (this.showLogo) {
53+
try {
54+
let flagPath = DESKLET_ROOT + '/icon.png';
55+
let file = Gio.file_new_for_path(flagPath);
56+
if (file.query_exists(null)) {
57+
let gicon = new Gio.FileIcon({ file: file });
58+
this.flag = new St.Icon({ gicon: gicon, icon_size: Math.round(this.fontSize * 0.9) });
59+
}
60+
} catch (e) {}
61+
}
62+
63+
// label for swatch beats
64+
this.label = new St.Label({ text: '@000', style_class: 'swatch-label' });
65+
66+
// center alignment container
67+
this.inner = new St.BoxLayout({ style_class: 'swatch-content', vertical: false, x_align: St.Align.MIDDLE });
68+
if (this.flag) this.inner.add_actor(this.flag);
69+
this.inner.add_actor(this.label);
70+
71+
this.bg.add_actor(this.inner);
72+
73+
// now that label and content exist, apply styles
74+
this._applyStyles();
75+
76+
this.wrapper = new St.BoxLayout({ vertical: false });
77+
this.wrapper.add_actor(this.bg);
78+
this.container.add_actor(this.wrapper);
79+
this.setContent(this.container);
80+
81+
},
82+
83+
_applyStyles: function() {
84+
if (!this.label) {
85+
return;
86+
}
87+
88+
// Make outer container and wrapper transparent so only the pill shows
89+
try {
90+
this.container.set_style('background: transparent; padding: 0;');
91+
if (this.wrapper) this.wrapper.set_style('background: transparent; padding: 0; align-items: center;');
92+
} catch (e) {}
93+
94+
// Inline style for the pill background
95+
const rgba = this._colorToRgba(this.bgColor, this.bgOpacity);
96+
const pillStyle = 'background-color: ' + rgba + '; border-radius: 40px; padding: 8px 20px; box-shadow: 0 8px 20px rgba(0,0,0,0.6); display: flex; align-items: center;';
97+
this.bg.set_style(pillStyle);
98+
let margin = Math.round(this.fontSize * 0.4);
99+
this.label.set_style(
100+
'color: ' + this.fontColor + ';' +
101+
'font-size: ' + Math.round(this.fontSize) + 'px;' +
102+
'font-weight: 400;' +
103+
'margin-left: ' + margin + 'px;' +
104+
'margin-right: ' + margin + 'px;'
105+
);
106+
},
107+
108+
_colorToRgba: function(color, alpha) {
109+
let r = 0, g = 0, b = 0;
110+
if (color.startsWith('#')) {
111+
// Parse hex (e.g., #rrggbb or #rrggbbaa, ignore alpha if present)
112+
color = color.replace('#', '');
113+
if (color.length >= 6) {
114+
r = parseInt(color.substring(0, 2), 16);
115+
g = parseInt(color.substring(2, 4), 16);
116+
b = parseInt(color.substring(4, 6), 16);
117+
}
118+
} else {
119+
// Parse rgb(r,g,b) or rgba(r,g,b,a)
120+
const match = color.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/);
121+
if (match) {
122+
r = parseInt(match[1], 10);
123+
g = parseInt(match[2], 10);
124+
b = parseInt(match[3], 10);
125+
// We ignore the stored alpha and use the passed alpha
126+
}
127+
}
128+
// Fallback to black if parsing fails
129+
if (isNaN(r) || isNaN(g) || isNaN(b)) {
130+
return 'rgba(0,0,0,' + alpha + ')';
131+
}
132+
return 'rgba(' + r + ',' + g + ',' + b + ',' + alpha + ')';
133+
},
134+
135+
_startTimer: function() {
136+
if (this._timeout) Mainloop.source_remove(this._timeout);
137+
this._update();
138+
// update every 1 second
139+
this._timeout = Mainloop.timeout_add_seconds(1, () => {
140+
this._update();
141+
return true; // repeat
142+
});
143+
},
144+
145+
_update: function() {
146+
const beats = this._calculateSwatchTime(new Date());
147+
let beatInteger = Math.floor(beats); // 0 to 999
148+
let text;
149+
if (this.showCentibeats) {
150+
let paddedInteger = String(beatInteger).padStart(3, '0');
151+
let fractional = (beats - beatInteger).toFixed(2).substring(1);
152+
text = `@${paddedInteger}${fractional}`;
153+
} else {
154+
// Always 3 digits with leading zeros (e.g. @000)
155+
text = '@' + String(beatInteger).padStart(3, '0');
156+
}
157+
if (this.label && typeof this.label.set_text === 'function') {
158+
this.label.set_text(text);
159+
}
160+
return true;
161+
},
162+
163+
_calculateSwatchTime: function(date) {
164+
// Calculate beats using Biel Mean Time (UTC+1)
165+
const utcHours = date.getUTCHours();
166+
const utcMinutes = date.getUTCMinutes();
167+
const utcSeconds = date.getUTCSeconds();
168+
const utcMilliseconds = date.getUTCMilliseconds();
169+
// Convert to BMT (UTC+1)
170+
const bmtHours = (utcHours + 1) % 24;
171+
// total seconds since midnight BMT
172+
const totalSeconds = (bmtHours * 3600) + (utcMinutes * 60) + utcSeconds + (utcMilliseconds / 1000);
173+
const beats = (totalSeconds / 86.4) % 1000; // 86400 / 1000 = 86.4
174+
return beats;
175+
},
176+
177+
178+
179+
_onSettingsChanged: function() {
180+
// Handle logo show/hide
181+
if (this.showLogo && !this.flag) {
182+
try {
183+
let flagPath = DESKLET_ROOT + '/icon.png';
184+
let file = Gio.file_new_for_path(flagPath);
185+
if (file.query_exists(null)) {
186+
let gicon = new Gio.FileIcon({ file: file });
187+
this.flag = new St.Icon({ gicon: gicon, icon_size: Math.round(this.fontSize * 0.9) });
188+
this.inner.insert_actor(this.flag, 0);
189+
}
190+
} catch (e) {}
191+
} else if (!this.showLogo && this.flag) {
192+
this.inner.remove_actor(this.flag);
193+
this.flag = null;
194+
}
195+
// Update icon size if font size changed
196+
if (this.flag) {
197+
this.flag.set_icon_size(Math.round(this.fontSize * 0.9));
198+
}
199+
this._applyStyles();
200+
this._update();
201+
},
202+
203+
on_desklet_removed: function() {
204+
if (this._timeout) Mainloop.source_remove(this._timeout);
205+
}
206+
};
207+
208+
function main(metadata, desklet_id) {
209+
return new MyDesklet(metadata, desklet_id);
210+
}
608 Bytes
Loading
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
{
2+
"uuid": "swatchtime@kdawson",
3+
"name": "Swatch Internet Time",
4+
"description": "A desklet which displays the Swatch Internet Time on your desktop",
5+
"website": "https://github.com/swatchtime/cinnamon-desklet",
6+
"version": "1.0",
7+
"max-instances": "10",
8+
"settings-schema": "settings-schema.json",
9+
"prevent-decorations": true,
10+
"author": "Ken Dawson (kendawson-online)",
11+
"cinnamon-version": "5.0",
12+
"license": "MIT"
13+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
# SWATCH INTERNET TIME
2+
# This file is put in the public domain.
3+
# kendawson-online, 2025
4+
#
5+
#, fuzzy
6+
msgid ""
7+
msgstr ""
8+
"Project-Id-Version: swatchtime@kdawson 1.0\n"
9+
"Report-Msgid-Bugs-To: https://github.com/linuxmint/cinnamon-spices-desklets/"
10+
"issues\n"
11+
"POT-Creation-Date: 2025-11-24 19:22-0500\n"
12+
"PO-Revision-Date: \n"
13+
"Last-Translator: \n"
14+
"Language-Team: \n"
15+
"Language: \n"
16+
"MIME-Version: 1.0\n"
17+
"Content-Type: text/plain; charset=UTF-8\n"
18+
"Content-Transfer-Encoding: 8bit\n"
19+
20+
#. metadata.json->name
21+
msgid "Swatch Internet Time"
22+
msgstr ""
23+
24+
#. metadata.json->description
25+
msgid "A desklet which displays the Swatch Internet Time on your desktop"
26+
msgstr ""
27+
28+
#. settings-schema.json->show-centibeats->description
29+
msgid "Show centibeats"
30+
msgstr ""
31+
32+
#. settings-schema.json->show-centibeats->tooltip
33+
msgid "Show centibeats (e.g. @123.45 instead of @123)"
34+
msgstr ""
35+
36+
#. settings-schema.json->show-logo->description
37+
#. settings-schema.json->show-logo->tooltip
38+
msgid "Show logo"
39+
msgstr ""
40+
41+
#. settings-schema.json->bg-color->description
42+
#. settings-schema.json->bg-color->tooltip
43+
msgid "Background color"
44+
msgstr ""
45+
46+
#. settings-schema.json->font-color->description
47+
#. settings-schema.json->font-color->tooltip
48+
msgid "Font color"
49+
msgstr ""
50+
51+
#. settings-schema.json->bg-opacity->description
52+
#. settings-schema.json->bg-opacity->tooltip
53+
msgid "Background opacity"
54+
msgstr ""
55+
56+
#. settings-schema.json->font-size->description
57+
#. settings-schema.json->font-size->tooltip
58+
msgid "Font size (px)"
59+
msgstr ""
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
{
2+
"show-centibeats": {
3+
"type": "switch",
4+
"default": false,
5+
"description": "Show centibeats",
6+
"tooltip": "Show centibeats (e.g. @123.45 instead of @123)"
7+
},
8+
"show-logo": {
9+
"type": "switch",
10+
"default": true,
11+
"description": "Show logo",
12+
"tooltip": "Show logo"
13+
},
14+
"bg-color": {
15+
"type": "colorchooser",
16+
"default": "rgb(0,0,0)",
17+
"description": "Background color",
18+
"tooltip": "Background color"
19+
},
20+
"font-color": {
21+
"type": "colorchooser",
22+
"default": "rgb(255,255,255)",
23+
"description": "Font color",
24+
"tooltip": "Font color"
25+
},
26+
"bg-opacity": {
27+
"type": "scale",
28+
"default": 0.5,
29+
"min": 0.0,
30+
"max": 1.0,
31+
"step": 0.01,
32+
"description": "Background opacity",
33+
"tooltip": "Background opacity"
34+
},
35+
"font-size": {
36+
"type": "scale",
37+
"default": 28,
38+
"min": 12,
39+
"max": 256,
40+
"step": 1,
41+
"description": "Font size (px)",
42+
"tooltip": "Font size (px)"
43+
}
44+
}

swatchtime@kdawson/info.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"author":"kendawson-online"
3+
}

swatchtime@kdawson/screenshot.png

124 KB
Loading

0 commit comments

Comments
 (0)