breaktime
This commit is contained in:
parent
7eb36b622b
commit
90a4a23bd4
|
|
@ -15,7 +15,7 @@ lib.mkIf config.u.has.graphical {
|
|||
"copyq.service"
|
||||
"fcitx5.service"
|
||||
"fastcompmgr.service"
|
||||
"safeeyes.service"
|
||||
# "safeeyes.service"
|
||||
"snixembed.service"
|
||||
];
|
||||
Requires = [
|
||||
|
|
|
|||
227
user/bin/breaktime
Executable file
227
user/bin/breaktime
Executable file
|
|
@ -0,0 +1,227 @@
|
|||
# vi: filetype=python
|
||||
import sys
|
||||
import subprocess
|
||||
from enum import Enum
|
||||
from threading import Thread
|
||||
from PyQt6.QtWidgets import (
|
||||
QApplication,
|
||||
QSystemTrayIcon,
|
||||
QMenu,
|
||||
QWidget,
|
||||
QVBoxLayout,
|
||||
QMessageBox,
|
||||
)
|
||||
from PyQt6.QtGui import QIcon, QPixmap, QColor, QAction, QPainter
|
||||
from PyQt6.QtCore import QTimer, Qt
|
||||
|
||||
|
||||
class State(Enum):
|
||||
IDLE = 0
|
||||
PENDING = 1
|
||||
BREAK = 2
|
||||
|
||||
|
||||
S = 1000
|
||||
M = 60 * S
|
||||
|
||||
DURATION = {
|
||||
State.IDLE: 15*M,
|
||||
State.PENDING: 1*M,
|
||||
State.BREAK: 15*S,
|
||||
}
|
||||
TOOLTIP = {
|
||||
State.IDLE: "Waiting for next break",
|
||||
State.PENDING: "Click to take a break",
|
||||
State.BREAK: "Taking a break",
|
||||
}
|
||||
COLOR = {
|
||||
State.IDLE: "#608c58",
|
||||
State.PENDING: "#d32a5d",
|
||||
State.BREAK: "#7e95d3",
|
||||
QWidget: "rgba(40, 20, 40, 200)",
|
||||
}
|
||||
DONE = '/run/current-system/sw/share/sounds/freedesktop/stereo/complete.oga'
|
||||
POP = '/run/current-system/sw/share/sounds/freedesktop/stereo/message.oga'
|
||||
audio = True
|
||||
|
||||
|
||||
def m_ceil(n):
|
||||
return (n + M - 1) // M
|
||||
|
||||
|
||||
def circle_icon(color, text=None):
|
||||
pixmap = QPixmap(64, 64)
|
||||
pixmap.fill(Qt.GlobalColor.transparent)
|
||||
|
||||
painter = QPainter(pixmap)
|
||||
painter.setRenderHint(QPainter.RenderHint.Antialiasing)
|
||||
|
||||
painter.setBrush(QColor(color))
|
||||
painter.setPen(Qt.PenStyle.NoPen)
|
||||
painter.drawEllipse(2, 2, 60, 60)
|
||||
|
||||
if text:
|
||||
painter.setPen(QColor("white" if color != "white" else "black"))
|
||||
font = painter.font()
|
||||
font.setPixelSize(34)
|
||||
font.setBold(True)
|
||||
painter.setFont(font)
|
||||
painter.drawText(pixmap.rect(), Qt.AlignmentFlag.AlignCenter, text)
|
||||
painter.end()
|
||||
return QIcon(pixmap)
|
||||
|
||||
|
||||
def play_sound(sound=DONE, n=1):
|
||||
global audio
|
||||
audio = True
|
||||
|
||||
def play():
|
||||
for _ in range(n):
|
||||
if audio:
|
||||
subprocess.call(['paplay', sound])
|
||||
Thread(target=play).start()
|
||||
|
||||
|
||||
class BreakTimer(QSystemTrayIcon):
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.state = State.IDLE
|
||||
|
||||
self.timer = QTimer(self)
|
||||
self.timer.timeout.connect(self.on_timeout)
|
||||
self.activated.connect(self.on_tray_click)
|
||||
self.icon_color = ""
|
||||
self.icon_text = ""
|
||||
self.color = ""
|
||||
self.text = ""
|
||||
self.icon_timer = QTimer(self)
|
||||
self.icon_timer.timeout.connect(self.update_ui)
|
||||
self.icon_timer.start(S)
|
||||
|
||||
self.overlay = TransparentOverlay()
|
||||
screen_geometry = app.primaryScreen().virtualGeometry()
|
||||
self.overlay.setGeometry(screen_geometry)
|
||||
|
||||
menu = QMenu()
|
||||
self.p_ac = QAction("Pause Timer", menu)
|
||||
self.p_ac.triggered.connect(self.pause_timers)
|
||||
menu.addAction(self.p_ac)
|
||||
b_ac = QAction("Break Now", menu)
|
||||
b_ac.triggered.connect(self.start_break)
|
||||
menu.addAction(b_ac)
|
||||
q_ac = QAction("Quit", menu)
|
||||
q_ac.triggered.connect(sys.exit)
|
||||
menu.addAction(q_ac)
|
||||
self.setContextMenu(menu)
|
||||
|
||||
self.start_idle()
|
||||
|
||||
def update_icon(self):
|
||||
if self.color != self.icon_color or self.text != self.icon_text:
|
||||
self.icon_color = self.color
|
||||
self.icon_text = self.text
|
||||
self.setIcon(circle_icon(self.color, self.text))
|
||||
|
||||
def update_ui(self):
|
||||
if self.state is State.IDLE:
|
||||
self.text = str(m_ceil(self.timer.remainingTime()) or self.text)
|
||||
self.update_icon()
|
||||
elif self.state is State.PENDING:
|
||||
if self.color == "white":
|
||||
self.color = COLOR[State.PENDING]
|
||||
else:
|
||||
self.color = "white"
|
||||
self.update_icon()
|
||||
|
||||
def set_state(self, state, text):
|
||||
global audio
|
||||
audio = False
|
||||
self.state = state
|
||||
self.color = COLOR[self.state]
|
||||
self.text = str(text)
|
||||
self.update_icon()
|
||||
self.setToolTip(TOOLTIP[self.state])
|
||||
self.timer.start(DURATION[self.state])
|
||||
|
||||
def start_idle(self):
|
||||
self.set_state(State.IDLE, DURATION[State.IDLE]//M)
|
||||
|
||||
def start_break(self):
|
||||
self.set_state(State.BREAK, "...")
|
||||
self.overlay.show()
|
||||
self.overlay.grabKeyboard()
|
||||
|
||||
def on_timeout(self):
|
||||
if self.state is State.IDLE:
|
||||
self.set_state(State.PENDING, 0)
|
||||
play_sound()
|
||||
elif self.state is State.PENDING:
|
||||
n = int(self.text or 0) + 1
|
||||
self.text = str(n)
|
||||
self.update_icon()
|
||||
if n < 5:
|
||||
play_sound(n=n)
|
||||
else:
|
||||
self.inactive_popup()
|
||||
elif self.state is State.BREAK:
|
||||
self.overlay.hide()
|
||||
self.start_idle()
|
||||
play_sound(POP)
|
||||
self.showMessage('Timer', 'Timer is complete')
|
||||
|
||||
def inactive_popup(self):
|
||||
self.pause_timers()
|
||||
popup = QMessageBox()
|
||||
popup.setWindowTitle("Break Timer")
|
||||
popup.setText("Timer paused due to inactivity")
|
||||
resume = popup.addButton("Resume", QMessageBox.ButtonRole.AcceptRole)
|
||||
resume.clicked.connect(self.start_idle)
|
||||
quit = popup.addButton("Quit", QMessageBox.ButtonRole.RejectRole)
|
||||
quit.clicked.connect(sys.exit)
|
||||
popup.exec()
|
||||
|
||||
def on_tray_click(self, reason):
|
||||
if reason == QSystemTrayIcon.ActivationReason.Trigger:
|
||||
if self.state is State.PENDING:
|
||||
self.start_break()
|
||||
|
||||
def pause_timers(self):
|
||||
if self.timer.isActive():
|
||||
self.remaining_time = self.timer.remainingTime()
|
||||
self.color = "gray"
|
||||
self.update_icon()
|
||||
self.timer.stop()
|
||||
self.icon_timer.stop()
|
||||
self.p_ac.setText("Resume Timer")
|
||||
else:
|
||||
self.color = COLOR[self.state]
|
||||
self.update_icon()
|
||||
self.timer.start(self.remaining_time)
|
||||
self.icon_timer.start()
|
||||
self.p_ac.setText("Pause Timer")
|
||||
|
||||
|
||||
class TransparentOverlay(QWidget):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.setWindowFlags(Qt.WindowType.BypassWindowManagerHint)
|
||||
self.setAttribute(Qt.WidgetAttribute.WA_TranslucentBackground)
|
||||
layout = QVBoxLayout()
|
||||
layout.setContentsMargins(0, 0, 0, 0)
|
||||
layout.setSpacing(0)
|
||||
bg = QWidget()
|
||||
bg.setStyleSheet(f"background-color: {COLOR[QWidget]};")
|
||||
layout.addWidget(bg)
|
||||
self.setLayout(layout)
|
||||
|
||||
def keyPressEvent(self, event):
|
||||
if event.key() == Qt.Key.Key_Escape:
|
||||
self.hide()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
app = QApplication(sys.argv)
|
||||
app.setQuitOnLastWindowClosed(False)
|
||||
reminder = BreakTimer()
|
||||
reminder.show()
|
||||
sys.exit(app.exec())
|
||||
|
|
@ -28,13 +28,21 @@ in {
|
|||
} (builtins.readFile ./furigana))
|
||||
];
|
||||
};
|
||||
home.file = builtins.listToAttrs (
|
||||
home.file =
|
||||
builtins.listToAttrs (
|
||||
map
|
||||
(x: {
|
||||
name = ".local/bin/${x}";
|
||||
value = {source = ln x;};
|
||||
})
|
||||
(builtins.attrNames config.u.bin)
|
||||
);
|
||||
)
|
||||
// {
|
||||
".local/bin/breaktime".source = let
|
||||
breaktime = pkgs.writers.writePython3Bin "breaktime" {
|
||||
libraries = [pkgs.python3Packages.pyqt6];
|
||||
} (builtins.readFile ./breaktime);
|
||||
in "${breaktime}/bin/breaktime";
|
||||
};
|
||||
home.packages = pkgs.lib.lists.flatten (builtins.attrValues config.u.bin);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -12,8 +12,8 @@ dbus-update-activation-environment $_vars
|
|||
|
||||
xrdb -merge ~/.config/Xresources
|
||||
xset r rate 200 30
|
||||
xrandr --output DisplayPort-1 --mode 1920x1080 --rate 144
|
||||
xsetwacom set "Wacom One by Wacom S Pen stylus" MapToOutput DisplayPort-0
|
||||
breaktime &
|
||||
sct 4500
|
||||
|
||||
systemctl --user start autostart.target
|
||||
|
|
|
|||
Loading…
Reference in a new issue