From cce63a41691e2ceeeb76d30cfabbca0cd6102bab Mon Sep 17 00:00:00 2001 From: iAmInAction <83808704+iAmInActions@users.noreply.github.com> Date: Wed, 4 Jun 2025 13:15:49 +0200 Subject: [PATCH] Add Pizza Timer make sure to `pip3 install Pillow pystray` --- PizzaTimer/DING.WAV | Bin 0 -> 11598 bytes PizzaTimer/main.py | 210 ++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 210 insertions(+) create mode 100644 PizzaTimer/DING.WAV create mode 100644 PizzaTimer/main.py diff --git a/PizzaTimer/DING.WAV b/PizzaTimer/DING.WAV new file mode 100644 index 0000000000000000000000000000000000000000..5741f232b72e289006bb7adbe2073fe33f7a40db GIT binary patch literal 11598 zcmeI1*K%A}c7{23zVsuw=H3tCDh)+RGfE6#1VDm_ox4xYr@K$*9FY?N3KS_(VyR?T zx%_Va*V_9uxzw4;A{kOlL*Gt9b;mFIZE*D%q{q);ay)tZSdNls& z!`Yx#8lFt5g_X6urX}xxvz(5Gk#6{tpMQF_8IM^R$s*`|_Pd-&~Aw%lq3R_*Ag z-``GJ&B5_OlwE%CB9mHw@YRD%*lzn?^YFue{d~}=k52cZ%;N`ZiNwqA?%dmSyG_rl z9sT~F?@#;fz4OCX?$OffcJldOZ-2dNH7dSS0{iWv-#>bFK2TPku5724zxwRXQ=?pT zjqu?8fBb$q8XsL=P0Wqu=c)AaH=q6GN!|<0yf^vr-+w!wOpo5Y+YOQ{>$%L@-Op}6 zNjtuhb-J&A|MhG-Jbe4(ew2Q(lh1A4`|Qr6l<(y;dgJwP?=PnP!#8h^E6MdlPD$MV z{L7_u;Ho*barxnw%gNyI;_|4RT;I+c$@^b?@i6DRT1IKU`sLSG{oelN+mmu;V@ol1 z9)9`x!<-lBDW!S->xc8vVDIv$(^`IgOVtyP@7!KWI-!wPs>dHbyxJY^UA;SLD(gFn zntpoc^9S2bprn=Z@h`tz?hW?eygP3zTd9JUdGf`b?>B8%NoB&rpWnaU8y;T0z3l4A zlw#(de0Ar$O~X>s>2Uw;`yX~kM?bu~>{{uhVr7@V{`|X5-BOe3aQEuxi|J_p^5@q* zC!N;q-1Bel+}+e%J&|%pSMScJ!`;hw7Xvq)Hf&|>+b{2JXpWLdJA>Cho=^MJi?o120E*n@eBceW}!rIdPm6WDucC!B9_;}LoPR}oo+OB4JzP|bB!Aerovx$t`J3O9t zJCn1^E%h+*G)e#wx2#+hFw0Hb~<2Pu#1ykXqsr~ zou^C7302P}Gj?n5Xt&oLpIn^ui>BoVW@7o_@{US&R&(zVK8X!Vrs;=Pl9;09k}0z> z1?w;?+m;{Nsg*~|+susm;nN#!0q*5LTc6KtZ zx?o=Z<A2VG?Vg-Y8@}y^zPh>mY(1kE z(#f3Po=p0!&gA%XuN8m=dSZ3?Wm;9U$*j{Dfwf16C;Q!^;{>6VezE)_si?VB#;gt| zgLZ4Me{$H5TrelMzVdu0ujbQft=tpR~XrFuSp~nbq_{Hg8vZV2$o% zZ_+Ng2*xRFzuZi#2qtfq+k;*c!R$@D5sOe0>v-NXw0uqvTm4=O!R$`^5a9*Bk=ocu z7IdwU)BHxS*KRcX)7?Q0fd;;r+1yAJ3|+~oUY%KQG9A__n83L=aOF8xU`5g@RRX z_aLx6oQykV55c%aD?e&@6dKjH9M@k~!Vf71b^`JH+~< zL90x5o}NiCQ&lspx7&?cqdOS1D}m>u5?C);%``OC@T)Ci?f#%s4LwxJ!Yarp2u9Vt zsM&7T>Mb-!y$BZAYMPm@>JGCSSg%p?;MCDF$#lUmbyc%VjdrtMYj*o!KI~jQn<8cq ztGAl9YNONdHOsz-nJ{w1ECgePHDdKPSVYV>u_p4mWonukRGZ9ty;cMfAp)H-AFN(M zFufKD3({y&6^OF$)~MAerSyHDa}9D;6=w%ES;Tm=vMpRl~-CmM2&hObCRC zJj8=RV3q8ujTXfF5bv34fthK##cHh@m5Gsf%!DPFZJJK8Qm;j21j;N3eOoIiy6q4P zXN<(7N?-+u2eX4ptyU>Vbu<+*R6@UI5wjYakrM-^key@UQKBrFNLq+0sfuPY^Ga3n zSw+J|LWHHo%q>=`6$-T3s1zX(!C=B5o&$}T)f$az2_LFL-!W85nr-0&QMm%UXr5_o zcg?ExMzu_Kfs1iBC{WAxOJrAGVA@BlR+fS4nhrbLvb|zNb`>;SEaH<)iZC=C;$i28 zWwMLVa8Z#z89IVFa}2qY3=?MBFtpvE97W|44y;v3eBisxTsU=t(jp67Oqj59{28O9 zQOj5c80>7vFGf)WpJ*H@5YvZ*0umE}E-;Ik6dy@Dw3>r8Az4U)Qf8_o5Q7ai6H$^` zV-a8!C=TRc<``vLFl3v8nK2I3Cq|X{I8e{`J$uF|&?%o*7&GOAL{ure?&p0uNup$b-UAnfX>1T(sIgupq5S;B$T zqBJioo(3BoQ?D`e1f#4WG0a38dN6cpmPAIq5^8|WRckTM2&1yZ0mFcyIUodrrFL?sZY7;FSXfkqT4Ed;Uy zXkJhW7Kn_p2rLT<)RvWqSpbOYf?;jbyr2lQnsLjBjDleU304NH z2u31M`5E&C!?Hl2%uH1g8wiVp?TB$(ph`1F8xQwcm99!|Csa^Ta4+#9p^+90*=F&Q zS#J0#MuA$E6Jq^V$*xHvxb3li1q(jK5GXdKEk)Gi`t>oN94LHJTcEd5c?iVj64@4O zlr|n!ms^bnTg;1SCowM(qM??VG4wWXHEBnEPmIEzUOuV!%7!30p?>daT}sa zSO^p-%>?ax^!<#{`bD5KMl1cM2s}1IUfG7S5@;uAm~S=Q1;pZ3n=vdI z5o+elrJ+exf#V%S`KNfMJ46nD=&6ZaBTSFChqSliSxxbI=d zy?uSZ%owe7!RX?J9o;=td74tma4%uWkWapsc>PwzvVcIzAcD?|1&RTsyJt?ISle>_ z(%s_%x`AjX5h!*AiQr)7S+&Aj&7-?V1nSB*#FQdXvg3P+);2_x_zr z!C*)wjLHZI2a@<}p4Lw0yIly>+B1~4RzdfXKi zBu3)F=sXUT88xm<3=ZU(pkO}1sMXLFlo=0OjSi&e;}CzvQ!IVrfo}+8Mm{UJ=qXTC z5Y-id;#CTi!etQ@4D66~76I{`b0LtLnwcjmKVx*@0^^L9O5~G8ATiYcYzKw|={yde z#A9xlv3SW~h;AZAE0sQKaH}C{C@8fU#B*CAq16gi36)2lspMD|TqO?-6U_q2ju|C} z+L4lmre^UWF}jtJ7!tq0sGAp9i7pSai;7?f26p(^ChVAT1<{;fu3#hr52^?>W^hW| z&}M;H_t?7#23-L=!K4VjLPT|mAu()6Z3v&3T3!g)BP1RLgio##l19^xIV+3A&@&Jv zls6S6<_4q0U>1GMykzB&eZo#MkFE)90%RMVMuSPMW|19r3^B|zs!K`ZO^JlUCk09? zl9^9Qqg_GC{v<;Mg&mCx+lAPdTzPa_+!ZuRsJRr#KCc)Fh6b?HtP%pu<{DnSc~_5p&CdjvC2<4KE> zW8r-;$7ps0qwiRvU~tO0<3OB7_|j(5}G01F)SGKl3Ti-j7#d!>=A6O|2>1AY_83iL5`2-?{FT{c_ ziW%y3lhKLr$^XBe85<%`WCc5z6QqM;r!z*73-6sHAWS$^ytILoPDjN$0_mUB(AW#)!Pimbi8r+X<(19x*mUm4qD_fMf#A zGlqCsT2bZYdGn#r8@MZ&#nR*RSmVr2=0(OL^ward8Vh89gxD!FcFMV-AO#YwCgQ<& zPTDo(Q0X-Kl1%!P4J=R$McOybph~@A3{6EJ+&3hx3na>+c zG(F{7=%c*E+PO%ih?ubBrl2~A0!k*6EucVL2!4T@kbL7St}Tqk3-gRD@bzgXjw2EL zwO}XNO`jx0e$9N6%s7^q#Xjd)Y)DeB55%9ySlDp}{sH6ch}_}(qTQz$rw2Z9sEm`N zjScaWoKO^`fxihh`%W*5pxDjqb^62Xb^gNwy1{PPE*hdc=vVyX2K?i%{|_1XZ>O4% A_y7O^ literal 0 HcmV?d00001 diff --git a/PizzaTimer/main.py b/PizzaTimer/main.py new file mode 100644 index 0000000..13f8fd0 --- /dev/null +++ b/PizzaTimer/main.py @@ -0,0 +1,210 @@ +import tkinter as tk +from tkinter import messagebox +from threading import Thread +import time +import subprocess +import sys +import os +import pystray +from PIL import Image, ImageDraw, ImageFont + + +class TimerApp: + def __init__(self, root): + self.root = root + self.root.title("Pizza Timer") + self.root.geometry("240x350") + self.root.configure(bg="#2e2e2e") # Dark gray background + self.running = False + self.remaining_seconds = 0 + self.original_minutes = 0 + + # Timer display label + self.timer_label = tk.Label(root, text="00:00", font=("Helvetica", 32), bg="#2e2e2e", fg="#ff00ff") + self.timer_label.pack(pady=2) + + # Separator + tk.Frame(root, bg="#cccccc", height=2).pack(fill=tk.X, pady=5) + + # Input for minutes + self.minutes_var = tk.IntVar(value=1) + tk.Label(root, text="Minutes:", bg="#2e2e2e", fg="#ffffff").pack() + self.minutes_entry = tk.Entry(root, textvariable=self.minutes_var, bg="#3a3a3a", fg="#ffffff", insertbackground="white") + self.minutes_entry.pack() + + # Alert message + tk.Label(root, text="Message:", bg="#2e2e2e", fg="#ffffff").pack() + self.message_text = tk.Text(root, height=5, width=25, bg="#3a3a3a", fg="#ffffff", insertbackground="white") + self.message_text.pack(pady=5) + + # Notification method + self.method_var = tk.StringVar(value="beep") + tk.Label(root, text="Notification backend:", bg="#2e2e2e", fg="#ffffff").pack() + self.method_var.set("beep") # Set default + + OPTIONS = ["beep", "aplay", "mplayer"] + self.method_dropdown = tk.OptionMenu(root, self.method_var, *OPTIONS) + self.method_dropdown.config(bg="#2e2e2e", fg="#acacac", activebackground="#444444", activeforeground="#ffffff", highlightthickness=0) + self.method_dropdown["menu"].config(bg="#2e2e2e", fg="#acacac", activebackground="#444444", activeforeground="#ffffff") + self.method_dropdown.pack() + + # Buttons + button_frame = tk.Frame(root, bg="#2e2e2e") + button_frame.pack(pady=10) + start_button = tk.Button( + button_frame, + text="Start", + command=self.start_timer, + bg="#444444", + fg="#ffffff", + activebackground="#666666", + activeforeground="#ffffff", + relief=tk.FLAT + ) + start_button.pack(side=tk.LEFT, padx=5) + + reset_button = tk.Button( + button_frame, + text="Reset", + command=self.reset_timer, + bg="#444444", + fg="#ffffff", + activebackground="#666666", + activeforeground="#ffffff", + relief=tk.FLAT + ) + reset_button.pack(side=tk.LEFT, padx=5) + + # Hide on close + self.root.protocol("WM_DELETE_WINDOW", self.hide_window) + + # Tray icon setup + self.icon = self.create_tray_icon() + + def create_tray_icon(self): + icon_image = self.create_icon_image() + return pystray.Icon("timer", icon_image, "Pizza Timer", menu=pystray.Menu( + pystray.MenuItem("Show", self.show_window), + pystray.MenuItem("Quit", self.quit_app) + )) + + def create_icon_image(self): + # Draw a simple clock face + image = Image.new('RGB', (64, 64), "#2e2e2e") + draw = ImageDraw.Draw(image) + draw.ellipse((8, 8, 56, 56), outline="white", width=4) + + # Coordinates for triangle (pizza slice) + center = (32, 32) + point1 = (48, 0) # Top center + point2 = (64, 16) # Right center + + # Draw filled triangle + draw.polygon([center, point1, point2], fill="orange", outline="yellow") + draw.ellipse((48, 15, 52, 22), fill="red") # mini pepperoni + return image + + def update_tray_icon_image(self, minutes, seconds): + img = Image.new("RGB", (64, 64), "#2e2e2e") + draw = ImageDraw.Draw(img) + draw.line((0, 32, 64, 32), fill="green", width=4) # Separator + + time_m = f"{minutes:02}" + time_s = f"{seconds:02}" + + try: + font = ImageFont.truetype("DejaVuSansMono.ttf", 28) + except: + font = ImageFont.load_default() + + # Center each number in its 64x32 half + def draw_centered_text(text, top_offset): + bbox = font.getbbox(text) + w = bbox[2] - bbox[0] + h = bbox[3] - bbox[1] + x = (64 - w) // 2 + y = top_offset - 6 + (32 - h) // 2 + draw.text((x, y), text, fill="white", font=font) + + draw_centered_text(time_m, 0) # Top half for MM + draw_centered_text(time_s, 32) # Bottom half for SS + + if self.icon.visible: + self.icon.icon = img + + def update_timer_display(self): + mins, secs = divmod(self.remaining_seconds, 60) + time_str = f"{mins:02}:{secs:02}" + self.timer_label.config(text=time_str) + if self.icon.visible: + self.icon.title = f"Pizza Timer - {time_str}" + self.update_tray_icon_image(mins, secs) + + def hide_window(self): + self.root.withdraw() + Thread(target=self.icon.run, daemon=True).start() + + def show_window(self, icon=None, item=None): + self.icon.stop() + self.root.after(0, self.root.deiconify) + + def quit_app(self, icon=None, item=None): + self.icon.stop() + self.root.quit() + + def start_timer(self): + if self.running: + return + + try: + minutes = int(self.minutes_var.get()) + except ValueError: + messagebox.showerror("Error", "Invalid number of minutes.") + return + + if minutes <= 0: + messagebox.showerror("Error", "Minutes must be greater than 0.") + return + + self.original_minutes = minutes + self.remaining_seconds = minutes * 60 + self.running = True + Thread(target=self.countdown, daemon=True).start() + + def reset_timer(self): + self.running = False + self.remaining_seconds = self.original_minutes * 60 + self.update_timer_display() + + def countdown(self): + while self.remaining_seconds > 0 and self.running: + self.root.after(0, self.update_timer_display) + time.sleep(1) + self.remaining_seconds -= 1 + + if self.running and self.remaining_seconds <= 0: + self.running = False + self.root.after(0, self.update_timer_display) + self.root.after(0, self.trigger_alarm) + + def trigger_alarm(self): + message = self.message_text.get("1.0", tk.END).strip() + messagebox.showinfo("TIMER ALERT", message or "Go grab your Pizza!") + + method = self.method_var.get() + if method == "beep": + subprocess.Popen(["beep", "-f", "2000", "-l", "2000"]) + elif method == "aplay": + subprocess.Popen(["aplay", "DING.WAV"]) + elif method == "mplayer": + subprocess.Popen(["mplayer", "DING.WAV"]) + + +def main(): + root = tk.Tk() + app = TimerApp(root) + root.mainloop() + + +if __name__ == "__main__": + main()