Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Select an option

  • Save eros18123/62e88fc31f5c354f78c39ccd60350970 to your computer and use it in GitHub Desktop.

Select an option

Save eros18123/62e88fc31f5c354f78c39ccd60350970 to your computer and use it in GitHub Desktop.
chat pc servidor x pc cliente rede interna
#agente servidor.pyw
# agente.py (Versão Final com Notificação Customizada e 100% Confiável)
import socket
import subprocess
import sys
import os
import threading
import queue
import time
from PIL import Image, ImageDraw
from pystray import MenuItem as item, Icon
# --- Configurações ---
MEU_PAPEL = 'servidor' # <-- MUDE AQUI CONFORME O PC ('servidor' ou 'cliente')
# Portas de comunicação de rede
PORTA_CHAT = 55555
PORTA_COMANDO_REMOTO = 50001
PORTA_PRESENCA = 50000
# Portas de comunicação local (Agente <-> GUI)
PORTA_GUI_LOCAL = 50003
# Comandos e Mensagens
COMANDO_INICIAR_SERVIDOR = b'INICIE_O_SERVIDOR_DE_CHAT'
MSG_PRESENCA_SERVIDOR = b'SERVIDOR_ONLINE'
# Caminhos dos scripts
base_path = os.path.dirname(os.path.realpath(sys.argv[0]))
caminho_script_servidor = os.path.join(base_path, 'servidor.py')
caminho_script_cliente = os.path.join(base_path, 'cliente.py')
caminho_script_notificador = os.path.join(base_path, 'notificador.py') # <<< NOVO CAMINHO
HISTORICO_CHAT_ARQUIVO_SERVIDOR = os.path.join(base_path, "chat_history.log")
HISTORICO_CHAT_ARQUIVO_CLIENTE = os.path.join(base_path, "chat_history_client.log")
# --- Variáveis Globais de Estado ---
icon = None
socket_remoto = None
socket_gui = None
fila_para_gui = queue.Queue()
fila_mensagens_offline = []
# --- Funções de Persistência ---
def salvar_mensagem_no_historico(mensagem):
arquivo = HISTORICO_CHAT_ARQUIVO_SERVIDOR if MEU_PAPEL == 'servidor' else HISTORICO_CHAT_ARQUIVO_CLIENTE
try:
with open(arquivo, "a", encoding="utf-8") as f:
f.write(mensagem)
except Exception as e:
print(f"!!! ERRO AO SALVAR HISTÓRICO: {e}")
# --- Funções da Bandeja do Sistema (pystray) ---
def criar_imagem_estrela():
width, height = 64, 64
image = Image.new('RGBA', (width, height), (0, 0, 0, 0))
dc = ImageDraw.Draw(image)
dc.polygon([(32, 0), (40, 24), (64, 24), (44, 40), (52, 64), (32, 48), (12, 64), (20, 40), (0, 24), (24, 24)], fill='red')
return image
def iniciar_programa_chat(ignore_arg1=None, ignore_arg2=None):
script_path = caminho_script_servidor if MEU_PAPEL == 'servidor' else caminho_script_cliente
print(f"Iniciando GUI: {script_path}...")
subprocess.Popen(['pythonw', script_path])
def fechar_agente(icon, item):
print("Fechando agente...")
icon.stop()
os._exit(0)
# --- Funções de Notificação ---
# <<< FUNÇÃO TOTALMENTE REESCRITA PARA USAR NOSSO PRÓPRIO NOTIFICADOR >>>
def mostrar_notificacao(titulo, mensagem):
try:
print(f"--- CHAMANDO NOTIFICADOR CUSTOMIZADO: Título='{titulo}' ---")
# Executa o script notificador.py em segundo plano, passando o título e a mensagem
subprocess.Popen(['pythonw', caminho_script_notificador, titulo, mensagem])
except Exception as e:
print(f"!!!!!!!!!! ERRO CRÍTICO AO CHAMAR O SCRIPT NOTIFICADOR: {e} !!!!!!!!!!")
# --- LÓGICA DE REDE PRINCIPAL (código restante sem alterações) ---
def logica_servidor():
global socket_remoto
servidor = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
servidor.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
servidor.bind(('0.0.0.0', PORTA_CHAT))
servidor.listen(1)
print("Lógica do Servidor iniciada. Aguardando conexões...")
threading.Thread(target=anunciar_presenca_servidor, daemon=True).start()
while True:
conn, addr = servidor.accept()
print(f"Cliente {addr[0]} conectou.")
socket_remoto = conn
if fila_mensagens_offline:
for msg in fila_mensagens_offline:
try:
socket_remoto.send(msg.encode('utf-8'))
time.sleep(0.1)
except Exception as e:
print(f"Erro ao enviar msg offline: {e}")
fila_mensagens_offline.clear()
threading.Thread(target=receber_mensagens_remotas, args=(conn,), daemon=True).start()
def anunciar_presenca_servidor():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
while True:
if not socket_remoto:
sock.sendto(MSG_PRESENCA_SERVIDOR, ("<broadcast>", PORTA_PRESENCA))
time.sleep(3)
def logica_cliente():
global socket_remoto
while True:
servidor_ip = None
try:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.settimeout(3)
s.bind(('', PORTA_PRESENCA))
dados, addr = s.recvfrom(1024)
if dados == MSG_PRESENCA_SERVIDOR:
servidor_ip = addr[0]
except socket.timeout:
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM, socket.IPPROTO_UDP) as sock:
sock.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1)
sock.sendto(COMANDO_INICIAR_SERVIDOR, ("<broadcast>", PORTA_COMANDO_REMOTO))
time.sleep(5)
continue
if servidor_ip:
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.connect((servidor_ip, PORTA_CHAT))
socket_remoto = sock
print("Conectado ao servidor.")
receber_mensagens_remotas(sock)
except Exception as e:
print(f"Falha ao conectar: {e}. Tentando novamente em 5s.")
socket_remoto = None
time.sleep(5)
def receber_mensagens_remotas(sock):
global socket_remoto
while True:
try:
msg = sock.recv(1024).decode('utf-8')
if not msg: break
print(f"Mensagem remota recebida: {msg}")
titulo = "Nova Mensagem do Cliente" if MEU_PAPEL == 'servidor' else "Nova Mensagem do Servidor"
mostrar_notificacao(titulo, msg)
prefixo = "Cliente: " if MEU_PAPEL == 'servidor' else "Servidor: "
mensagem_completa = f"{prefixo}{msg}\n"
fila_para_gui.put(mensagem_completa)
salvar_mensagem_no_historico(mensagem_completa)
except:
break
print("Conexão remota perdida.")
socket_remoto = None
sock.close()
def servidor_gui_local():
global socket_gui
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('127.0.0.1', PORTA_GUI_LOCAL))
s.listen(1)
print(f"Agente escutando a GUI local na porta {PORTA_GUI_LOCAL}.")
while True:
conn, addr = s.accept()
print("GUI conectou ao agente.")
socket_gui = conn
threading.Thread(target=enviar_para_gui, daemon=True).start()
threading.Thread(target=receber_da_gui, daemon=True).start()
def enviar_para_gui():
while socket_gui:
try:
msg = fila_para_gui.get(timeout=1)
socket_gui.sendall(msg.encode('utf-8'))
except queue.Empty:
continue
except Exception as e:
print(f"Erro ao enviar para GUI: {e}")
break
def receber_da_gui():
global socket_gui
while True:
try:
msg_bytes = socket_gui.recv(1024)
if not msg_bytes: break
msg = msg_bytes.decode('utf-8')
print(f"Recebido da GUI para enviar: {msg}")
mensagem_completa = f"Você: {msg}\n"
fila_para_gui.put(mensagem_completa)
salvar_mensagem_no_historico(mensagem_completa)
if socket_remoto:
socket_remoto.sendall(msg_bytes)
else:
fila_mensagens_offline.append(msg)
fila_para_gui.put(f"Você (guardado): {msg}\n")
except Exception as e:
print(f"Erro ao receber da GUI: {e}")
break
print("GUI desconectou.")
socket_gui = None
def escutar_comandos_remotos():
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
s.bind(('', PORTA_COMANDO_REMOTO))
while True:
dados, _ = s.recvfrom(1024)
if dados == COMANDO_INICIAR_SERVIDOR and MEU_PAPEL == 'servidor':
iniciar_programa_chat()
if __name__ == '__main__':
threading.Thread(target=servidor_gui_local, daemon=True).start()
if MEU_PAPEL == 'servidor':
threading.Thread(target=logica_servidor, daemon=True).start()
threading.Thread(target=escutar_comandos_remotos, daemon=True).start()
elif MEU_PAPEL == 'cliente':
threading.Thread(target=logica_cliente, daemon=True).start()
else:
print("ERRO: MEU_PAPEL deve ser 'servidor' ou 'cliente'.")
sys.exit(1)
menu = (item('Abrir Chat', iniciar_programa_chat, default=True), item('Fechar', fechar_agente))
icon = Icon("ChatApp", criar_imagem_estrela(), f"Chat App ({MEU_PAPEL})", menu)
icon.run()
@eros18123
Copy link
Author

