summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorTreeki <treeki@gmail.com>2014-02-14 01:26:01 +0100
committerTreeki <treeki@gmail.com>2014-02-14 01:26:01 +0100
commitf07f7dd153d6c605e98143c594d2a578457a724e (patch)
tree5f40de6c2bc9a8fbff9d816d86456cadcb26c05f
parentd6beb37b20cd631d2fb129339e1d23a148454917 (diff)
downloadbounce4-f07f7dd153d6c605e98143c594d2a578457a724e.tar.gz
bounce4-f07f7dd153d6c605e98143c594d2a578457a724e.zip
add richtext support and message priorities
-rwxr-xr-xbouncer/build.sh2
-rwxr-xr-xbouncer/build_static.sh2
-rw-r--r--bouncer/core.h4
-rw-r--r--bouncer/richtext.cpp176
-rw-r--r--bouncer/richtext.h92
-rw-r--r--bouncer/window.cpp114
-rw-r--r--python_client.py109
7 files changed, 429 insertions, 70 deletions
diff --git a/bouncer/build.sh b/bouncer/build.sh
index 092cca1..be4826d 100755
--- a/bouncer/build.sh
+++ b/bouncer/build.sh
@@ -2,7 +2,7 @@
mkdir -p binary
NETCODE="socketcommon.cpp client.cpp mobileclient.cpp server.cpp ircserver.cpp netcore.cpp"
-SOURCES="$NETCODE main.cpp window.cpp dns.cpp ini.cpp"
+SOURCES="$NETCODE main.cpp window.cpp dns.cpp ini.cpp richtext.cpp"
FLAGS="-std=c++11 -DUSE_GNUTLS -lgnutls -pthread -g"
g++ -o binary/nb4 $FLAGS $SOURCES
diff --git a/bouncer/build_static.sh b/bouncer/build_static.sh
index e75d469..5807c1f 100755
--- a/bouncer/build_static.sh
+++ b/bouncer/build_static.sh
@@ -2,7 +2,7 @@
mkdir -p binary
NETCODE="socketcommon.cpp client.cpp mobileclient.cpp server.cpp ircserver.cpp netcore.cpp"
-SOURCES="$NETCODE main.cpp window.cpp dns.cpp ini.cpp"
+SOURCES="$NETCODE main.cpp window.cpp dns.cpp ini.cpp richtext.cpp"
FLAGS="-static -static-libgcc -static-libstdc++ -std=c++11 -pthread"
g++ -o binary/nb4_static $FLAGS $SOURCES
diff --git a/bouncer/core.h b/bouncer/core.h
index 1a13a1d..28b99f2 100644
--- a/bouncer/core.h
+++ b/bouncer/core.h
@@ -66,7 +66,7 @@ public:
virtual void handleUserInput(const char *str) { }
virtual void handleUserClosed();
- void pushMessage(const char *str);
+ void pushMessage(const char *str, int priority = 0);
void notifyWindowRename();
};
@@ -109,6 +109,8 @@ public:
void handleTopic(const UserRef &user, const char *message);
void handleTopicInfo(const char *user, int timestamp);
+ void outputUserMessage(const char *nick, const char *message, bool isAction);
+
char getEffectivePrefixChar(const char *nick) const;
void disconnected();
diff --git a/bouncer/richtext.cpp b/bouncer/richtext.cpp
new file mode 100644
index 0000000..22c6330
--- /dev/null
+++ b/bouncer/richtext.cpp
@@ -0,0 +1,176 @@
+#include "richtext.h"
+
+static int matchIRCColourString(const char *str, int pos, int *result) {
+ // First, try to match a single digit.
+ if (str[pos] >= '0' && str[pos] <= '9') {
+ int first = str[pos] - '0';
+ if (str[pos + 1] >= '0' && str[pos + 1] <= '9') {
+ int second = str[pos + 1] - '0';
+
+ int attempt = (first * 10) + second;
+ if (attempt < 0 || attempt > 15) {
+ // Invalid number, give up here
+ return pos;
+ } else {
+ // Valid number!
+ *result = attempt;
+ return pos + 2;
+ }
+ } else {
+ // Second character was not a digit, so
+ // let's take the colour we *did* parse as is
+ *result = first;
+ return pos + 1;
+ }
+ } else {
+ // First character was not a digit, so pass entirely.
+ return pos;
+ }
+}
+
+static int matchIRCColourPair(const char *str, int pos, int *whichFG, int *whichBG) {
+ // str[pos] is the first character following the
+ // \x03 colour code.
+
+ *whichFG = -1;
+ *whichBG = -1;
+
+ pos = matchIRCColourString(str, pos, whichFG);
+ if (*whichFG != -1) {
+ if (str[pos] == ',') {
+ pos = matchIRCColourString(str, pos + 1, whichBG);
+ }
+ }
+
+ return pos;
+}
+
+
+void RichTextBuilder::appendIRC(const char *str) {
+ // We're up for fun here!
+ const int colLayer = COL_LEVEL_IRC;
+
+ bool b = false, i = false, u = false;
+ int activeFG = COL_DEFAULT_FG, activeBG = COL_DEFAULT_BG;
+
+ int pos = 0;
+ int len = strlen(str);
+
+ while (pos < len) {
+ char ch = str[pos];
+
+ if (ch < 0 || ch == 7 || ch == 10 || ch >= 0x20) {
+ // Pass this character through unfiltered.
+ writeU8(ch);
+ pos++;
+ continue;
+ }
+
+ // IRC control code, but what?
+ if (ch == 2) {
+ b = !b;
+ if (b)
+ bold();
+ else
+ endBold();
+ } else if (ch == 0x1D) {
+ i = !i;
+ if (i)
+ italic();
+ else
+ endItalic();
+ } else if (ch == 0x1F) {
+ u = !u;
+ if (u)
+ underline();
+ else
+ endUnderline();
+ } else if (ch == 0xF) {
+ // Reset all formatting
+ if (b) {
+ endBold();
+ b = false;
+ }
+ if (i) {
+ endItalic();
+ i = false;
+ }
+ if (u) {
+ endUnderline();
+ u = false;
+ }
+ if (activeFG != COL_DEFAULT_FG) {
+ endForeground(colLayer);
+ activeFG = COL_DEFAULT_FG;
+ }
+ if (activeBG != COL_DEFAULT_BG) {
+ endBackground(colLayer);
+ activeBG = COL_DEFAULT_BG;
+ }
+ } else if (ch == 0x16) {
+ // Reverse
+ int swap = activeBG;
+ activeBG = activeFG;
+ activeFG = swap;
+
+ if (activeFG == COL_DEFAULT_FG)
+ endForeground(colLayer);
+ else
+ foreground(colLayer, activeFG);
+
+ if (activeBG == COL_DEFAULT_BG)
+ endBackground(colLayer);
+ else
+ background(colLayer, activeBG);
+
+ } else if (ch == 3) {
+ // Colours!
+
+ int whichFG = -1, whichBG = -1;
+
+ pos = matchIRCColourPair(str, pos + 1, &whichFG, &whichBG);
+
+ if (whichFG == -1) {
+ if (activeFG != COL_DEFAULT_FG) {
+ activeFG = COL_DEFAULT_FG;
+ endForeground(colLayer);
+ }
+ } else {
+ activeFG = whichFG;
+ foreground(colLayer, whichFG);
+ }
+
+ if (whichBG == -1) {
+ if (whichFG == -1 && activeBG != COL_DEFAULT_BG) {
+ activeBG = COL_DEFAULT_BG;
+ endBackground(colLayer);
+ }
+ } else {
+ activeBG = whichBG;
+ background(colLayer, whichBG);
+ }
+
+ continue;
+ }
+ pos++;
+ }
+
+ // Clean up any leftover state.
+ if (b)
+ endBold();
+ if (i)
+ endItalic();
+ if (u)
+ endUnderline();
+ if (activeFG != COL_DEFAULT_FG)
+ endForeground(colLayer);
+ if (activeBG != COL_DEFAULT_BG)
+ endBackground(colLayer);
+}
+
+const char *RichTextBuilder::c_str() {
+ if (size() == 0 || data()[size() - 1] != 0)
+ writeU8(0);
+
+ return data();
+}
diff --git a/bouncer/richtext.h b/bouncer/richtext.h
new file mode 100644
index 0000000..608a1d6
--- /dev/null
+++ b/bouncer/richtext.h
@@ -0,0 +1,92 @@
+#include "buffer.h"
+
+enum ColourPresets {
+ COL_IRC_WHITE = 0,
+ COL_IRC_BLACK = 1,
+ COL_IRC_BLUE = 2,
+ COL_IRC_GREEN = 3,
+ COL_IRC_RED = 4,
+ COL_IRC_BROWN = 5,
+ COL_IRC_PURPLE = 6,
+ COL_IRC_ORANGE = 7,
+ COL_IRC_YELLOW = 8,
+ COL_IRC_LIME = 9,
+ COL_IRC_TEAL = 10,
+ COL_IRC_CYAN = 11,
+ COL_IRC_LIGHT_BLUE = 12,
+ COL_IRC_PINK = 13,
+ COL_IRC_GREY = 14,
+ COL_IRC_LIGHT_GREY = 15,
+
+ COL_DEFAULT_FG = 16,
+ COL_DEFAULT_BG = 17,
+
+ COL_ACTION = 18,
+ COL_JOIN = 19,
+ COL_PART = 20,
+ COL_QUIT = 21,
+ COL_KICK = 22,
+ COL_CHANNEL_NOTICE = 23,
+};
+
+enum ColourLevels {
+ COL_LEVEL_BASE = 0,
+ COL_LEVEL_IRC = 1,
+ COL_LEVEL_NICK = 2,
+};
+
+class RichTextBuilder : public Buffer {
+public:
+ void bold() { writeU8(1); }
+ void endBold() { writeU8(2); }
+
+ void italic() { writeU8(3); }
+ void endItalic() { writeU8(4); }
+
+ void underline() { writeU8(5); }
+ void endUnderline() { writeU8(6); }
+
+ void colour(bool background, int layer, int r, int g, int b) {
+ writeU8(0x10 + (background ? 4 : 0) + layer);
+ writeU8((r==0)?2:(r&254));
+ writeU8((g==0)?1:g);
+ writeU8((b==0)?1:b);
+ }
+ void colour(bool background, int layer, int col) {
+ writeU8(0x10 + (background ? 4 : 0) + layer);
+ writeU8((col << 1) | 1);
+ }
+
+ void endColour(bool background, int layer) {
+ writeU8(0x18 + (background ? 4 : 0) + layer);
+ }
+
+ void foreground(int layer, int r, int g, int b) {
+ colour(false, layer, r, g, b);
+ }
+ void foreground(int layer, int col) {
+ colour(false, layer, col);
+ }
+ void endForeground(int layer) {
+ endColour(false, layer);
+ }
+
+ void background(int layer, int r, int g, int b) {
+ colour(true, layer, r, g, b);
+ }
+ void background(int layer, int col) {
+ colour(true, layer, col);
+ }
+ void endBackground(int layer) {
+ endColour(true, layer);
+ }
+
+ void append(const char *str) {
+ Buffer::append(str, strlen(str));
+ }
+
+ void appendIRC(const char *str);
+
+ const char *c_str();
+};
+
diff --git a/bouncer/window.cpp b/bouncer/window.cpp
index 88e8247..6085304 100644
--- a/bouncer/window.cpp
+++ b/bouncer/window.cpp
@@ -1,4 +1,5 @@
#include "core.h"
+#include "richtext.h"
Window::Window(NetCore *_core) {
core = _core;
@@ -29,7 +30,7 @@ void Window::notifyWindowRename() {
Packet::B2C_WINDOW_RENAME, packet);
}
-void Window::pushMessage(const char *str) {
+void Window::pushMessage(const char *str, int priority) {
messages.push_back(str);
bool createdPacket = false;
@@ -39,6 +40,7 @@ void Window::pushMessage(const char *str) {
if (core->clients[i]->isAuthed()) {
if (!createdPacket) {
packet.writeU32(id);
+ packet.writeU8(priority);
packet.writeStr(str);
createdPacket = true;
}
@@ -198,18 +200,7 @@ void Channel::handleUserInput(const char *str) {
if (str[0] == '/') {
if (strncmp(str, "/me ", 4) == 0) {
- // The duplication of code between here and
- // handlePrivmsg is ugly. TODO: fixme.
- char prefix[2];
- prefix[0] = getEffectivePrefixChar(server->currentNick);
- prefix[1] = 0;
-
- snprintf(msgBuf, sizeof(msgBuf),
- "* %s%s %s",
- prefix,
- server->currentNick,
- &str[4]);
- pushMessage(msgBuf);
+ outputUserMessage(server->currentNick, &str[4], /*isAction=*/true);
snprintf(msgBuf, sizeof(msgBuf),
"PRIVMSG %s :\x01" "ACTION %s\x01",
@@ -218,18 +209,7 @@ void Channel::handleUserInput(const char *str) {
server->sendLine(msgBuf);
}
} else {
- // Aaaand this is also pretty ugly ><;;
- // TODO: fixme.
- char prefix[2];
- prefix[0] = getEffectivePrefixChar(server->currentNick);
- prefix[1] = 0;
-
- snprintf(msgBuf, sizeof(msgBuf),
- "<%s%s> %s",
- prefix,
- server->currentNick,
- str);
- pushMessage(msgBuf);
+ outputUserMessage(server->currentNick, str, /*isAction=*/false);
snprintf(msgBuf, sizeof(msgBuf),
"PRIVMSG %s :%s",
@@ -573,61 +553,61 @@ void Channel::handleMode(const UserRef &user, const char *str) {
}
void Channel::handlePrivmsg(const UserRef &user, const char *str) {
- char prefix[2];
- prefix[0] = getEffectivePrefixChar(user.nick.c_str());
- prefix[1] = 0;
+ outputUserMessage(user.nick.c_str(), str, /*isAction=*/false);
+}
- char buf[15000];
- snprintf(buf, 15000,
- "<%s%s> %s",
- prefix,
- user.nick.c_str(),
- str);
+void Channel::outputUserMessage(const char *nick, const char *str, bool isAction) {
+ RichTextBuilder rt;
- pushMessage(buf);
+ if (isAction) {
+ rt.foreground(COL_LEVEL_BASE, COL_ACTION);
+ rt.append("* ");
+ } else {
+ rt.writeS8('<');
+ }
+
+ char prefix = getEffectivePrefixChar(nick);
+ if (prefix != 0)
+ rt.writeS8(prefix);
+
+ rt.append(nick);
+ rt.append(isAction ? " " : "> ");
+
+ rt.appendIRC(str);
+
+ pushMessage(rt.c_str(), 2);
}
void Channel::handleCtcp(const UserRef &user, const char *type, const char *params) {
char buf[15000];
if (strcmp(type, "ACTION") == 0) {
- char prefix[2];
- prefix[0] = getEffectivePrefixChar(user.nick.c_str());
- prefix[1] = 0;
-
- snprintf(buf, sizeof(buf),
- "* %s%s %s",
- prefix,
- user.nick.c_str(),
- params);
-
+ outputUserMessage(user.nick.c_str(), params, /*isAction=*/true);
} else {
snprintf(buf, sizeof(buf),
"CTCP from %s : %s %s",
user.nick.c_str(),
type,
params);
+ pushMessage(buf, 2);
}
-
- pushMessage(buf);
}
void Channel::handleTopic(const UserRef &user, const char *message) {
- char buf[1024];
+ RichTextBuilder rt;
+ rt.foreground(COL_LEVEL_BASE, COL_CHANNEL_NOTICE);
if (user.isValid) {
- snprintf(buf, sizeof(buf),
- "%s changed the topic to: %s",
- user.nick.c_str(),
- message);
+ rt.append(user.nick.c_str());
+ rt.append(" changed the topic to: ");
+ rt.appendIRC(message);
} else {
- snprintf(buf, sizeof(buf),
- "Topic: %s",
- message);
+ rt.append("Topic: ");
+ rt.appendIRC(message);
}
- pushMessage(buf);
+ pushMessage(rt.c_str());
topic = message;
@@ -639,12 +619,18 @@ void Channel::handleTopic(const UserRef &user, const char *message) {
}
void Channel::handleTopicInfo(const char *user, int timestamp) {
- char buf[1024];
- snprintf(buf, sizeof(buf),
- "Topic set by %s at %d",
- user,
- timestamp);
- pushMessage(buf);
+ char intConv[50];
+ snprintf(intConv, sizeof(intConv), "%d", timestamp);
+
+ RichTextBuilder rt;
+ rt.foreground(COL_LEVEL_BASE, COL_CHANNEL_NOTICE);
+
+ rt.append("Topic set by ");
+ rt.append(user);
+ rt.append(" at ");
+ rt.append(intConv);
+
+ pushMessage(rt.c_str());
}
@@ -764,7 +750,7 @@ void Query::handlePrivmsg(const char *str) {
partner.c_str(),
str);
- pushMessage(buf);
+ pushMessage(buf, 2);
}
void Query::handleCtcp(const char *type, const char *params) {
@@ -784,7 +770,7 @@ void Query::handleCtcp(const char *type, const char *params) {
params);
}
- pushMessage(buf);
+ pushMessage(buf, 2);
}
void Query::renamePartner(const char *_partner) {
diff --git a/python_client.py b/python_client.py
index af0eecb..2f509bd 100644
--- a/python_client.py
+++ b/python_client.py
@@ -5,6 +5,33 @@
import sys, socket, ssl, threading, struct
from PyQt5 import QtCore, QtGui, QtWidgets
+PRESET_COLOURS = [
+ QtGui.QColor(255,255,255), # COL_IRC_WHITE = 0,
+ QtGui.QColor( 0, 0, 0), # COL_IRC_BLACK = 1,
+ QtGui.QColor( 0, 0,128), # COL_IRC_BLUE = 2,
+ QtGui.QColor( 0,128, 0), # COL_IRC_GREEN = 3,
+ QtGui.QColor(255, 0, 0), # COL_IRC_RED = 4,
+ QtGui.QColor(128, 0, 0), # COL_IRC_BROWN = 5,
+ QtGui.QColor(128, 0,128), # COL_IRC_PURPLE = 6,
+ QtGui.QColor(128,128, 0), # COL_IRC_ORANGE = 7,
+ QtGui.QColor(255,255, 0), # COL_IRC_YELLOW = 8,
+ QtGui.QColor( 0,255, 0), # COL_IRC_LIME = 9,
+ QtGui.QColor( 0,128,128), # COL_IRC_TEAL = 10,
+ QtGui.QColor( 0,255,255), # COL_IRC_CYAN = 11,
+ QtGui.QColor( 0, 0,255), # COL_IRC_LIGHT_BLUE = 12,
+ QtGui.QColor(255, 0,255), # COL_IRC_PINK = 13,
+ QtGui.QColor(128,128,128), # COL_IRC_GREY = 14,
+ QtGui.QColor(192,192,192), # COL_IRC_LIGHT_GREY = 15,
+ QtGui.QColor( 0, 0, 0), # COL_DEFAULT_FG = 16,
+ QtGui.QColor(255,255,255), # COL_DEFAULT_BG = 17,
+ QtGui.QColor(102, 0,204), # COL_ACTION = 18,
+ QtGui.QColor( 0,153, 0), # COL_JOIN = 19,
+ QtGui.QColor(102, 0, 0), # COL_PART = 20,
+ QtGui.QColor(102, 0, 0), # COL_QUIT = 21,
+ QtGui.QColor(102, 0, 0), # COL_KICK = 22,
+ QtGui.QColor( 51,102,153), # COL_CHANNEL_NOTICE = 23,
+]
+
protocolVer = 1
sock = None
authed = False
@@ -139,6 +166,9 @@ class WindowTab(QtWidgets.QWidget):
self.output = QtWidgets.QTextEdit(self)
self.output.setReadOnly(True)
+ self.output.setTextColor(PRESET_COLOURS[16])
+ self.output.setTextBackgroundColor(PRESET_COLOURS[17])
+
self.input = QtWidgets.QLineEdit(self)
self.input.returnPressed.connect(self.handleLineEntered)
@@ -160,7 +190,80 @@ class WindowTab(QtWidgets.QWidget):
isAtEnd = cursor.atEnd()
cursor.movePosition(QtGui.QTextCursor.End)
cursor.clearSelection()
- cursor.insertText(msg)
+
+ fmt = QtGui.QTextCharFormat()
+
+ foreground = [None,None,None,None]
+ background = [None,None,None,None]
+
+ pos = 0
+ build = ''
+ l = len(msg)
+ while pos < l:
+ char = msg[pos]
+ c = ord(char)
+ if c == 7 or c == 10 or c >= 0x20:
+ build += char
+ else:
+ cursor.insertText(build, fmt)
+ build = ''
+
+ if c == 1:
+ fmt.setFontWeight(QtGui.QFont.Bold)
+ elif c == 2:
+ fmt.setFontWeight(QtGui.QFont.Normal)
+ elif c == 3:
+ fmt.setFontItalic(True)
+ elif c == 4:
+ fmt.setFontItalic(False)
+ elif c == 5:
+ fmt.setFontUnderline(True)
+ elif c == 6:
+ fmt.setFontUnderline(False)
+ elif (c >= 0x10 and c <= 0x1F):
+ layer = c & 3
+ isBG = (True if ((c & 4) == 4) else False)
+ isEnd = (True if ((c & 8) == 8) else False)
+
+ col = None
+
+ if not isEnd and (pos + 1) < l:
+ bit1 = ord(msg[pos + 1])
+ col = None
+ if (bit1 & 1) == 1:
+ col = PRESET_COLOURS[bit1 >> 1]
+ pos += 1
+ elif (pos + 3) < l:
+ bit2 = ord(msg[pos + 2])
+ bit3 = ord(msg[pos + 3])
+ pos += 3
+ col = QtGui.QColor(bit1,bit2,bit3)
+
+ if isBG:
+ array = background
+ else:
+ array = foreground
+
+ array[layer] = col
+ maxcol = None
+ for check in array:
+ if check is not None:
+ maxcol = check
+
+ if isBG:
+ if maxcol:
+ fmt.setBackground(QtGui.QBrush(maxcol))
+ else:
+ fmt.clearBackground()
+ else:
+ if maxcol:
+ fmt.setForeground(QtGui.QBrush(maxcol))
+ else:
+ fmt.clearForeground()
+
+
+ pos += 1
+ cursor.insertText(build, fmt)
cursor.insertText('\n')
if isAtEnd:
@@ -392,8 +495,8 @@ class MainWindow(QtWidgets.QMainWindow):
elif ptype == 0x102:
# WINDOW MESSAGES
- wndID, msglen = struct.unpack_from('<II', pdata, 0)
- msg = pdata[8:8+msglen].decode('utf-8', 'replace')
+ wndID, priority, msglen = struct.unpack_from('<IbI', pdata, 0)
+ msg = pdata[9:9+msglen].decode('utf-8', 'replace')
self.tabLookup[wndID].pushMessage(msg)
elif ptype == 0x103:
# WINDOW RENAME