taper/taper@vgmlr/applet.js (7.1 kb)
Modified: 02:37:00 66 026 (22 May 026) - 2 Days Ago
Download
// taper
// linux session timer and alert
// vgmlr.com/mlwrk

const Main = imports.ui.main;
const Applet = imports.ui.applet;
const GLib = imports.gi.GLib;
const Gio = imports.gi.Gio;
const PopupMenu = imports.ui.popupMenu;
const Settings = imports.ui.settings;

class SessionTimerApplet extends Applet.TextIconApplet {
    constructor(metadata, orientation, panel_height, instance_id) {
        super(orientation, panel_height, instance_id);
        
        this.seconds = 0;
        this._currentInterval = 0;
        
        try {
            this.set_applet_label("00:00:00");
            this._buildMenu(orientation);

            this.settings = new Settings.AppletSettings(this, metadata.uuid, instance_id);
            this.settings.bindProperty(Settings.BindingDirection.BIDIRECTIONAL, "history", "history", this._updateHistoryMenu, null);
            this.settings.bindProperty(Settings.BindingDirection.BIDIRECTIONAL, "last_recorded_time", "last_recorded_time", null, null);
            this.settings.bindProperty(Settings.BindingDirection.BIDIRECTIONAL, "session_start_timestamp", "session_start_timestamp", null, null);

            if (this.last_recorded_time > 0 && this.session_start_timestamp > 0) {
                this._archiveSession(this.session_start_timestamp, this.last_recorded_time);
            }

            this._startNewSession();
            this._setupDBus();
            
        } catch (e) {
            global.logError("Taper Error: " + e);
        }
    }

    _buildMenu(orientation) {
        this.menuManager = new PopupMenu.PopupMenuManager(this);
        this.menu = new Applet.AppletPopupMenu(this, orientation);
        this.menuManager.addMenu(this.menu);

        this._intervalSection = new PopupMenu.PopupMenuSection();
        this.menu.addMenuItem(this._intervalSection);
        this.menu.addMenuItem(new PopupMenu.PopupSeparatorMenuItem());
        
        this._historySection = new PopupMenu.PopupMenuSection();
        this.menu.addMenuItem(this._historySection);
    }

    _startNewSession() {
        this.seconds = 0;
        this.session_start_timestamp = Date.now();
        this.last_recorded_time = 0;
        
        this.settings.setValue("session_start_timestamp", this.session_start_timestamp);
        this.settings.setValue("last_recorded_time", 0);
        
        this._updateLabel();
        this._updateTooltip("No Alert");
        this._updateHistoryMenu();
        this._updateIntervalMenu();

        if (!this._timerId) {
            this._timerId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 1, () => this._onTick());
        }
    }

    _onTick() {
        this.seconds++;
        this._updateLabel();

        if (this.seconds % 10 === 0) {
            this.last_recorded_time = this.seconds;
            this.settings.setValue("last_recorded_time", this.seconds);
        }

        if (this._currentInterval > 0 && this.seconds % (this._currentInterval * 60) === 0) {
            Main.notify("Taper Session", `${this._currentInterval} Minutes Elapsed`);
        }

        return true;
    }

    _archiveSession(timestamp, durationSeconds) {
        let h = Math.floor(durationSeconds / 3600);
        let m = Math.floor((durationSeconds % 3600) / 60);
        let s = durationSeconds % 60;
        let durationStr = h.toString().padStart(2, '0') + ":" + m.toString().padStart(2, '0') + ":" + s.toString().padStart(2, '0');

        let startDate = new Date(timestamp);
        let currentYear = startDate.getFullYear();
        let march20Current = new Date(currentYear, 2, 20);
        let newYearDate = (startDate < march20Current) ? new Date(currentYear - 1, 2, 20) : march20Current;
        let daysSince = Math.floor((startDate.getTime() - newYearDate.getTime()) / (1000 * 60 * 60 * 24)) + 1;
        
        const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
        let hh = startDate.getHours().toString().padStart(2, '0');
        let mm = startDate.getMinutes().toString().padStart(2, '0');
        let ddMon = startDate.getDate().toString().padStart(2, '0') + " " + months[startDate.getMonth()];
        
        let text = hh + ":" + mm + " " + daysSince + " (" + ddMon + ") " + durationStr;
        
        let tempHistory = [...this.history];
        tempHistory.push(text);
        if (tempHistory.length > 5) tempHistory.shift();
        
        this.history = tempHistory;
        this.settings.setValue("history", tempHistory);
    }

    _updateHistoryMenu() {
        if (!this._historySection) return;
        this._historySection.removeAll();
        this.history.forEach(time => {
            this._historySection.addMenuItem(new PopupMenu.PopupMenuItem(time, { reactive: false }));
        });
    }

    _updateLabel() {
        let h = Math.floor(this.seconds / 3600);
        let m = Math.floor((this.seconds % 3600) / 60);
        let s = this.seconds % 60;
        let text = h.toString().padStart(2, '0') + ":" + m.toString().padStart(2, '0') + ":" + s.toString().padStart(2, '0');
        this.set_applet_label(text);
    }

    _updateIntervalMenu() {
        if (!this._intervalSection) return;
        this._intervalSection.removeAll();
        const options = [{l: "No Alert", v: 0}, {l: "15 Minutes", v: 15}, {l: "30 Minutes", v: 30}, {l: "60 Minutes", v: 60}];        
        options.forEach(opt => {
            let item = new PopupMenu.PopupMenuItem(opt.l);
            if (this._currentInterval === opt.v) {
                item.setShowDot(true);
                item.label.set_style("font-weight: bold;");
                this._updateTooltip(opt.v === 0 ? "No Alert" : opt.l);
            }
            item.connect('activate', () => {
                this._currentInterval = opt.v;
                this._updateIntervalMenu();
            });
            this._intervalSection.addMenuItem(item);
        });
    }

    _updateTooltip(intervalText) {
        this.set_applet_tooltip(`Taper: ${intervalText}`);
    }

    _setupDBus() {
        try {
            let bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, null);
            this._dbusId = bus.signal_subscribe(
                null, null, 'PrepareForSleep', null, null, Gio.DBusSignalFlags.NONE,
                (conn, sender, obj, iface, sig, params) => {
                    let sleeping = params.get_child_value(0).unpack();
                    if (sleeping) {
                        if (this.seconds > 0) {
                            this._archiveSession(this.session_start_timestamp, this.seconds);
                        }
                    } else {
                        this._startNewSession();
                    }
                }
            );
        } catch (e) {
            global.logError("D-Bus Error: " + e);
        }
    }

    on_applet_clicked() {
        this.menu.toggle();
    }

    on_applet_removed_from_panel() {
        if (this._timerId) GLib.source_remove(this._timerId);
        if (this._dbusId) {
            let bus = Gio.bus_get_sync(Gio.BusType.SYSTEM, null);
            bus.signal_unsubscribe(this._dbusId);
        }
    }
}

function main(metadata, orientation, panel_height, instance_id) {
    return new SessionTimerApplet(metadata, orientation, panel_height, instance_id);
}