# Copyright (c) 2017 Zordsdavini
#
# 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.
from datetime import datetime, timedelta
from time import time
from libqtile.command.base import expose_command
from libqtile.utils import send_notification
from libqtile.widget import base
[docs]class Pomodoro(base.ThreadPoolText):
"""Pomodoro technique widget"""
defaults = [
("num_pomodori", 4, "Number of pomodori to do in a cycle"),
("length_pomodori", 25, "Length of one pomodori in minutes"),
("length_short_break", 5, "Length of a short break in minutes"),
("length_long_break", 15, "Length of a long break in minutes"),
("color_inactive", "ff0000", "Colour then pomodoro is inactive"),
("color_active", "00ff00", "Colour then pomodoro is running"),
("color_break", "ffff00", "Colour then it is break time"),
("notification_on", True, "Turn notifications on"),
("prefix_inactive", "POMODORO", "Prefix when app is inactive"),
("prefix_active", "", "Prefix then app is active"),
("prefix_break", "B ", "Prefix during short break"),
("prefix_long_break", "LB ", "Prefix during long break"),
("prefix_paused", "PAUSE", "Prefix during pause"),
(
"update_interval",
1,
"Update interval in seconds, if none, the "
"widget updates whenever the event loop is idle.",
),
]
STATUS_START = "start"
STATUS_INACTIVE = "inactive"
STATUS_ACTIVE = "active"
STATUS_BREAK = "break"
STATUS_LONG_BREAK = "long_break"
STATUS_PAUSED = "paused"
status = "inactive"
paused_status = None
end_time = datetime.now()
time_left = None
pomodoros = 1
def __init__(self, **config):
base.ThreadPoolText.__init__(self, "", **config)
self.add_defaults(Pomodoro.defaults)
self.prefix = {
"inactive": self.prefix_inactive,
"active": self.prefix_active,
"break": self.prefix_break,
"long_break": self.prefix_long_break,
"paused": self.prefix_paused,
}
self.add_callbacks(
{
"Button1": self.toggle_break,
"Button3": self.toggle_active,
}
)
def tick(self):
self.update(self.poll())
return self.update_interval - time() % self.update_interval
def _update(self):
if self.status in [self.STATUS_INACTIVE, self.STATUS_PAUSED]:
return
if self.end_time > datetime.now() and self.status != self.STATUS_START:
return
if self.status == self.STATUS_ACTIVE and self.pomodoros == self.num_pomodori:
self.status = self.STATUS_LONG_BREAK
self.end_time = datetime.now() + timedelta(minutes=self.length_long_break)
self.pomodoros = 1
if self.notification_on:
self._send_notification(
"normal",
"Please take a long break! End Time: " + self.end_time.strftime("%H:%M"),
)
return
if self.status == self.STATUS_ACTIVE:
self.status = self.STATUS_BREAK
self.end_time = datetime.now() + timedelta(minutes=self.length_short_break)
self.pomodoros += 1
if self.notification_on:
self._send_notification(
"normal",
"Please take a short break! End Time: " + self.end_time.strftime("%H:%M"),
)
return
self.status = self.STATUS_ACTIVE
self.end_time = datetime.now() + timedelta(minutes=self.length_pomodori)
if self.notification_on:
self._send_notification(
"critical",
"Please start with the next Pomodori! End Time: "
+ self.end_time.strftime("%H:%M"),
)
return
def _get_text(self):
self._update()
if self.status in [self.STATUS_INACTIVE, self.STATUS_PAUSED]:
self.layout.colour = self.color_inactive
return self.prefix[self.status]
time_left = self.end_time - datetime.now()
if self.status == self.STATUS_ACTIVE:
self.layout.colour = self.color_active
else:
self.layout.colour = self.color_break
time_string = f"{time_left.seconds // 3600:d}:{time_left.seconds % 3600 // 60:02d}:{time_left.seconds % 60:02d}"
return self.prefix[self.status] + time_string
[docs] @expose_command()
def toggle_break(self):
if self.status == self.STATUS_INACTIVE:
self.status = self.STATUS_START
return
if self.paused_status is None:
self.paused_status = self.status
self.time_left = self.end_time - datetime.now()
self.status = self.STATUS_PAUSED
if self.notification_on:
self._send_notification("low", "Pomodoro has been paused")
else:
self.status = self.paused_status
self.paused_status = None
self.end_time = self.time_left + datetime.now()
if self.notification_on:
if self.status == self.STATUS_ACTIVE:
status = "Pomodoro"
else:
status = "break"
self._send_notification(
"normal",
f"Please continue on {status}! End Time: " + self.end_time.strftime("%H:%M"),
)
[docs] @expose_command()
def toggle_active(self):
if self.status != self.STATUS_INACTIVE:
self.status = self.STATUS_INACTIVE
if self.notification_on:
self._send_notification("critical", "Pomodoro has been suspended")
else:
self.status = self.STATUS_START
def _send_notification(self, urgent, message):
send_notification("Pomodoro", message, urgent=urgent)
def poll(self):
return self._get_text()