--- /dev/null
+[submodule "LunarCalendarPy"]
+ path = LunarCalendarPy
+ url = git://adrianiainlam.tk/LunarCalendarPy.git
--- /dev/null
+Subproject commit e0750e842201b71c37fdb55a6d6f84bfb39d2c5f
An application indicator for Unity that displays the current date and time
in lunar calendar.
-Copyright (c) 2016 Adrian I Lam <adrianiainlam@gmail.com>
+Copyright (c) 2016-2019 Adrian I Lam <spam@adrianiainlam.tk> s/spam/me/
*Not to be confused with [indicator-lunar][1], which shows attributes and
ephemerides for astronomical objects.*
## Dependencies
- - [Node.js][2]
-
- Note: node-gtk, one of this program's dependencies, requires nodejs version
- 5 or above.
-
- [2]: https://nodejs.org/en/
-
- - [node-gtk][3] (by @WebReflection)
-
- npm package: https://www.npmjs.com/package/node-gtk
-
- Dependencies: build-essential, git, nodejs (>= 5), gobject-introspection,
- libgirepository1.0-dev
-
- Note: This package failed to build for me. I had to remove `-Werror` from
- `cflags` in file "bindings.gyp" to get it to build.
-
- [3]: https://github.com/WebReflection/node-gtk
-
- - [lunar-calendar-zh][4] (by @roadmanfong)
-
- npm package: https://www.npmjs.com/package/lunar-calendar-zh
-
- Note: This package contains a bug which renders it useless if your computer
- is set to a time zone which observes Daylight Saving. I have forked it and
- fixed it in <https://github.com/adrianiainlam/LunarCalendar>.
-
- [4]: https://github.com/roadmanfong/LunarCalendar
-
- - [node-cron (cron)][5] (by @ncb000gt)
-
- npm package: https://www.npmjs.com/package/cron
-
- [5]: https://github.com/ncb000gt/node-cron
-
- - [node-dbus (dbus-native)][6] (by @sidorares)
-
- npm package: https://www.npmjs.com/package/dbus-native
-
- [6]: https://github.com/sidorares/node-dbus
+ - Python 3
+
+ - [LunarCalendarPy][lcp] (included as submodule here)
+
+ Translated from the JavaScript [LunarCalendar][lc] library by GitHub user
+ @zzyss86.
+
+ [lc]: https://github.com/zzyss86/LunarCalendar
+ [lcp]: https://adrianiainlam.tk/git/?p=LunarCalendarPy.git;a=summary
+
+ - [schedule][schedule]
+
+ Used for periodic update of the indicator.
+
+ [schedule]: https://pypi.org/project/schedule/
+
+ - [dbus-python][dbus]
+
+ Detects suspends/hibernates which would cause incorrect timings
+ used by schedule.
+
+ [dbus]: https://pypi.org/project/dbus-python/
+
+This indicator used to be written in JavaScript (node.js) using the
+node-gtk package, but it was eventually abandoned, got replaced,
+the replacement was abandoned, etc. The situation was a bit too messy
+for me so I decided to just rewrite the whole thing in Python, which
+would also make installation easier for most standard Ubuntus, and
+would use less RAM.
+
## Usage
- 1. Install the dependencies listed above.
- 2. Clone this repository.
+ 1. Install schedule and dbus-python (`pip install schedule dbus-python`).
+ 2. Clone this repository (`git clone --recurse-submodules git://adrianiainlam.tk/indicator-lunar-calendar.git`).
3. Add the script as a startup application.
4. Run the script manually for the first time. (Alternatively, log out
and log in again.)
## License
This program is released under the MIT License. For the full text of this
-license, please refer to the file "indicator-lunar-calendar.js".
+license, please refer to the file "indicator-lunar-calendar.py".
+++ /dev/null
-#!/usr/bin/env node
-/*
- * indicator-lunar-calendar - shows lunar calendar information
- * Copyright (c) 2016 Adrian I Lam <adrianiainlam@gmail.com>
- *
- * 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 AUTHOR 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.
- */
-
-/* import dependencies */
-const gi = require('node-gtk');
-const Gtk = gi.require('Gtk', '3.0');
-const AppIndicator3 = gi.require('AppIndicator3');
-var CronJob = require('cron').CronJob;
-var LunarCalendar = require('lunar-calendar-zh');
-var DBus = require('dbus-native');
-
-/* setup indicator object */
-gi.startLoop();
-Gtk.init();
-var indicator = AppIndicator3.Indicator.new(
- "lunar-indicator",
- __dirname + '/icons/鼠.svg',
- AppIndicator3.IndicatorCategory.APPLICATION_STATUS
-);
-indicator.setStatus(AppIndicator3.IndicatorStatus.ACTIVE);
-var menu = new Gtk.Menu();
-var item = new Gtk.MenuItem();
-menu.append(item);
-indicator.setMenu(menu);
-menu.showAll();
-
-function update_indicator() {
- /* get current time at UTC+8, add 1 to date if after 23:00 (子時) */
- var now = new Date(new Date().getTime() + 8 * 3600 * 1000);
- var hour = now.getUTCHours();
- if(hour >= 23) { // 子時 of the next day
- now = new Date(now.getTime() + 24 * 3600 * 1000);
- }
- var year = now.getUTCFullYear();
- var month = now.getUTCMonth() + 1;
- var day = now.getUTCDate();
-
- /* obtain date/time in lunar calendar */
- var lunar = LunarCalendar.solarToLunar(year, month, day);
- lunar.hour = '子丑寅卯辰巳午未申酉戌亥'[Math.floor((hour + 1) % 24 / 2)];
-
- /* output formatting */
- var compact_date = lunar.lunarMonthName + lunar.lunarDayName;
- var long_date = lunar.GanZhiYear + '年(' + lunar.zodiac + '年)\n' +
- lunar.lunarMonthName + lunar.lunarDayName;
- if(lunar.term) { // add solar term (節氣) to output if at solar term
- compact_date += ' ' + lunar.term;
- long_date += ' ' + lunar.term;
- }
- long_date += '\n' + lunar.hour + '時';
-
- /* output to indicator */
- indicator.setIcon(__dirname + '/icons/' + lunar.zodiac + '.svg');
- indicator.setLabel(compact_date, '');
- item.setLabel(long_date);
-
- console.log('Indicator updated. ' + lunar.hour + ' Time: ' + new Date());
- /* DO NOT REMOVE THE ABOVE LINE.
- * I have no absolutely no idea why but the indicator doesn't get
- * updated if this line is removed.
- * It wasn't needed though before implementing the DBus section below.
- * So perhaps some kind of conflict between the two?
- */
-}
-
-var job = new CronJob({
- cronTime: '0 * * * *', // every hour
- onTick: update_indicator,
- start: true
-});
-
-update_indicator();
-
-/* Detect resume from suspend and update date/time */
-var bus = DBus.systemBus();
-var service = bus.getService('org.freedesktop.login1');
-service.getInterface(
- '/org/freedesktop/login1',
- 'org.freedesktop.login1.Manager',
- function(err, nm) {
- nm.addListener('PrepareForSleep', function(arg) {
- // PrepareForSleep returns false when resuming from suspend
- if(!arg) {
- job.stop(); // force cronjob to recalculate time
- job.start();
- update_indicator();
- }
- });
- }
-);
-
-Gtk.main();
--- /dev/null
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+#
+# indicator-lunar-calendar - shows lunar calendar information
+# Copyright (c) 2019 Adrian I Lam <spam@adrianiainlam.tk> s/spam/me/
+#
+# 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 AUTHOR 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.
+#
+# I would like to thank Tobias Schlitt <toby@php.net>, who wrote
+# indicator-chars <https://github.com/tobyS/indicator-chars> which I used
+# as a reference when writing this software.
+
+import signal
+import datetime
+import os
+import gi
+gi.require_version('Gdk', '3.0')
+gi.require_version('Gtk', '3.0')
+gi.require_version('AppIndicator3', '0.1')
+from gi.repository import Gdk, Gtk, AppIndicator3
+from LunarCalendarPy.LunarCalendar import LunarCalendar
+import schedule
+import threading
+import time
+import dbus
+from dbus.mainloop.glib import DBusGMainLoop
+
+APP_NAME = 'indicator-lunar-calendar-py'
+APP_VERSION = '1.2+py'
+
+SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
+
+
+class IndicatorLunarCalendar:
+ def __init__(self):
+ self.indicator = AppIndicator3.Indicator.new(
+ 'lunar-indicator',
+ os.path.join(SCRIPT_DIR, 'icons', '鼠.svg'),
+ AppIndicator3.IndicatorCategory.APPLICATION_STATUS)
+ self.indicator.set_status(AppIndicator3.IndicatorStatus.ACTIVE)
+
+ self.menu = Gtk.Menu()
+ self.item = Gtk.MenuItem()
+ self.item.connect("activate", self.do_nothing)
+ self.menu.append(self.item)
+ self.indicator.set_menu(self.menu)
+ self.menu.show_all()
+
+ self.lc = LunarCalendar()
+ self.update_indicator()
+
+ def update_indicator(self):
+ # get current time at UTC+8, add 1 to date if after 23:00 (子時)
+ now = datetime.datetime.utcnow() + datetime.timedelta(hours=8)
+ hour = now.hour
+ if hour >= 23:
+ now = now.date() + datetime.timedelta(days=1)
+
+ lunar = self.lc.solarToLunar(now.year, now.month, now.day)
+ lunar['hour'] = '子丑寅卯辰巳午未申酉戌亥'[(hour + 1) % 24 // 2]
+
+ compact_date = lunar['lunarMonthName'] + lunar['lunarDayName']
+ long_date = (
+ lunar['GanZhiYear'] + '年(' + lunar['zodiac'] + '年)\n' +
+ lunar['lunarMonthName'] + lunar['lunarDayName']
+ )
+ if lunar['term']:
+ compact_date += ' ' + lunar['term']
+ long_date += ' ' + lunar['term']
+ long_date += '\n' + lunar['hour'] + '時'
+
+ self.indicator.set_icon(
+ os.path.join(SCRIPT_DIR, 'icons', lunar['zodiac'] + '.svg')
+ )
+ self.indicator.set_label(compact_date, '')
+ self.item.set_label(long_date)
+
+ def do_nothing(self, arg):
+ pass
+
+
+def run_schedule_one_iteration():
+ prev_idle_sec = 60 * 60
+ idle_sec = schedule.idle_seconds()
+ while idle_sec < prev_idle_sec:
+ prev_idle_sec = idle_sec
+
+ if idle_sec > 2:
+ time.sleep(idle_sec - 2)
+ elif idle_sec > 0.199:
+ time.sleep(idle_sec - 0.199)
+ else:
+ schedule.run_pending()
+
+ idle_sec = schedule.idle_seconds()
+
+
+def cronThreadBody():
+ while True:
+ run_schedule_one_iteration()
+
+
+def updateJob(i):
+ i.update_indicator()
+
+
+if __name__ == '__main__':
+ signal.signal(signal.SIGINT, lambda signum, frame: Gtk.main_quit())
+
+ i = IndicatorLunarCalendar()
+
+ schedule.every().hour.at(":00").do(updateJob, i=i)
+ cronThread = threading.Thread(target=cronThreadBody)
+ cronThread.start()
+
+ dbusloop = DBusGMainLoop()
+ bus = dbus.SystemBus(mainloop=dbusloop)
+ obj = bus.get_object('org.freedesktop.login1', '/org/freedesktop/login1')
+ iface = dbus.Interface(obj,
+ dbus_interface='org.freedesktop.login1.Manager')
+
+ def sleepHandler(arg):
+ if arg == 0:
+ i.update_indicator()
+ schedule.clear()
+ schedule.every().minute.at(":00").do(updateJob, i=i)
+ newThread = threading.Thread(target=run_schedule_one_iteration)
+ newThread.start()
+
+ iface.connect_to_signal('PrepareForSleep', sleepHandler)
+
+ Gtk.main()