# main.py
#
# Copyright 2024 Lucas Fryzek
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see .
#
# SPDX-License-Identifier: GPL-3.0-or-later
import sys
import os
import gi
gi.require_version('Gtk', '4.0')
gi.require_version('Adw', '1')
from gi.repository import Gtk, Gio, Adw
from .window import WeegtkWindow
from .network import Network
from weegtk import protocol
from weegtk import config
from .chat import WeegtkChat
from .preferences import WeegtkPreferences
class WeegtkApplication(Adw.Application):
"""The main application singleton class."""
def __init__(self):
super().__init__(application_id='com.fryzekconcepts.weegtk',
flags=Gio.ApplicationFlags.DEFAULT_FLAGS)
self.create_action('quit', lambda *_: self.quit(), ['q'])
self.create_action('about', self.on_about_action)
self.create_action('preferences', self.on_preferences_action)
self.network = Network()
self.network.connect("status_changed", self._network_status_changed)
self.network.connect("message_from_weechat", self._network_weechat_msg)
conf = config.read()
#self.network.connect_weechat("localhost", "9001", False, relay_pw, None, "")
self.network.connect_weechat_ssh(conf["ssh"]["host"],
conf["ssh"]["port"],
conf["ssh"]["username"],
conf["ssh"]["key"],
conf["relay"]["hostname"],
conf["relay"]["port"],
conf["relay"]["password"])
self.buffers = []
def _network_status_changed(self, source_object, status, extra):
"""Called when the network status has changed."""
# TODO handle this
#if self.config.getboolean('look', 'statusbar'):
# self.statusBar().showMessage(status)
#self.network.debug_print(0, '', status, forcecolor='#0000AA')
#self.network_status_set(status)
print(f"Status is {status}")
def _network_weechat_msg(self, source_object, message):
"""Called when a message is received from WeeChat."""
#print(f"Message {message}")
try:
proto = protocol.Protocol()
message = proto.decode(message.get_data())
# TODO figure this out
#if message.uncompressed:
# self.network.debug_print(
# 0, '==>',
# 'message uncompressed (%d bytes):\n%s'
# % (message.size_uncompressed,
# protocol.hex_and_ascii(message.uncompressed, 20)),
# forcecolor='#008800')
#self.network.debug_print(0, '', 'Message: %s' % message)
#print(f"parsed message is f{message}")
self.parse_message(message)
except:
print('Error while decoding message from WeeChat:\n%s'
% traceback.format_exc())
self.net.disconnect_weechat()
def do_activate(self):
"""Called when the application is activated.
We raise the application's main window, creating it if
necessary.
"""
win = self.props.active_window
if not win:
win = WeegtkWindow(application=self)
win.present()
def on_about_action(self, *args):
"""Callback for the app.about action."""
about = Adw.AboutDialog(application_name='weegtk',
application_icon='com.fryzekconcepts.weegtk',
developer_name='Lucas Fryzek',
version='0.1.0',
developers=['Lucas Fryzek '],
copyright='© 2024 Lucas Fryzek')
# Translators: Replace "translator-credits" with your name/username, and optionally an email or URL.
about.set_translator_credits(_('translator-credits'))
about.add_credit_section("QWeechat", ["Sébastien Helleu "])
about.present(self.props.active_window)
def on_preferences_action(self, widget, _):
"""Callback for the app.preferences action."""
pref = WeegtkPreferences()
pref.present(self.props.active_window)
def create_action(self, name, callback, shortcuts=None):
"""Add an application action.
Args:
name: the name of the action
callback: the function to be called when the action is
activated
shortcuts: an optional list of accelerators
"""
action = Gio.SimpleAction.new(name, None)
action.connect("activate", callback)
self.add_action(action)
if shortcuts:
self.set_accels_for_action(f"app.{name}", shortcuts)
def _parse_handshake(self, message):
"""Parse a WeeChat message with handshake response."""
for obj in message.objects:
if obj.objtype != 'htb':
continue
self.network.init_with_handshake(obj.value)
break
def _parse_listbuffers(self, message):
"""Parse a WeeChat message with list of buffers."""
# TODO handle gui for this
for obj in message.objects:
if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
continue
#self.list_buffers.clear()
#while self.stacked_buffers.count() > 0:
# buf = self.stacked_buffers.widget(0)
# self.stacked_buffers.removeWidget(buf)
self.buffers = []
for item in obj.value['items']:
buf = self.create_buffer(item)
self.insert_buffer(len(self.buffers), buf)
#self.list_buffers.setCurrentRow(0)
#self.buffers[0].widget.input.setFocus()
def _parse_line(self, message):
"""Parse a WeeChat message with a buffer line."""
for obj in message.objects:
lines = []
if obj.objtype != 'hda' or obj.value['path'][-1] != 'line_data':
continue
for item in obj.value['items']:
if message.msgid == 'listlines':
ptrbuf = item['__path'][0]
else:
ptrbuf = item['buffer']
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == ptrbuf]
if index:
lines.append(
(index[0],
(item['date'], item['prefix'],
item['message']))
)
if message.msgid == 'listlines':
lines.reverse()
for line in lines:
self.buffers[line[0]].display(*line[1])
def _parse_nicklist(self, message):
"""Parse a WeeChat message with a buffer nicklist."""
# TODO implement nicklist for chat
# buffer_refresh = {}
# for obj in message.objects:
# if obj.objtype != 'hda' or \
# obj.value['path'][-1] != 'nicklist_item':
# continue
# group = '__root'
# for item in obj.value['items']:
# index = [i for i, b in enumerate(self.buffers)
# if b.pointer() == item['__path'][0]]
# if index:
# if not index[0] in buffer_refresh:
# self.buffers[index[0]].nicklist = {}
# buffer_refresh[index[0]] = True
# if item['group']:
# group = item['name']
# self.buffers[index[0]].nicklist_add_item(
# group, item['group'], item['prefix'], item['name'],
# item['visible'])
# for index in buffer_refresh:
# self.buffers[index].nicklist_refresh()
def _parse_nicklist_diff(self, message):
"""Parse a WeeChat message with a buffer nicklist diff."""
buffer_refresh = {}
for obj in message.objects:
if obj.objtype != 'hda' or \
obj.value['path'][-1] != 'nicklist_item':
continue
group = '__root'
for item in obj.value['items']:
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == item['__path'][0]]
if not index:
continue
buffer_refresh[index[0]] = True
if item['_diff'] == ord('^'):
group = item['name']
elif item['_diff'] == ord('+'):
self.buffers[index[0]].nicklist_add_item(
group, item['group'], item['prefix'], item['name'],
item['visible'])
elif item['_diff'] == ord('-'):
self.buffers[index[0]].nicklist_remove_item(
group, item['group'], item['name'])
elif item['_diff'] == ord('*'):
self.buffers[index[0]].nicklist_update_item(
group, item['group'], item['prefix'], item['name'],
item['visible'])
for index in buffer_refresh:
self.buffers[index].nicklist_refresh()
def _parse_buffer_opened(self, message):
"""Parse a WeeChat message with a new buffer (opened)."""
for obj in message.objects:
if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
continue
for item in obj.value['items']:
buf = self.create_buffer(item)
index = self.find_buffer_index_for_insert(item['next_buffer'])
self.insert_buffer(index, buf)
def _parse_buffer(self, message):
"""Parse a WeeChat message with a buffer event
(anything except a new buffer).
"""
for obj in message.objects:
if obj.objtype != 'hda' or obj.value['path'][-1] != 'buffer':
continue
for item in obj.value['items']:
index = [i for i, b in enumerate(self.buffers)
if b.pointer() == item['__path'][0]]
if not index:
continue
index = index[0]
if message.msgid == '_buffer_type_changed':
self.buffers[index].data['type'] = item['type']
elif message.msgid in ('_buffer_moved', '_buffer_merged',
'_buffer_unmerged'):
buf = self.buffers[index]
buf.data['number'] = item['number']
self.remove_buffer(index)
index2 = self.find_buffer_index_for_insert(
item['next_buffer'])
self.insert_buffer(index2, buf)
elif message.msgid == '_buffer_renamed':
self.buffers[index].data['full_name'] = item['full_name']
self.buffers[index].data['short_name'] = item['short_name']
elif message.msgid == '_buffer_title_changed':
self.buffers[index].data['title'] = item['title']
self.buffers[index].update_title()
elif message.msgid == '_buffer_cleared':
self.buffers[index].widget.chat.clear()
elif message.msgid.startswith('_buffer_localvar_'):
self.buffers[index].data['local_variables'] = \
item['local_variables']
self.buffers[index].update_prompt()
elif message.msgid == '_buffer_closing':
self.remove_buffer(index)
def parse_message(self, message):
"""Parse a WeeChat message."""
if message.msgid.startswith('debug'):
self.network.debug_print(0, '', '(debug message, ignored)')
elif message.msgid == 'handshake':
self._parse_handshake(message)
elif message.msgid == 'listbuffers':
self._parse_listbuffers(message)
elif message.msgid in ('listlines', '_buffer_line_added'):
self._parse_line(message)
elif message.msgid in ('_nicklist', 'nicklist'):
self._parse_nicklist(message)
elif message.msgid == '_nicklist_diff':
self._parse_nicklist_diff(message)
elif message.msgid == '_buffer_opened':
self._parse_buffer_opened(message)
elif message.msgid.startswith('_buffer_'):
self._parse_buffer(message)
elif message.msgid == '_upgrade':
self.network.desync_weechat()
elif message.msgid == '_upgrade_ended':
self.network.sync_weechat()
else:
print(f"Unknown message with id {message.msgid}")
def create_buffer(self, item):
"""Create a new buffer."""
buf = WeegtkChat(data=item)
buf.connect("buffer_input", self.buffer_input)
return buf
def insert_buffer(self, index, buf):
"""Insert a buffer in list."""
self.buffers.insert(index, buf)
buf_name = buf.data['short_name']
# Only make chats visible on main screen
if True or buf.is_chat():
test_label = Gtk.Label(label="Hello")
self.props.active_window.stack.add_titled(buf, name=buf_name, title=buf_name)
def buffer_input(self, source_object, full_name, text):
if self.network.is_connected():
message = f"input {full_name} {text}\n"
self.network.send_to_weechat(message)
def main(version):
"""The application's entry point."""
app = WeegtkApplication()
return app.run(sys.argv)