Skip to content

Instantly share code, notes, and snippets.

@cat-in-136
Last active April 18, 2021 15:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save cat-in-136/64759185fc206fa4c8cdf49ec4de361e to your computer and use it in GitHub Desktop.
Save cat-in-136/64759185fc206fa4c8cdf49ec4de361e to your computer and use it in GitHub Desktop.
Clock using Clutter + GJS
#!/bin/env gjs
/* Copyright (c) 2021 @cat_in_136
*
* Permission is hereby granted, free of charge, to any person
* obtaining a copy of this software and associated documentation
* files (the "Software"), to deal in the Software without
* restriction, including without limitation the rights to use,
* copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the
* Software is furnished to do so, subject to the following
* conditions:
*
* The above copyright notice and this permission notice shall be
* included in all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
* EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
* OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
* NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
* HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
* FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
* OTHER DEALINGS IN THE SOFTWARE.
*/
const Clutter = imports.gi.Clutter;
const GLib = imports.gi.GLib;
const Mainloop = imports.mainloop;
const TIMES = ARGV.map(text => {
if (text === "now") {
return {text, date: GLib.DateTime.new_now_local() };
}
if (/^([0-9]{1,2}):([0-9]{1,2})(:([0-9]{1,2}))?$/.test(text)) {
const date = new Date();
date.setHours(RegExp.$1 | 0, RegExp.$2 | 0, RegExp.$4 | 0);
return {text, date: GLib.DateTime.new_from_unix_local(date.getTime() / 1000.0)};
}
if (!!Date.parse(text)) {
return {text, date: GLib.DateTime.new_from_unix_local(Date.parse(text))};
}
throw new Error(`Error: Could not parse as date string: ${text}`);
});
Clutter.init(null);
const stage = new Clutter.Stage({
title: "djclock",
layout_manager: new Clutter.BoxLayout(),
user_resizable: true,
x_expand: true,
y_expand: true,
background_color: Clutter.Color.from_string("#000")[1],
});
stage.set_size(256, (1+TIMES.length)*24);
stage.connect("destroy", () => Clutter.main_quit());
const box = new Clutter.Box({
layout_manager: new Clutter.BoxLayout({
orientation: Clutter.Orientation.VERTICAL,
spacing: 2,
}),
x_expand: true,
});
stage.add_child(box);
const time_current = new Clutter.Text({
x_expand: true,
text: '...',
color: Clutter.Color.from_string("#fff")[1],
});
box.add_child(time_current);
const times_dj = TIMES.map(v => {
const time = new Clutter.Text({
x_expand: true,
color: Clutter.Color.from_string("#fff")[1],
background_color: Clutter.Color.from_string("#000")[1],
});
if (typeof(v.text) === "string") {
time.set_text(v.text);
}
box.add_child(time);
return time;
});
const pt_60sec_before = new Clutter.PropertyTransition({ property_name: 'background-color' });
pt_60sec_before.set_from(Clutter.Color.from_string("#000")[1]);
pt_60sec_before.set_from(Clutter.Color.from_string("#800")[1]);
pt_60sec_before.set_progress_mode(Clutter.AnimationMode.LINEAR);
const tg_60sec_before = new Clutter.TransitionGroup();
tg_60sec_before.set_duration(1000);
tg_60sec_before.set_repeat_count(-1);
tg_60sec_before.add_transition(pt_60sec_before);
const TG_NAME_60SEC_BEFORE = "60sec-before";
const pt_30sec_before = new Clutter.PropertyTransition({ property_name: 'background-color' });
pt_30sec_before.set_from(Clutter.Color.from_string("#000")[1]);
pt_30sec_before.set_from(Clutter.Color.from_string("#f00")[1]);
pt_30sec_before.set_progress_mode(Clutter.AnimationMode.LINEAR);
const tg_30sec_before = new Clutter.TransitionGroup();
tg_30sec_before.set_duration(500);
tg_30sec_before.set_repeat_count(-1);
tg_30sec_before.add_transition(pt_30sec_before);
const TG_NAME_30SEC_BEFORE = "30sec-before";
function doTick() {
const date_format = "%F %H:%M:%S.%f %Z";
const now = GLib.DateTime.new_now_local();
time_current.set_text(now.format(date_format));
for (const i in TIMES) {
if (TIMES[i].date) {
const diff = now.difference(TIMES[i].date);
const microseconds = ((Math.abs(diff) / 1) | 0)% 1000000;
const seconds = ((Math.abs(diff) / 1000000) | 0) % 60;
const minutes = ((Math.abs(diff) / 60000000) | 0) % 60;
const hours = ((Math.abs(diff) / 3600000000) | 0) % 24;
const days = ((Math.abs(diff) / 86400000000) | 0);
const text = [
(diff < 0)? "-" : "+",
(days != 0)? `${days} ` : "",
`${hours}`.padStart(2, "0"),
":",
`${minutes}`.padStart(2, "0"),
":",
`${seconds}`.padStart(2, "0"),
".",
`${microseconds}`.padStart(6, "0"),
].join("");
times_dj[i].set_text(text);
if ((diff <= -30000000) && (diff > -60000000)) {
if (TIMES[i].transition !== TG_NAME_60SEC_BEFORE) {
times_dj[i].remove_all_transitions();
times_dj[i].add_transition(TG_NAME_60SEC_BEFORE, tg_60sec_before);
TIMES[i].transition = TG_NAME_60SEC_BEFORE;
}
} else if ((diff <= 0) && (diff > -30000000)) {
if (TIMES[i].transition !== TG_NAME_30SEC_BEFORE) {
times_dj[i].remove_all_transitions();
times_dj[i].add_transition(TG_NAME_30SEC_BEFORE, tg_30sec_before);
TIMES[i].transition = TG_NAME_30SEC_BEFORE;
}
} else {
if (TIMES[i].transition) {
times_dj[i].remove_all_transitions();
delete TIMES[i].transition;
times_dj[i].set_background_color(Clutter.Color.from_string("#000")[1]);
}
}
} else {
times_dj[i].set_text(`${TIMES[i].text}`);
}
}
Mainloop.timeout_add(50, () => doTick());
}
Mainloop.idle_add(() => doTick());
stage.show();
Clutter.main();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment