Update desktop.py
This commit is contained in:
284
desktop.py
284
desktop.py
@@ -1,41 +1,47 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
"""
|
"""
|
||||||
Desktop GUI wrapper for chatter.py using Tkinter.
|
Desktop GUI wrapper for chatter.py using Tkinter.
|
||||||
Supports text messages and attachments with clickable filenames.
|
Supports text messages and attachments (files) with clickable filenames.
|
||||||
Polls server every 1.5s for updates.
|
Polls server every 1.5s for updates.
|
||||||
Includes 24-hour toggle and timezone selection.
|
Settings now apply instantly.
|
||||||
|
Attachment preview images resize dynamically with the popup.
|
||||||
|
App icon set to chatter.png / chatter.ico for notifications.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import threading
|
import threading
|
||||||
import tkinter as tk
|
import tkinter as tk
|
||||||
from tkinter import filedialog, scrolledtext, messagebox
|
from tkinter import filedialog, scrolledtext, messagebox
|
||||||
import requests, os, time, webbrowser
|
import requests, os, time, webbrowser, sys
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from dateutil import parser, tz
|
|
||||||
from plyer import notification
|
from plyer import notification
|
||||||
import cv2
|
import cv2
|
||||||
from PIL import Image, ImageTk
|
from PIL import Image, ImageTk
|
||||||
from io import BytesIO
|
from io import BytesIO
|
||||||
|
from dateutil import tz
|
||||||
|
|
||||||
SERVER = "https://chatter.wholeworldcoding.com"
|
SERVER = "https://chatter.wholeworldcoding.com"
|
||||||
SESSION = requests.Session()
|
SESSION = requests.Session()
|
||||||
LAST_MSG_IDS = set()
|
LAST_MSG_IDS = set()
|
||||||
|
|
||||||
|
# Default font sizes
|
||||||
|
BASE_FONT_SIZE = 10
|
||||||
|
BASE_PADDING = 5
|
||||||
|
|
||||||
# ---------------- ChatApp ----------------
|
|
||||||
class ChatApp:
|
class ChatApp:
|
||||||
def __init__(self, root):
|
def __init__(self, root):
|
||||||
self.root = root
|
self.root = root
|
||||||
root.title("chatter")
|
root.title("chatter")
|
||||||
root.geometry("800x600")
|
root.geometry("900x700")
|
||||||
root.minsize(500, 400)
|
root.minsize(600, 500)
|
||||||
|
|
||||||
# Settings
|
|
||||||
self.settings = {
|
self.settings = {
|
||||||
"popup_notification": True,
|
"popup_notification": True,
|
||||||
"camera_notification": False,
|
"camera_notification": False,
|
||||||
"time_24h": False,
|
"use_24_hour": False,
|
||||||
"timezone": time.tzname[0] # Default local timezone
|
"date_position": "before",
|
||||||
|
"timezone": tz.tzlocal(),
|
||||||
|
"zoom": 1.0,
|
||||||
|
"dark_mode": False,
|
||||||
}
|
}
|
||||||
|
|
||||||
self.username = None
|
self.username = None
|
||||||
@@ -85,45 +91,75 @@ class ChatApp:
|
|||||||
|
|
||||||
self.msg_entry = tk.Entry(frame)
|
self.msg_entry = tk.Entry(frame)
|
||||||
self.msg_entry.grid(row=0, column=0, sticky="ew", padx=5)
|
self.msg_entry.grid(row=0, column=0, sticky="ew", padx=5)
|
||||||
|
self.msg_entry.bind("<Return>", lambda e: self.send_message())
|
||||||
|
|
||||||
tk.Button(frame, text="Send", command=self.send_message).grid(row=0, column=1, padx=5)
|
tk.Button(frame, text="Send", command=self.send_message).grid(row=0, column=1, padx=5)
|
||||||
tk.Button(frame, text="📎 File", command=self.send_file).grid(row=0, column=2, padx=5)
|
tk.Button(frame, text="📎 File", command=self.send_file).grid(row=0, column=2, padx=5)
|
||||||
|
|
||||||
|
self.apply_theme()
|
||||||
self.poll_messages()
|
self.poll_messages()
|
||||||
|
|
||||||
# ---------- Settings ----------
|
# ---------- Settings ----------
|
||||||
def open_settings(self):
|
def open_settings(self):
|
||||||
win = tk.Toplevel(self.root)
|
win = tk.Toplevel(self.root)
|
||||||
win.title("Settings")
|
win.title("Settings")
|
||||||
win.geometry("350x250")
|
win.geometry("400x400")
|
||||||
win.resizable(False, False)
|
|
||||||
|
|
||||||
|
canvas = tk.Canvas(win)
|
||||||
|
scrollbar = tk.Scrollbar(win, orient="vertical", command=canvas.yview)
|
||||||
|
scroll_frame = tk.Frame(canvas)
|
||||||
|
scroll_frame.bind(
|
||||||
|
"<Configure>",
|
||||||
|
lambda e: canvas.configure(scrollregion=canvas.bbox("all"))
|
||||||
|
)
|
||||||
|
canvas.create_window((0, 0), window=scroll_frame, anchor="nw")
|
||||||
|
canvas.configure(yscrollcommand=scrollbar.set)
|
||||||
|
canvas.pack(side="left", fill="both", expand=True)
|
||||||
|
scrollbar.pack(side="right", fill="y")
|
||||||
|
|
||||||
|
# Enable mouse wheel scrolling
|
||||||
|
def _on_mousewheel(event):
|
||||||
|
canvas.yview_scroll(-int(event.delta/120), "units")
|
||||||
|
scroll_frame.bind_all("<MouseWheel>", _on_mousewheel)
|
||||||
|
|
||||||
|
# Variables
|
||||||
popup_var = tk.BooleanVar(value=self.settings["popup_notification"])
|
popup_var = tk.BooleanVar(value=self.settings["popup_notification"])
|
||||||
cam_var = tk.BooleanVar(value=self.settings["camera_notification"])
|
cam_var = tk.BooleanVar(value=self.settings["camera_notification"])
|
||||||
time24_var = tk.BooleanVar(value=self.settings.get("time_24h", False))
|
hour24_var = tk.BooleanVar(value=self.settings.get("use_24_hour", False))
|
||||||
tz_var = tk.StringVar(value=self.settings.get("timezone", time.tzname[0]))
|
datepos_var = tk.StringVar(value=self.settings.get("date_position", "before"))
|
||||||
|
zoom_var = tk.DoubleVar(value=self.settings.get("zoom", 1.0))
|
||||||
|
dark_var = tk.BooleanVar(value=self.settings.get("dark_mode", False))
|
||||||
|
|
||||||
tk.Label(win, text="Notification Options", font=("Courier", 12, "bold")).pack(pady=5)
|
tk.Label(scroll_frame, text="Notification Options", font=("Courier", 12, "bold")).pack(pady=10)
|
||||||
tk.Checkbutton(win, text="Popup Notification", variable=popup_var).pack(anchor="w", padx=20)
|
tk.Checkbutton(scroll_frame, text="Popup Notification", variable=popup_var,
|
||||||
tk.Checkbutton(win, text="Camera Notification", variable=cam_var).pack(anchor="w", padx=20)
|
command=lambda: self.update_setting("popup_notification", popup_var.get())).pack(anchor="w", padx=20, pady=5)
|
||||||
|
tk.Checkbutton(scroll_frame, text="Camera Notification", variable=cam_var,
|
||||||
|
command=lambda: self.update_setting("camera_notification", cam_var.get())).pack(anchor="w", padx=20, pady=5)
|
||||||
|
|
||||||
tk.Label(win, text="Time Display", font=("Courier", 12, "bold")).pack(pady=5)
|
tk.Label(scroll_frame, text="Clock Options", font=("Courier", 12, "bold")).pack(pady=10)
|
||||||
tk.Checkbutton(win, text="24-Hour Clock", variable=time24_var).pack(anchor="w", padx=20)
|
tk.Checkbutton(scroll_frame, text="Use 24-hour time", variable=hour24_var,
|
||||||
|
command=lambda: self.update_setting("use_24_hour", hour24_var.get())).pack(anchor="w", padx=20, pady=5)
|
||||||
|
|
||||||
tk.Label(win, text="Timezone", font=("Courier", 12, "bold")).pack(pady=5)
|
tk.Label(scroll_frame, text="Date Position", font=("Courier", 12, "bold")).pack(pady=10)
|
||||||
# Common timezones for dropdown
|
tk.Radiobutton(scroll_frame, text="Before username", variable=datepos_var, value="before",
|
||||||
tz_options = ["UTC", "US/Eastern", "US/Central", "US/Mountain", "US/Pacific", "Europe/London", "Europe/Berlin"]
|
command=lambda: self.update_setting("date_position", datepos_var.get())).pack(anchor="w", padx=20)
|
||||||
tk.OptionMenu(win, tz_var, *tz_options).pack(anchor="w", padx=20)
|
tk.Radiobutton(scroll_frame, text="After username", variable=datepos_var, value="after",
|
||||||
|
command=lambda: self.update_setting("date_position", datepos_var.get())).pack(anchor="w", padx=20)
|
||||||
|
|
||||||
def save_settings():
|
tk.Label(scroll_frame, text="Zoom (Font Size)", font=("Courier", 12, "bold")).pack(pady=10)
|
||||||
self.settings["popup_notification"] = popup_var.get()
|
tk.Scale(scroll_frame, variable=zoom_var, from_=0.5, to=3.0, resolution=0.1, orient="horizontal",
|
||||||
self.settings["camera_notification"] = cam_var.get()
|
command=lambda e: self.update_setting("zoom", zoom_var.get())).pack(padx=20, pady=5)
|
||||||
self.settings["time_24h"] = time24_var.get()
|
|
||||||
self.settings["timezone"] = tz_var.get()
|
|
||||||
self.update_chat_timestamps()
|
|
||||||
messagebox.showinfo("Saved", "Settings updated.")
|
|
||||||
win.destroy()
|
|
||||||
|
|
||||||
tk.Button(win, text="Save", command=save_settings).pack(pady=10)
|
tk.Label(scroll_frame, text="Appearance", font=("Courier", 12, "bold")).pack(pady=10)
|
||||||
|
tk.Checkbutton(scroll_frame, text="Dark Mode", variable=dark_var,
|
||||||
|
command=lambda: self.update_setting("dark_mode", dark_var.get())).pack(anchor="w", padx=20, pady=5)
|
||||||
|
|
||||||
|
# ---------- Settings updater ----------
|
||||||
|
def update_setting(self, key, value):
|
||||||
|
self.settings[key] = value
|
||||||
|
if key in ["zoom", "dark_mode"]:
|
||||||
|
self.apply_theme()
|
||||||
|
self.poll_messages(force=True)
|
||||||
|
|
||||||
# ---------- Auth ----------
|
# ---------- Auth ----------
|
||||||
def login(self):
|
def login(self):
|
||||||
@@ -173,6 +209,7 @@ class ChatApp:
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"File send error: {e}")
|
print(f"File send error: {e}")
|
||||||
|
|
||||||
|
# ---------- Attachment Preview ----------
|
||||||
def show_attachment_popup(self, file_name, username):
|
def show_attachment_popup(self, file_name, username):
|
||||||
popup = tk.Toplevel(self.root)
|
popup = tk.Toplevel(self.root)
|
||||||
popup.title(f"{username} sent a file")
|
popup.title(f"{username} sent a file")
|
||||||
@@ -181,79 +218,55 @@ class ChatApp:
|
|||||||
file_url = f"{SERVER}/uploads/{file_name}"
|
file_url = f"{SERVER}/uploads/{file_name}"
|
||||||
preview_url = file_url.replace("/uploads/", "/preview/")
|
preview_url = file_url.replace("/uploads/", "/preview/")
|
||||||
|
|
||||||
tk.Label(popup, wrap="400", text=f"{username} sent: {file_name}", font=("Courier", 12, "bold")).pack(pady=10)
|
# Title
|
||||||
|
title_label = tk.Label(popup, wraplength=380,
|
||||||
try:
|
text=f"{username} sent: {file_name}",
|
||||||
resp = requests.get(file_url)
|
font=self.get_font("italic"),
|
||||||
pil_img = Image.open(BytesIO(resp.content))
|
fg="grey")
|
||||||
pil_img.thumbnail((350, 350))
|
title_label.pack(pady=5)
|
||||||
tk_img = ImageTk.PhotoImage(pil_img)
|
|
||||||
label_img = tk.Label(popup, image=tk_img)
|
|
||||||
label_img.image = tk_img
|
|
||||||
label_img.pack(pady=5)
|
|
||||||
except Exception:
|
|
||||||
tk.Label(popup, wrap="400", text="You can preview the file, or download it.").pack(pady=5)
|
|
||||||
|
|
||||||
|
# Buttons
|
||||||
btn_frame = tk.Frame(popup)
|
btn_frame = tk.Frame(popup)
|
||||||
btn_frame.pack(pady=10)
|
btn_frame.pack(pady=5)
|
||||||
|
|
||||||
def download_file():
|
def download_file():
|
||||||
save_path = filedialog.asksaveasfilename(initialfile=file_name)
|
save_path = filedialog.asksaveasfilename(initialfile=file_name)
|
||||||
if save_path:
|
if save_path:
|
||||||
with open(save_path, "wb") as f:
|
with open(save_path, "wb") as f:
|
||||||
f.write(requests.get(file_url).content)
|
f.write(requests.get(file_url).content)
|
||||||
messagebox.showinfo("Downloaded", f"File saved to {save_path}")
|
messagebox.showinfo("Downloaded", f"File saved to {save_path}")
|
||||||
|
|
||||||
tk.Button(btn_frame, text="Download", command=download_file).pack(side=tk.LEFT, padx=5)
|
tk.Button(btn_frame, text="Download", command=download_file).pack(side=tk.LEFT, padx=5)
|
||||||
tk.Button(btn_frame, text="Preview", command=lambda: webbrowser.open(preview_url)).pack(side=tk.LEFT, padx=5)
|
tk.Button(btn_frame, text="Preview", command=lambda: webbrowser.open(preview_url)).pack(side=tk.LEFT, padx=5)
|
||||||
|
|
||||||
# ---------- Timestamp helper ----------
|
# Image frame
|
||||||
def format_timestamp(self, ts):
|
img_frame = tk.Frame(popup)
|
||||||
|
img_frame.pack(expand=True, fill="both", pady=5)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
dt = parser.parse(ts)
|
resp = requests.get(file_url)
|
||||||
dt = dt.astimezone(tz.gettz(self.settings.get("timezone", time.tzname[0])))
|
pil_img = Image.open(BytesIO(resp.content))
|
||||||
if self.settings.get("time_24h"):
|
|
||||||
return dt.strftime("%Y-%m-%d %H:%M:%S")
|
|
||||||
else:
|
|
||||||
return dt.strftime("%Y-%m-%d %I:%M:%S %p")
|
|
||||||
except Exception:
|
except Exception:
|
||||||
return str(ts)
|
tk.Label(img_frame, wraplength=380, text="You can preview the file, or download it.").pack(pady=5)
|
||||||
|
return
|
||||||
|
|
||||||
def update_chat_timestamps(self):
|
# Display image function
|
||||||
"""Re-render chat box to apply new timezone or 24h/12h setting."""
|
label_img = tk.Label(img_frame)
|
||||||
try:
|
label_img.pack(expand=True)
|
||||||
# Re-poll messages and re-render
|
|
||||||
r = SESSION.get(SERVER + "/api/messages")
|
|
||||||
if r.status_code != 200:
|
|
||||||
return
|
|
||||||
msgs = r.json()
|
|
||||||
|
|
||||||
self.chat_box.config(state='normal')
|
def update_image(*args):
|
||||||
self.chat_box.delete("1.0", tk.END)
|
popup.update_idletasks()
|
||||||
|
max_width = popup.winfo_width() - 20
|
||||||
|
max_height = popup.winfo_height() - title_label.winfo_reqheight() - btn_frame.winfo_reqheight() - 40
|
||||||
|
img_copy = pil_img.copy()
|
||||||
|
img_copy.thumbnail((max_width, max_height))
|
||||||
|
tk_img = ImageTk.PhotoImage(img_copy)
|
||||||
|
label_img.config(image=tk_img)
|
||||||
|
label_img.image = tk_img
|
||||||
|
|
||||||
for m in msgs:
|
# Bind resize event
|
||||||
ts = m.get("created_at") or time.time()
|
popup.bind("<Configure>", update_image)
|
||||||
ts_str = self.format_timestamp(ts)
|
update_image()
|
||||||
|
|
||||||
if m.get("attachment"):
|
# ---------- Message Polling ----------
|
||||||
attach_name = m["attachment"]
|
|
||||||
tag_name = f"attach{m['id']}"
|
|
||||||
self.chat_box.insert(tk.END, f"[{ts_str}] {m['username']} sent a file: ", f"grey{m['id']}")
|
|
||||||
self.chat_box.tag_config(f"grey{m['id']}", foreground="grey", font=("Courier", 10, "italic"))
|
|
||||||
self.chat_box.insert(tk.END, f"{attach_name}\n", tag_name)
|
|
||||||
self.chat_box.tag_config(tag_name, foreground="blue", underline=True)
|
|
||||||
self.chat_box.tag_bind(tag_name, "<Button-1>",
|
|
||||||
lambda e, fn=attach_name, un=m["username"]: self.show_attachment_popup(fn, un))
|
|
||||||
else:
|
|
||||||
text = m.get("text", "")
|
|
||||||
self.chat_box.insert(tk.END, f"[{ts_str}] {m['username']}: {text}\n")
|
|
||||||
|
|
||||||
self.chat_box.config(state='disabled')
|
|
||||||
self.chat_box.yview(tk.END)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"Update timestamp error: {e}")
|
|
||||||
|
|
||||||
# ---------- Poll messages ----------
|
|
||||||
def poll_messages(self, force=False):
|
def poll_messages(self, force=False):
|
||||||
try:
|
try:
|
||||||
r = SESSION.get(SERVER + "/api/messages")
|
r = SESSION.get(SERVER + "/api/messages")
|
||||||
@@ -264,9 +277,50 @@ class ChatApp:
|
|||||||
new_msgs = [m for m in msgs if m["id"] not in LAST_MSG_IDS]
|
new_msgs = [m for m in msgs if m["id"] not in LAST_MSG_IDS]
|
||||||
|
|
||||||
if new_msgs or force:
|
if new_msgs or force:
|
||||||
self.update_chat_timestamps()
|
self.chat_box.config(state='normal')
|
||||||
|
self.chat_box.delete("1.0", tk.END)
|
||||||
|
padding = int(BASE_PADDING * self.settings.get("zoom", 1.0))
|
||||||
|
for m in msgs:
|
||||||
|
ts = m.get("created_at") or time.time()
|
||||||
|
try:
|
||||||
|
dt = datetime.fromtimestamp(float(ts)).astimezone(self.settings["timezone"])
|
||||||
|
ts_str = dt.strftime("%m/%d/%Y, %H:%M:%S") if self.settings.get("use_24_hour") else dt.strftime("%m/%d/%Y, %I:%M:%S %p")
|
||||||
|
except Exception:
|
||||||
|
ts_str = str(ts)
|
||||||
|
|
||||||
|
ts_tag = f"ts{m['id']}"
|
||||||
|
uname_tag = f"bold{m['id']}"
|
||||||
|
msg_tag = f"msg{m['id']}"
|
||||||
|
attach_tag = f"attach{m['id']}"
|
||||||
|
|
||||||
|
if m.get("attachment"):
|
||||||
|
attach_name = m["attachment"]
|
||||||
|
if self.settings["date_position"] == "before":
|
||||||
|
self.chat_box.insert(tk.END, f"[{ts_str}] {m['username']} sent a file: ", ts_tag)
|
||||||
|
else:
|
||||||
|
self.chat_box.insert(tk.END, f"{m['username']} [{ts_str}] sent a file: ", ts_tag)
|
||||||
|
self.chat_box.tag_config(ts_tag, foreground="grey", font=self.get_font("italic"))
|
||||||
|
self.chat_box.insert(tk.END, f"{attach_name}\n", attach_tag)
|
||||||
|
self.chat_box.tag_config(attach_tag, foreground="blue", font=self.get_font("normal"), underline=True)
|
||||||
|
self.chat_box.tag_bind(attach_tag, "<Button-1>",
|
||||||
|
lambda e, fn=attach_name, un=m["username"]: self.show_attachment_popup(fn, un))
|
||||||
|
else:
|
||||||
|
if self.settings["date_position"] == "before":
|
||||||
|
self.chat_box.insert(tk.END, f"[{ts_str}] ", ts_tag)
|
||||||
|
self.chat_box.insert(tk.END, f"{m['username']}", uname_tag)
|
||||||
|
self.chat_box.insert(tk.END, f": {m.get('text','')}\n", msg_tag)
|
||||||
|
else:
|
||||||
|
self.chat_box.insert(tk.END, f"{m['username']}", uname_tag)
|
||||||
|
self.chat_box.insert(tk.END, f" [{ts_str}]: ", ts_tag)
|
||||||
|
self.chat_box.insert(tk.END, f"{m.get('text','')}\n", msg_tag)
|
||||||
|
|
||||||
|
self.chat_box.tag_config(ts_tag, foreground="grey", font=self.get_font("grey"))
|
||||||
|
self.chat_box.tag_config(uname_tag, font=self.get_font("bold"))
|
||||||
|
self.chat_box.tag_config(msg_tag, font=self.get_font("normal"), spacing1=padding, spacing3=padding)
|
||||||
|
|
||||||
|
self.chat_box.config(state='disabled')
|
||||||
|
self.chat_box.yview(tk.END)
|
||||||
|
|
||||||
# Notify user about new messages
|
|
||||||
for m in new_msgs:
|
for m in new_msgs:
|
||||||
if m["username"] != self.username:
|
if m["username"] != self.username:
|
||||||
msg_preview = m.get("text") or m.get("attachment", "Attachment")
|
msg_preview = m.get("text") or m.get("attachment", "Attachment")
|
||||||
@@ -279,6 +333,31 @@ class ChatApp:
|
|||||||
|
|
||||||
self.root.after(1500, self.poll_messages)
|
self.root.after(1500, self.poll_messages)
|
||||||
|
|
||||||
|
# ---------- Font helpers ----------
|
||||||
|
def get_font(self, kind="normal"):
|
||||||
|
scale = self.settings.get("zoom", 1.0)
|
||||||
|
size = max(int(BASE_FONT_SIZE * scale), 1)
|
||||||
|
if kind == "bold":
|
||||||
|
return ("Courier", size+1, "bold")
|
||||||
|
elif kind == "italic":
|
||||||
|
return ("Courier", size, "italic")
|
||||||
|
elif kind == "grey":
|
||||||
|
return ("Courier", max(size-1, 1))
|
||||||
|
else:
|
||||||
|
return ("Courier", size)
|
||||||
|
|
||||||
|
# ---------- Theme ----------
|
||||||
|
def apply_theme(self):
|
||||||
|
dark = self.settings.get("dark_mode", False)
|
||||||
|
bg = "#1e1e1e" if dark else "white"
|
||||||
|
fg = "#d4d4d4" if dark else "black"
|
||||||
|
entry_bg = "#2e2e2e" if dark else "white"
|
||||||
|
entry_fg = "#d4d4d4" if dark else "black"
|
||||||
|
|
||||||
|
self.chat_box.config(bg=bg, fg=fg, insertbackground=fg)
|
||||||
|
self.msg_entry.config(bg=entry_bg, fg=entry_fg, insertbackground=fg)
|
||||||
|
self.root.config(bg=bg)
|
||||||
|
|
||||||
# ---------- Notifications ----------
|
# ---------- Notifications ----------
|
||||||
def notify_user(self, sender, message):
|
def notify_user(self, sender, message):
|
||||||
if self.settings.get("popup_notification"):
|
if self.settings.get("popup_notification"):
|
||||||
@@ -288,7 +367,20 @@ class ChatApp:
|
|||||||
|
|
||||||
def show_system_notification(self, title, message):
|
def show_system_notification(self, title, message):
|
||||||
try:
|
try:
|
||||||
notification.notify(title=title, app_name="chatter", message=message[:200], timeout=5)
|
# Determine icon path for PyInstaller
|
||||||
|
if getattr(sys, 'frozen', False):
|
||||||
|
base_path = sys._MEIPASS
|
||||||
|
else:
|
||||||
|
base_path = os.path.dirname(os.path.abspath(__file__))
|
||||||
|
icon_path = os.path.join(base_path, "chatter.ico")
|
||||||
|
|
||||||
|
notification.notify(
|
||||||
|
title=title,
|
||||||
|
message=message[:200],
|
||||||
|
app_name="chatter",
|
||||||
|
app_icon=icon_path, # Use .ico for Windows toast
|
||||||
|
timeout=5
|
||||||
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f"Notification failed: {e}")
|
print(f"Notification failed: {e}")
|
||||||
|
|
||||||
@@ -307,5 +399,13 @@ class ChatApp:
|
|||||||
# --- Run GUI ---
|
# --- Run GUI ---
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
root = tk.Tk()
|
root = tk.Tk()
|
||||||
|
|
||||||
|
# Set app icon
|
||||||
|
try:
|
||||||
|
icon_img = tk.PhotoImage(file="chatter.png")
|
||||||
|
root.iconphoto(True, icon_img)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to load icon: {e}")
|
||||||
|
|
||||||
app = ChatApp(root)
|
app = ChatApp(root)
|
||||||
root.mainloop()
|
root.mainloop()
|
||||||
|
|||||||
Reference in New Issue
Block a user