Add Python version and remove JavaScript version
[indicator-lunar-calendar.git] / indicator-lunar-calendar.py
diff --git a/indicator-lunar-calendar.py b/indicator-lunar-calendar.py
new file mode 100755 (executable)
index 0000000..9f3ef01
--- /dev/null
@@ -0,0 +1,148 @@
+#!/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()