#servidor.py

servidor.py (Versão Definitiva e Robusta)

import socket
import threading
import tkinter as tk
from tkinter import scrolledtext, messagebox
import queue
import os
import sys
import time

--- Configurações ---

PORTA_GUI_LOCAL = 50003
HISTORICO_CHAT_ARQUIVO = "chat_history.log"
SINGLETON_LOCK_PORT = 50004

class ChatGUI:
def init(self):
self.lock_socket = None
self.agente_socket = None
self.root = None
self.gui_queue = queue.Queue()

    # 1. Tenta criar o lock de instância única
    if not self.check_singleton():
        # Se falhar, é porque outra janela já está aberta. Avisa e sai.
        messagebox.showwarning("Aviso", "A janela do Chat Servidor já está aberta.")
        sys.exit(0)

    # 2. Tenta se conectar ao agente
    self.agente_socket = self.conectar_ao_agente()
    if not self.agente_socket:
        # Se falhar, avisa o usuário e limpa o lock antes de sair.
        messagebox.showerror("Erro de Conexão", "Não foi possível conectar ao Agente.\n\nVerifique se o 'agente.py' está em execução.")
        self.cleanup_and_exit()

    # 3. Se tudo deu certo, cria e roda a GUI
    self.create_widgets()
    self.start_threads()
    self.root.mainloop()

