Compare commits
2 Commits
973c5bdfc9
...
9d1bbdaa85
Author | SHA1 | Date |
---|---|---|
|
9d1bbdaa85 | |
|
e469c1dca8 |
|
@ -0,0 +1,8 @@
|
|||
# Default ignored files
|
||||
/shelf/
|
||||
/workspace.xml
|
||||
# Editor-based HTTP Client requests
|
||||
/httpRequests/
|
||||
# Datasource local storage ignored files
|
||||
/dataSources/
|
||||
/dataSources.local.xml
|
|
@ -0,0 +1,14 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.13 (pythonProject)" jdkType="Python SDK" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
<component name="PyDocumentationSettings">
|
||||
<option name="format" value="PLAIN" />
|
||||
<option name="myDocStringFormat" value="Plain" />
|
||||
</component>
|
||||
</module>
|
|
@ -0,0 +1,6 @@
|
|||
<component name="InspectionProjectProfileManager">
|
||||
<settings>
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
|
@ -0,0 +1,7 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="Black">
|
||||
<option name="sdkName" value="Python 3.11.2 WSL (Debian): (/home/santi/.virtualenvs/Final/bin/python)" />
|
||||
</component>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.13 (pythonProject)" project-jdk-type="Python SDK" />
|
||||
</project>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectModuleManager">
|
||||
<modules>
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/Final.iml" filepath="$PROJECT_DIR$/.idea/Final.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$/.." vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
<mapping directory="$PROJECT_DIR$/app" vcs="Git" />
|
||||
</component>
|
||||
</project>
|
|
@ -0,0 +1,83 @@
|
|||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
import os
|
||||
import configparser
|
||||
|
||||
class ConfigMgr:
|
||||
|
||||
def __init__(self, top_level, config_changed_listener=None):
|
||||
self.top_level = top_level
|
||||
self.config_window = None
|
||||
self.config = configparser.ConfigParser()
|
||||
self.__load_config()
|
||||
self.config_changed_listener=config_changed_listener
|
||||
self.config.read('config.ini')
|
||||
|
||||
def __load_config(self):
|
||||
if os.path.exists('config.ini'):
|
||||
self.config.read('config.ini')
|
||||
else:
|
||||
print("Config file not found, creating default config file")
|
||||
with open('config.ini', 'w') as f:
|
||||
self.__write_default_config(f)
|
||||
self.config.read('config.ini')
|
||||
|
||||
def __write_default_config(self, file):
|
||||
chat_config = ("[Chat]\n"
|
||||
"server=http://localhost:2020\n"
|
||||
"name=User\n")
|
||||
file.write(chat_config)
|
||||
|
||||
def display_config_window(self):
|
||||
if (self.config_window is None
|
||||
or not tk.Toplevel.winfo_exists(self.config_window)):
|
||||
self.config_window = self.__build_config_window()
|
||||
else:
|
||||
self.config_window.lift()
|
||||
|
||||
def __build_config_window(self):
|
||||
config_window = tk.Toplevel(self.top_level)
|
||||
config_window.title("Config")
|
||||
config_window.geometry("400x300")
|
||||
|
||||
notebook = ttk.Notebook(config_window)
|
||||
notebook.pack(expand=True, fill="both")
|
||||
|
||||
chat_tab = ttk.Frame(notebook)
|
||||
notebook.add(chat_tab, text="Chat Config")
|
||||
|
||||
chat_server_label = tk.Label(chat_tab, text="Chat Server URL")
|
||||
chat_server_label.pack()
|
||||
self.chat_server_variable = tk.StringVar()
|
||||
try:
|
||||
self.chat_server_variable.set(self.config["Chat"]["server"])
|
||||
except KeyError:
|
||||
self.chat_server_variable.set("")
|
||||
chat_server_input = tk.Entry(chat_tab, textvariable=self.chat_server_variable)
|
||||
chat_server_input.pack()
|
||||
|
||||
chat_name_label = tk.Label(chat_tab, text="Name in the Chat")
|
||||
chat_name_label.pack()
|
||||
self.chat_name_variable = tk.StringVar()
|
||||
try:
|
||||
self.chat_name_variable.set(self.config["Chat"]["name"])
|
||||
except KeyError:
|
||||
self.chat_name_variable.set("")
|
||||
chat_name_input = tk.Entry(chat_tab, textvariable=self.chat_name_variable)
|
||||
chat_name_input.pack()
|
||||
|
||||
self.save_button = tk.Button(config_window, text="Save", command=self.save_config)
|
||||
self.save_button.pack(pady=10)
|
||||
|
||||
return config_window
|
||||
|
||||
def save_config(self):
|
||||
self.config["Chat"] = {"server": self.chat_server_variable.get(),
|
||||
"name": self.chat_name_variable.get()}
|
||||
with open('config.ini', 'w') as configfile:
|
||||
self.config.write(configfile)
|
||||
|
||||
self.config_changed_listener()
|
||||
|
||||
# Close window
|
||||
self.config_window.destroy()
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,89 @@
|
|||
import threading
|
||||
import time
|
||||
import flask
|
||||
from werkzeug.serving import make_server
|
||||
|
||||
|
||||
class Server:
|
||||
"""
|
||||
This server does NOT use Flask's built-in development server.
|
||||
Instead, it uses Werkzeug's make_server to create a WSGI server.
|
||||
|
||||
This server is thread-safe and can be stopped by setting the stop_event.
|
||||
|
||||
This server works as a chat server by polling, not by using websockets (to keep it simple).
|
||||
"""
|
||||
|
||||
def __init__(self, host: str, port: int, stop_event: threading.Event):
|
||||
self.host = host
|
||||
self.port = port
|
||||
self.stop_event = stop_event
|
||||
|
||||
# Flask definition
|
||||
self.flask = flask.Flask(__name__)
|
||||
self.flask.add_url_rule('/status', methods=['POST'], view_func=self.status)
|
||||
self.flask.add_url_rule('/send_message', methods=['POST'], view_func=self.send_message)
|
||||
self.flask.add_url_rule('/get_messages', methods=['POST'], view_func=self.get_messages)
|
||||
self.server = make_server(self.host, self.port, self.flask)
|
||||
self.watcher_thread = threading.Thread(target=self.__watcher)
|
||||
self.server_thread = threading.Thread(target=self.server.serve_forever)
|
||||
self.server_thread.start()
|
||||
self.watcher_thread.start()
|
||||
|
||||
# Message initialization
|
||||
self.message_id = 0
|
||||
self.messages = self.__init_messages()
|
||||
|
||||
def __watcher(self):
|
||||
while not self.stop_event.is_set():
|
||||
time.sleep(1)
|
||||
|
||||
self.shutdown()
|
||||
|
||||
def shutdown(self):
|
||||
self.__persist_messages()
|
||||
self.server.shutdown()
|
||||
|
||||
def __init_messages(self):
|
||||
messages = []
|
||||
try:
|
||||
with open('chat_server/messages.txt', 'r') as f:
|
||||
for line in f:
|
||||
parts = line.strip().split('|')
|
||||
messages.append({
|
||||
'id': int(parts[0]),
|
||||
'sender': parts[1],
|
||||
'content': parts[2]
|
||||
})
|
||||
self.message_id = messages[-1]['id'] if messages else 0
|
||||
except FileNotFoundError:
|
||||
with open('chat_server/messages.txt', 'w+') as f:
|
||||
pass
|
||||
return messages
|
||||
|
||||
def __persist_messages(self):
|
||||
with open('chat_server/messages.txt', 'w') as f:
|
||||
for message in self.messages:
|
||||
f.write(f"{message['id']}|{message['sender']}|{message['content']}\n")
|
||||
|
||||
def status(self):
|
||||
return 'OK'
|
||||
|
||||
def send_message(self):
|
||||
sender = flask.request.json['sender']
|
||||
content = flask.request.json['content']
|
||||
self.message_id += 1
|
||||
message = {
|
||||
'id': self.message_id,
|
||||
'sender': sender,
|
||||
'content': content
|
||||
}
|
||||
self.messages.append(message)
|
||||
return {'id': self.message_id}
|
||||
|
||||
def get_messages(self):
|
||||
try: last_id = flask.request.json.get('last_id')
|
||||
# last_id is a mandatory parameter
|
||||
except AttributeError: return flask.Response('Last ID not specified', status=400)
|
||||
new_messages = [msg for msg in self.messages if msg['id'] > last_id]
|
||||
return {'messages': new_messages}
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,2 @@
|
|||
1|Santi|Hola como estas?
|
||||
2|Santi|Andres no me copies el TO DO
|
|
@ -0,0 +1,4 @@
|
|||
[Chat]
|
||||
server = http://localhost:2020
|
||||
name = Santi
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
import tkinter as tk
|
||||
from tkinter import Menu
|
||||
from tkinter import ttk
|
||||
import threading
|
||||
|
||||
from app.chat_server.Server import Server
|
||||
from app.widgets import ClockLabel
|
||||
from app.ConfigMgr import ConfigMgr
|
||||
from app.widgets.ChatTab import ChatTab
|
||||
from app.widgets.UsageLabels import CPULabel, RAMLabel, BatteryLabel, NetworkLabel
|
||||
|
||||
# Evento para detener threads de manera segura
|
||||
stop_event = threading.Event()
|
||||
|
||||
def on_closing():
|
||||
"""Gestiona el cierre de la aplicación de manera segura."""
|
||||
# Detiene todos los threads relacionados con stop_event
|
||||
stop_event.set()
|
||||
|
||||
# Cierra la ventana principal
|
||||
root.quit()
|
||||
root.destroy()
|
||||
|
||||
def on_config_changed():
|
||||
"""Actualiza la configuración del servidor y nombre de usuario en el chat."""
|
||||
chat_frame.change_server_url(config_manager.config["Chat"]["server"])
|
||||
chat_frame.change_sender_name(config_manager.config["Chat"]["name"])
|
||||
|
||||
# Crea la ventana principal
|
||||
root = tk.Tk()
|
||||
root.title("Responsive Window") # Título de la ventana
|
||||
root.geometry("1150x700") # Tamaño inicial de la ventana
|
||||
|
||||
# Inicializa el gestor de configuración
|
||||
config_manager = ConfigMgr(root, config_changed_listener=on_config_changed)
|
||||
|
||||
# Inicializa el servidor de chat
|
||||
server = Server(host="localhost", port=2020, stop_event=stop_event)
|
||||
|
||||
# Configura la ventana principal para que sea responsive
|
||||
root.columnconfigure(0, weight=0) # Columna izquierda con tamaño fijo
|
||||
root.columnconfigure(1, weight=1) # Columna central ajustable
|
||||
root.columnconfigure(2, weight=0) # Columna derecha con tamaño fijo
|
||||
root.rowconfigure(0, weight=1) # Fila principal ajustable
|
||||
root.rowconfigure(1, weight=0) # Barra de estado con tamaño fijo
|
||||
|
||||
# Crea el menú superior
|
||||
menu_bar = Menu(root)
|
||||
|
||||
# Menú Archivo
|
||||
file_menu = Menu(menu_bar, tearoff=0)
|
||||
file_menu.add_command(label="New") # Comando Nuevo
|
||||
file_menu.add_command(label="Open") # Comando Abrir
|
||||
file_menu.add_separator() # Separador visual
|
||||
file_menu.add_command(label="Config", command=config_manager.display_config_window) # Configuración
|
||||
file_menu.add_command(label="Exit", command=on_closing) # Salir
|
||||
|
||||
# Menú Edición
|
||||
edit_menu = Menu(menu_bar, tearoff=0)
|
||||
edit_menu.add_command(label="Copy") # Comando Copiar
|
||||
edit_menu.add_command(label="Paste") # Comando Pegar
|
||||
|
||||
# Añade los menús al menú principal
|
||||
menu_bar.add_cascade(label="File", menu=file_menu)
|
||||
menu_bar.add_cascade(label="Edit", menu=edit_menu)
|
||||
|
||||
# Asigna el menú a la ventana principal
|
||||
root.config(menu=menu_bar)
|
||||
|
||||
# Crea los marcos laterales y central
|
||||
frame_left = tk.Frame(root, bg="lightblue", width=200) # Marco izquierdo
|
||||
frame_center = tk.Frame(root, bg="white") # Marco central
|
||||
chat_frame = ChatTab(root, chat_server_url=config_manager.config["Chat"]["server"],
|
||||
sender_name=config_manager.config["Chat"]["name"],
|
||||
stop_event=stop_event, width=200, bg="lightgreen") # Marco derecho para el chat
|
||||
|
||||
# Coloca los marcos en la cuadrícula
|
||||
frame_left.grid(row=0, column=0, sticky="ns") # Marco izquierdo
|
||||
frame_center.grid(row=0, column=1, sticky="nsew") # Marco central
|
||||
chat_frame.grid(row=0, column=2, sticky="ns") # Marco derecho
|
||||
|
||||
# Configura tamaños fijos para los marcos laterales
|
||||
frame_left.grid_propagate(False)
|
||||
chat_frame.grid_propagate(False)
|
||||
|
||||
# Divide el marco central en dos partes (superior ajustable e inferior fijo)
|
||||
frame_center.rowconfigure(0, weight=1) # Parte superior ajustable
|
||||
frame_center.rowconfigure(1, weight=0) # Parte inferior fija
|
||||
frame_center.columnconfigure(0, weight=1) # Ancho ajustable
|
||||
|
||||
# Crea sub-marcos dentro del marco central
|
||||
frame_top = tk.Frame(frame_center, bg="lightyellow") # Parte superior
|
||||
frame_bottom = tk.Frame(frame_center, bg="lightgray", height=100) # Parte inferior
|
||||
|
||||
# Coloca los sub-marcos en el marco central
|
||||
frame_top.grid(row=0, column=0, sticky="nsew") # Parte superior
|
||||
frame_bottom.grid(row=1, column=0, sticky="ew") # Parte inferior
|
||||
|
||||
# Fija el tamaño de la parte inferior
|
||||
frame_bottom.grid_propagate(False)
|
||||
|
||||
# Crea la barra de estado
|
||||
status_bar = tk.Label(root, text="Status bar", bg="lightgray", anchor="w") # Barra de estado
|
||||
status_bar.grid(row=1, column=0, columnspan=3, sticky="ew")
|
||||
|
||||
# Configura un cuaderno (notebook) para widgets
|
||||
style = ttk.Style()
|
||||
style.configure("CustomNotebook.TNotebook.Tab", font=("Arial", 12, "bold"))
|
||||
notebook = ttk.Notebook(frame_top, style="CustomNotebook.TNotebook")
|
||||
notebook.pack(fill="both", expand=True)
|
||||
|
||||
# Añade etiquetas de uso del sistema
|
||||
label_cpu = CPULabel(status_bar, bg="lightgreen", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event) # Uso de CPU
|
||||
label_ram = RAMLabel(status_bar, bg="lightcoral", font=("Helvetica", 10), relief="groove", anchor="center", width=10, stop_event=stop_event) # Uso de RAM
|
||||
label_battery = BatteryLabel(status_bar, bg="lightblue", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event) # Batería
|
||||
label_net = NetworkLabel(status_bar, text="Network", bg="lightpink", font=("Helvetica", 10), relief="groove", anchor="center", width=20, stop_event=stop_event) # Red
|
||||
label_time = ClockLabel(status_bar, font=("Helvetica", 12), bd=1, fg="darkblue", relief="sunken", anchor="center", width=20, stop_event=stop_event) # Reloj
|
||||
|
||||
# Coloca las etiquetas en la barra de estado
|
||||
label_cpu.pack(side="left", fill="both", expand=True)
|
||||
label_ram.pack(side="left", fill="both", expand=True)
|
||||
label_battery.pack(side="left", fill="both", expand=True)
|
||||
label_net.pack(side="left", fill="both", expand=True)
|
||||
label_time.pack(side="right", fill="both", expand=True)
|
||||
|
||||
# Configura la acción para el cierre de la ventana
|
||||
root.protocol("WM_DELETE_WINDOW", on_closing)
|
||||
|
||||
# Inicia la aplicación
|
||||
root.mainloop()
|
|
@ -0,0 +1,13 @@
|
|||
blinker==1.9.0
|
||||
certifi==2024.8.30
|
||||
charset-normalizer==3.4.0
|
||||
click==8.1.7
|
||||
Flask==3.1.0
|
||||
idna==3.10
|
||||
itsdangerous==2.2.0
|
||||
Jinja2==3.1.4
|
||||
MarkupSafe==3.0.2
|
||||
psutil==6.1.0
|
||||
requests==2.32.3
|
||||
urllib3==2.2.3
|
||||
Werkzeug==3.1.3
|
|
@ -0,0 +1,111 @@
|
|||
import time
|
||||
import tkinter as tk
|
||||
from tkinter import ttk
|
||||
from tkinter.ttk import Notebook
|
||||
from tkinter import Tk
|
||||
|
||||
import requests
|
||||
|
||||
from app.widgets.abc import ThreadedTab
|
||||
|
||||
|
||||
class ChatTab(ThreadedTab):
|
||||
|
||||
def __init__(self, root: Notebook | Tk, chat_server_url: str, sender_name: str, **kwargs):
|
||||
self.chat_server_url = chat_server_url
|
||||
self.sender_name = sender_name
|
||||
self.conn = False
|
||||
self.last_msg = 0
|
||||
super().__init__(root, **kwargs)
|
||||
|
||||
def task(self):
|
||||
try:
|
||||
self.get_messages()
|
||||
if not self.conn:
|
||||
self.connected()
|
||||
self.conn = True
|
||||
except requests.ConnectionError:
|
||||
self.disconnected()
|
||||
time.sleep(1)
|
||||
|
||||
def build(self):
|
||||
# Create the main frame for the chat interface
|
||||
self.chat_frame = tk.Frame(self)
|
||||
self.chat_frame.pack(fill="both", expand=True)
|
||||
|
||||
# Create the status label
|
||||
self.status_label = tk.Label(self.chat_frame, text="", font=("Helvetica", 16))
|
||||
self.status_label.pack(fill="x")
|
||||
|
||||
# Create the history frame with a scrollbar
|
||||
self.history_frame = tk.Frame(self.chat_frame)
|
||||
self.history_frame.pack(fill="both", expand=True)
|
||||
|
||||
self.history_canvas = tk.Canvas(self.history_frame)
|
||||
self.history_canvas.pack(side="left", fill="both", expand=True)
|
||||
|
||||
self.scrollbar = ttk.Scrollbar(self.history_frame, orient="vertical", command=self.history_canvas.yview)
|
||||
self.scrollbar.pack(side="right", fill="y")
|
||||
|
||||
self.history_canvas.configure(yscrollcommand=self.scrollbar.set)
|
||||
self.history_canvas.bind('<Configure>', lambda e: self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all")))
|
||||
|
||||
self.history_container = tk.Frame(self.history_canvas)
|
||||
self.history_container.bind("<Configure>", self.on_frame_configure)
|
||||
self.history_canvas.create_window((0, 0), window=self.history_container, anchor="nw")
|
||||
|
||||
# Create the input frame at the bottom
|
||||
self.input_frame = tk.Frame(self.chat_frame)
|
||||
self.input_frame.pack(fill="x")
|
||||
|
||||
self.message_entry = tk.Entry(self.input_frame)
|
||||
self.message_entry.pack(side="left", fill="x", expand=True, padx=5, pady=5)
|
||||
self.message_entry.bind("<Return>", lambda event: self.send_message()) # Bind Enter key to send_message
|
||||
|
||||
self.send_button = tk.Button(self.input_frame, text="Send", command=self.send_message)
|
||||
self.send_button.pack(side="right", padx=5, pady=5)
|
||||
|
||||
def on_frame_configure(self, event):
|
||||
self.history_canvas.configure(scrollregion=self.history_canvas.bbox("all"))
|
||||
|
||||
def send_message(self):
|
||||
message = self.message_entry.get()
|
||||
if message and self.conn:
|
||||
response = requests.post(f"{self.chat_server_url}/send_message", json={"content": message, "sender": self.sender_name})
|
||||
self.last_msg = response.json().get("id")
|
||||
self.display_message(self.sender_name, message)
|
||||
self.message_entry.delete(0, tk.END)
|
||||
|
||||
def display_message(self, sender, message):
|
||||
message_frame = tk.Frame(self.history_container, bg="lightgray", pady=5)
|
||||
message_frame.pack(fill="x", padx=5, pady=5)
|
||||
|
||||
message_label = tk.Label(message_frame, text=f"{sender}: {message}", anchor="w", justify="left", wraplength=300)
|
||||
message_label.pack(fill="x")
|
||||
|
||||
self.history_canvas.update_idletasks()
|
||||
self.history_canvas.yview_moveto(1)
|
||||
|
||||
def get_messages(self):
|
||||
response = requests.post(f"{self.chat_server_url}/get_messages", json={"last_id": self.last_msg})
|
||||
messages = response.json().get("messages")
|
||||
for message in messages:
|
||||
self.display_message(message["sender"], message["content"])
|
||||
|
||||
self.last_msg = messages[-1]["id"] if messages else self.last_msg
|
||||
|
||||
def connected(self):
|
||||
self.conn = True
|
||||
self.status_label.config(text="Connected to chat", fg="green")
|
||||
self.send_button.config(state="normal")
|
||||
|
||||
def disconnected(self):
|
||||
self.conn = False
|
||||
self.status_label.config(text="Disconnected from Chat", fg="red")
|
||||
self.send_button.config(state="disabled")
|
||||
|
||||
def change_sender_name(self, new_name: str):
|
||||
self.sender_name = new_name
|
||||
|
||||
def change_server_url(self, new_url: str):
|
||||
self.chat_server_url = new_url
|
|
@ -0,0 +1,16 @@
|
|||
import time
|
||||
from datetime import datetime
|
||||
|
||||
from .abc import ThreadedLabel
|
||||
|
||||
class ClockLabel(ThreadedLabel):
|
||||
|
||||
def task(self):
|
||||
now = datetime.now()
|
||||
day_of_week = now.strftime("%A")
|
||||
time_str = now.strftime("%H:%M:%S")
|
||||
date_str = now.strftime("%Y-%m-%d")
|
||||
label_text = f"{day_of_week}, {date_str} - {time_str}"
|
||||
try: self.config(text=label_text)
|
||||
except RuntimeError: pass
|
||||
time.sleep(0.5)
|
|
@ -0,0 +1,50 @@
|
|||
import time
|
||||
import psutil
|
||||
|
||||
from .abc import ThreadedLabel
|
||||
|
||||
class CPULabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
cpu_percent = psutil.cpu_percent()
|
||||
try: self.config(text=f'CPU: {cpu_percent}%')
|
||||
except RuntimeError: pass
|
||||
time.sleep(1)
|
||||
|
||||
class RAMLabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
memory = psutil.virtual_memory()
|
||||
try: self.config(text=f'RAM: {memory.percent}%')
|
||||
except RuntimeError: pass
|
||||
time.sleep(1)
|
||||
|
||||
class BatteryLabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
battery = psutil.sensors_battery()
|
||||
if battery is None:
|
||||
self.config(text='Battery: N/A')
|
||||
return
|
||||
battery_percent = battery.percent
|
||||
is_charging = battery.power_plugged
|
||||
time_left = battery.secsleft
|
||||
text = f'Battery: {battery_percent:.0f}%'
|
||||
if is_charging:
|
||||
text += ', Plugged in'
|
||||
else:
|
||||
text += f', ({time_left // 3600}h {time_left % 3600 // 60}m left)'
|
||||
|
||||
try: self.config(text=text)
|
||||
except RuntimeError: pass # Catch update on closed widget
|
||||
|
||||
time.sleep(1)
|
||||
|
||||
class NetworkLabel(ThreadedLabel):
|
||||
|
||||
def task(self, *args):
|
||||
network = psutil.net_io_counters()
|
||||
try: self.config(text=f'Net: {network.bytes_sent / 1024 / 1024:.2f} MB snt,'
|
||||
f' {network.bytes_recv / 1024 / 1024:.2f} MB rcv')
|
||||
except RuntimeError: pass # Catch update on closed widget
|
||||
time.sleep(1)
|
|
@ -0,0 +1,4 @@
|
|||
from .ClockLabel import ClockLabel
|
||||
from .UsageLabels import CPULabel, RAMLabel
|
||||
|
||||
__all__ = ['ClockLabel', 'CPULabel', 'RAMLabel']
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
|
@ -0,0 +1,22 @@
|
|||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
from threading import Thread
|
||||
from tkinter import Label
|
||||
|
||||
|
||||
class ThreadedLabel(ABC, Label):
|
||||
|
||||
def __init__(self, root: Label, stop_event: threading.Event, **kwargs):
|
||||
super().__init__(root, **kwargs)
|
||||
self.stop_event = stop_event
|
||||
self.declared_thread: Thread = threading.Thread(target=self.__loop)
|
||||
|
||||
self.declared_thread.start()
|
||||
|
||||
def __loop(self):
|
||||
while not self.stop_event.is_set():
|
||||
self.task()
|
||||
|
||||
@abstractmethod
|
||||
def task(self, *args):
|
||||
pass
|
|
@ -0,0 +1,26 @@
|
|||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
from tkinter import Tk
|
||||
from tkinter.ttk import Notebook
|
||||
from tkinter import Frame
|
||||
|
||||
class ThreadedTab(ABC, Frame):
|
||||
|
||||
def __init__(self, root: Notebook | Tk, stop_event: threading.Event, **kwargs):
|
||||
super().__init__(root, **kwargs)
|
||||
self.stop_event = stop_event
|
||||
self._thread = threading.Thread(target=self.__loop)
|
||||
self.build()
|
||||
self._thread.start()
|
||||
|
||||
def __loop(self):
|
||||
while not self.stop_event.is_set():
|
||||
self.task()
|
||||
|
||||
@abstractmethod
|
||||
def build(self):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def task(self, *args):
|
||||
raise NotImplementedError("Method not implemented")
|
|
@ -0,0 +1,4 @@
|
|||
from .ThreadedLabel import ThreadedLabel
|
||||
from .ThreadedTab import ThreadedTab
|
||||
|
||||
__all__ = ['ThreadedLabel', 'ThreadedTab']
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Reference in New Issue