def check_singleton(self):
    """Tenta ocupar uma porta para garantir instância única. Retorna True se for a primeira instância."""
    try:
        self.lock_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
        self.lock_socket.bind(("127.0.0.1", SINGLETON_LOCK_PORT))
        return True
    except OSError:
        return False

def conectar_ao_agente(self):
    """Tenta se conectar ao agente local com várias tentativas."""
    for _ in range(10):  # Tenta por 2 segundos
        try:
            sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
            sock.connect(('127.0.0.1', PORTA_GUI_LOCAL))
            return sock
        except ConnectionRefusedError:
            time.sleep(0.2)
    return None

def create_widgets(self):
    """Cria todos os elementos da interface gráfica."""
    self.root = tk.Tk()
    self.root.title("Chat Servidor")
    self.root.minsize(400, 300)
    self.root.protocol("WM_DELETE_WINDOW", self.on_closing)
    
    FONT_CONFIG = ("Arial", 16)

    frame_entrada = tk.Frame(self.root)
    frame_entrada.pack(side=tk.BOTTOM, fill=tk.X, padx=10, pady=10)
    
    self.entrada_msg = tk.Entry(frame_entrada, width=50, font=FONT_CONFIG)
    self.entrada_msg.pack(side=tk.LEFT, fill=tk.X, expand=True)
    self.entrada_msg.bind("<Return>", self.enviar_mensagem_para_agente)
    
    botao_enviar = tk.Button(frame_entrada, text="Enviar", command=self.enviar_mensagem_para_agente, font=("Arial", 12))
    botao_enviar.pack(side=tk.RIGHT, padx=5)

    self.area_chat = scrolledtext.ScrolledText(self.root, state=tk.DISABLED, wrap=tk.WORD, font=FONT_CONFIG)
    self.area_chat.pack(padx=10, pady=(10, 0), fill=tk.BOTH, expand=True)

def start_threads(self):
    """Inicia as threads de comunicação e o processador da fila da GUI."""
    threading.Thread(target=self.receber_mensagens_do_agente, daemon=True).start()
    self.carregar_historico()
    self.processar_fila_gui()

def receber_mensagens_do_agente(self):
    while True:
        try:
            msg = self.agente_socket.recv(4096).decode('utf-8')
            if not msg: break
            self.gui_queue.put(msg)
        except:
            break
    self.gui_queue.put("--- Conexão com o agente perdida. ---\n")

def enviar_mensagem_para_agente(self, event=None):
    msg = self.entrada_msg.get()
    if msg:
        try:
            self.agente_socket.send(msg.encode('utf-8'))
            self.entrada_msg.delete(0, tk.END)
        except Exception as e:
            self.gui_queue.put(f"--- Erro ao enviar: {e} ---\n")

def carregar_historico(self):
    if os.path.exists(HISTORICO_CHAT_ARQUIVO):
        with open(HISTORICO_CHAT_ARQUIVO, "r", encoding="utf-8") as f:
            for line in f:
                self.gui_queue.put(line)

def processar_fila_gui(self):
    try:
        while not self.gui_queue.empty():
            message = self.gui_queue.get_nowait()
            self.area_chat.config(state=tk.NORMAL)
            self.area_chat.insert(tk.END, message)
            self.area_chat.yview(tk.END)
            self.area_chat.config(state=tk.DISABLED)
    finally:
        self.root.after(100, self.processar_fila_gui)

def on_closing(self):
    """Função chamada quando a janela é fechada para limpar tudo."""
    self.cleanup_and_exit()

def cleanup_and_exit(self):
    """Fecha sockets, destrói a janela e sai do programa."""
    if self.agente_socket:
        self.agente_socket.close()
    if self.lock_socket:
        self.lock_socket.close() # Libera a trava!
    if self.root:
        self.root.destroy()
    sys.exit(0)

if name == "main":
app = ChatGUI()

@eros18123
Copy link
Author

#notificador.py

notificador.py

import tkinter as tk
import sys

--- Configurações ---

LARGURA_NOTIFICACAO = 350
ALTURA_NOTIFICACAO = 100
TEMPO_NA_TELA_MS = 5000 # 5 segundos
COR_FUNDO = "#333333"
COR_TEXTO_TITULO = "#FFFFFF"
COR_TEXTO_MSG = "#DDDDDD"

class NotificationWindow:
def init(self, title, message):
self.root = tk.Tk()
self.root.overrideredirect(True) # Remove a barra de título e bordas
self.root.attributes('-topmost', True) # Mantém a janela sempre na frente
self.root.config(bg=COR_FUNDO, bd=1, relief="solid")

    # Posiciona a janela no canto inferior direito
    screen_width = self.root.winfo_screenwidth()
    screen_height = self.root.winfo_screenheight()
    x = screen_width - LARGURA_NOTIFICACAO - 20
    y = screen_height - ALTURA_NOTIFICACAO - 60 # Um pouco acima da barra de tarefas
    self.root.geometry(f'{LARGURA_NOTIFICACAO}x{ALTURA_NOTIFICACAO}+{x}+{y}')

    # Adiciona os textos
    title_label = tk.Label(self.root, text=title, font=("Arial", 12, "bold"), bg=COR_FUNDO, fg=COR_TEXTO_TITULO, wraplength=LARGURA_NOTIFICACAO-20, justify="left")
    title_label.pack(pady=(10, 2), padx=10, anchor="w")

    message_label = tk.Label(self.root, text=message, font=("Arial", 10), bg=COR_FUNDO, fg=COR_TEXTO_MSG, wraplength=LARGURA_NOTIFICACAO-20, justify="left")
    message_label.pack(pady=2, padx=10, anchor="w")

    # Faz a janela fechar ao ser clicada
    self.root.bind("<Button-1>", self.close_window)
    title_label.bind("<Button-1>", self.close_window)
    message_label.bind("<Button-1>", self.close_window)

    # Agenda o fechamento automático
    self.root.after(TEMPO_NA_TELA_MS, self.close_window)
    
    self.root.mainloop()

def close_window(self, event=None):
    self.root.destroy()

if name == "main":
# Pega o título e a mensagem dos argumentos da linha de comando
if len(sys.argv) >= 3:
popup_title = sys.argv[1]
popup_message = " ".join(sys.argv[2:])
NotificationWindow(popup_title, popup_message)

@eros18123
Copy link
Author

#agente cliente.pyw

#obs: é so copiar do agente servidor.pyw e mudar onde tem servidor para cliente

pode ser necessario algumas instalaçoes via cmd, alem do python

pip install pystray Pillow win10toast-persist
py -m pip uninstall win10toast-persist
py -m pip install win10toast-persist
py -m pip install --upgrade pip

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment