diff --git a/.gitignore b/.gitignore index 2ac51d5..2c51d94 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ deploy moc* rcc* +*.qmlc diff --git a/gcd.go b/gcd.go index 4fdd172..1c02c45 100644 --- a/gcd.go +++ b/gcd.go @@ -1,13 +1,12 @@ package main import ( - "github.com/therecipe/qt/core" - "log" - "fmt" - "strings" "cwtch.im/cwtch/model" "encoding/base32" - ) + "github.com/therecipe/qt/core" + "log" + "strings" +) type GrandCentralDispatcher struct { core.QObject @@ -15,29 +14,30 @@ type GrandCentralDispatcher struct { currentOpenConversation string // messages pane stuff - _ func(from, message string) `signal:"AppendMessage"` - _ func() `signal:"ClearMessages"` - _ func() `signal:"ResetMessagePane"` + _ func(from, message string) `signal:"AppendMessage"` + _ func() `signal:"ClearMessages"` + _ func() `signal:"ResetMessagePane"` // contact list stuff - _ func(onion string, num int) `signal:"SetUnread"` - _ func(onion string, status int) `signal:"SetConnectionStatus"` + _ func(onion string, num int) `signal:"SetUnread"` + _ func(onion string, status int) `signal:"SetConnectionStatus"` _ func(name, onion, image, badge string, trusted bool) `signal:"AddContact"` - _ func(onion string) `signal:"MarkTrusted"` + _ func(onion string) `signal:"MarkTrusted"` // profile-area stuff _ func(name, onion, image string) `signal:"UpdateMyProfile"` + _ func(status int, str string) `signal:"TorStatus"` // other stuff i can't ontologize atm - _ func(str string) `signal:"InvokePopup"` + _ func(str string) `signal:"InvokePopup"` // exfiltrated signals (written in go, below) - _ func(message string) `signal:"sendMessage,auto"` - _ func(onion string) `signal:"loadMessagesPane,auto"` - _ func(signal string) `signal:"broadcast,auto"` // convenience relay signal - _ func(str string) `signal:"importString,auto"` - _ func(str string) `signal:"popup,auto"` - _ func(nick string) `signal:"updateNick,auto"` + _ func(message string) `signal:"sendMessage,auto"` + _ func(onion string) `signal:"loadMessagesPane,auto"` + _ func(signal string) `signal:"broadcast,auto"` // convenience relay signal + _ func(str string) `signal:"importString,auto"` + _ func(str string) `signal:"popup,auto"` + _ func(nick string) `signal:"updateNick,auto"` } func (this *GrandCentralDispatcher) sendMessage(message string) { @@ -57,11 +57,12 @@ func (this *GrandCentralDispatcher) sendMessage(message string) { gcd.MarkTrusted(gcd.currentOpenConversation) } - log.Printf("SENDING MESSAGE %v to %v...\n", message, gcd.currentOpenConversation) - connection := peer.PeerWithOnion(gcd.currentOpenConversation) + select { // fancy trick to do a non-blocking send. this means the user can only send a limited number of messages + // before the channel buffer fills. TODO: stop the user from sending if the buffer is full + case outgoingMessages <- Message{gcd.currentOpenConversation, message, true}: + default: + } - fmt.Printf("sending data, connection state: %v\n", connection.GetState()) - go connection.SendPacket([]byte(message)) DeliverMessageToUI(gcd.currentOpenConversation, message, true) } @@ -99,10 +100,15 @@ func (this *GrandCentralDispatcher) importString(str string) { onion := str name := onion + if strings.Contains(str, " ") {// usually people prepend spaces and we don't want it going into the name (use ~ for that) + parts := strings.Split(strings.TrimSpace(str), " ") + str = parts[len(parts) - 1] + } + if strings.Contains(str, "~") { parts := strings.Split(str, "~") - onion = parts[len(parts) - 1] - name = strings.Join(parts[:len(parts) - 1], " ") + onion = parts[len(parts)-1] + name = strings.Join(parts[:len(parts)-1], " ") } if len(onion) != 56 { @@ -117,7 +123,7 @@ func (this *GrandCentralDispatcher) importString(str string) { } if len(name) > 32 { - name = name[:32]//TODO: better strategy for long names? + name = name[:32] //TODO: better strategy for long names? } decodedPub, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion[:56])) @@ -136,7 +142,7 @@ func (this *GrandCentralDispatcher) importString(str string) { _, exists = peer.GetProfile().GetCustomAttribute(onion + "_name") if exists { gcd.InvokePopup("already have this contact") - return//TODO: bring them to the duplicate + return //TODO: bring them to the duplicate } pp := model.PublicProfile{ @@ -168,4 +174,4 @@ func (this *GrandCentralDispatcher) popup(str string) { func (this *GrandCentralDispatcher) updateNick(nick string) { peer.GetProfile().Name = nick peer.Save() -} \ No newline at end of file +} diff --git a/main.go b/main.go index 331ebd1..4a13e34 100644 --- a/main.go +++ b/main.go @@ -1,31 +1,36 @@ package main import ( -"os" - "github.com/therecipe/qt/core" -"github.com/therecipe/qt/quick" -"github.com/therecipe/qt/quickcontrols2" -"github.com/therecipe/qt/widgets" - "time" - "github.com/sethvargo/go-diceware/diceware" libpeer "cwtch.im/cwtch/peer" - "path" - "os/user" - "fmt" + "cwtch.im/cwtch/peer/connections" "encoding/base32" + "fmt" + "github.com/sethvargo/go-diceware/diceware" + "github.com/therecipe/qt/core" + "github.com/therecipe/qt/quick" + "github.com/therecipe/qt/quickcontrols2" + "github.com/therecipe/qt/widgets" + "os" + "os/user" + "path" "strings" - "cwtch.im/cwtch/peer/connections" -) + "time" + "strconv" + "git.openprivacy.ca/openprivacy/asaur" + ) var gcd *GrandCentralDispatcher + type ContactManager map[string]*Contact + var contactMgr ContactManager var peer libpeer.CwtchPeer +var outgoingMessages chan Message type Contact struct { Messages []Message - Unread int - Status connections.ConnectionState + Unread int + Status connections.ConnectionState } func (this *Contact) AddMessage(m Message) { @@ -34,7 +39,7 @@ func (this *Contact) AddMessage(m Message) { type Message struct { With, Message string - FromMe bool + FromMe bool } func DeliverMessageToUI(from, message string, fromMe bool) { @@ -77,8 +82,12 @@ func main() { view.SetSource(core.NewQUrl3("qrc:/qml/main.qml", 0)) } + outgoingMessages = make(chan Message, 1000) + go postmanPat() + initialize(view) view.Show() + go torStatusPoller() go presencePoller() go ricochetListener() go alice() @@ -128,6 +137,37 @@ func presencePoller() { // TODO: make this subscribe-able in ricochet } } +func torStatusPoller() { + for { + time.Sleep(time.Second) + //todo: this should use a config manager + //todo: also, try dialing the proxy to differentiate tor not running vs control port not configured + rawStatus, err := asaur.GetInfo("localhost:9051", "tcp4", "", "status/bootstrap-phase") + if err != nil { + gcd.TorStatus(0, "can't find tor. is it running? is the controlport configured?") + continue + } + + status := asaur.ParseBootstrapPhase(rawStatus) + progress, _ := strconv.Atoi(status["PROGRESS"]) + + if status["TAG"] == "done" { + gcd.TorStatus(3, "tor appears to be running just fine!") + continue + } + + if progress == 0 { + gcd.TorStatus(1, "tor is trying to start up") + continue + } + + gcd.TorStatus(2, status["SUMMARY"]) + //qCwtchApp.SetTorStatusProgress(progress) + //qCwtchApp.SetTorStatusSummary(status["SUMMARY"]) + //if status["TAG"] == "done" { + } +} + func ricochetListener() { processData := func(onion string, data []byte) []byte { /* _, exists := peer.GetProfile().GetCustomAttribute(onion + "_name") @@ -159,6 +199,30 @@ func ricochetListener() { } } +func postmanPat() { + postOffice := make(map[string]chan Message) + + for { + m := <-outgoingMessages + + _, found := postOffice[m.With] + if !found { + postOffice[m.With] = make(chan Message, 100) + go andHisBlackAndWhiteCat(postOffice[m.With]) + } + + postOffice[m.With] <- m + } +} + +func andHisBlackAndWhiteCat(incomingMessages chan Message) { + for { + m := <-incomingMessages + connection := peer.PeerWithOnion(m.With) + connection.SendPacket([]byte(m.Message)) + } +} + func initialize(view *quick.QQuickView) { //TODO: this section is ported over and has a lot of printf errors, need to show them in the ui var dirname, filename string @@ -203,11 +267,11 @@ func initialize(view *quick.QQuickView) { // temporary until we do real picture selection func randomProfileImage(onion string) string { - choices := []string{"001-centaur","002-kraken","003-dinosaur","004-tree-1","005-hand","006-echidna","007-robot","008-mushroom","009-harpy","010-phoenix","011-dragon-1","012-devil","013-troll","014-alien","015-minotaur","016-madre-monte","017-satyr","018-karakasakozou","019-pirate","020-werewolf","021-scarecrow","022-valkyrie","023-curupira","024-loch-ness-monster","025-tree","026-cerberus","027-gryphon","028-mermaid","029-vampire","030-goblin","031-yeti","032-leprechaun","033-medusa","034-chimera","035-elf","036-hydra","037-cyclops","038-pegasus","039-narwhal","040-woodcutter","041-zombie","042-dragon","043-frankenstein","044-witch","045-fairy","046-genie","047-pinocchio","048-ghost","049-wizard","050-unicorn"} + choices := []string{"001-centaur", "002-kraken", "003-dinosaur", "004-tree-1", "005-hand", "006-echidna", "007-robot", "008-mushroom", "009-harpy", "010-phoenix", "011-dragon-1", "012-devil", "013-troll", "014-alien", "015-minotaur", "016-madre-monte", "017-satyr", "018-karakasakozou", "019-pirate", "020-werewolf", "021-scarecrow", "022-valkyrie", "023-curupira", "024-loch-ness-monster", "025-tree", "026-cerberus", "027-gryphon", "028-mermaid", "029-vampire", "030-goblin", "031-yeti", "032-leprechaun", "033-medusa", "034-chimera", "035-elf", "036-hydra", "037-cyclops", "038-pegasus", "039-narwhal", "040-woodcutter", "041-zombie", "042-dragon", "043-frankenstein", "044-witch", "045-fairy", "046-genie", "047-pinocchio", "048-ghost", "049-wizard", "050-unicorn"} barr, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion)) if err != nil || len(barr) != 35 { fmt.Printf("error: %v %v %v\n", onion, err, barr) return "qrc:/qml/images/extra/openprivacy.png" } - return "qrc:/qml/images/profiles/" + choices[int(barr[33]) % len(choices)] + ".png" -} \ No newline at end of file + return "qrc:/qml/images/profiles/" + choices[int(barr[33])%len(choices)] + ".png" +} diff --git a/qml/images/extra/clipcircle.png b/qml/images/extra/clipcircle.png new file mode 100644 index 0000000..cb2eef1 Binary files /dev/null and b/qml/images/extra/clipcircle.png differ diff --git a/qml/images/extra/mermaid.png b/qml/images/extra/mermaid.png deleted file mode 100644 index 4af9b85..0000000 Binary files a/qml/images/extra/mermaid.png and /dev/null differ diff --git a/qml/images/extra/qt-arrow.png b/qml/images/extra/qt-arrow.png deleted file mode 100644 index b8305b8..0000000 Binary files a/qml/images/extra/qt-arrow.png and /dev/null differ diff --git a/qml/images/extra/qt-arrow@2x.png b/qml/images/extra/qt-arrow@2x.png deleted file mode 100644 index 8de14ff..0000000 Binary files a/qml/images/extra/qt-arrow@2x.png and /dev/null differ diff --git a/qml/images/extra/qt-arrow@3x.png b/qml/images/extra/qt-arrow@3x.png deleted file mode 100644 index 310fea2..0000000 Binary files a/qml/images/extra/qt-arrow@3x.png and /dev/null differ diff --git a/qml/images/extra/qt-arrow@4x.png b/qml/images/extra/qt-arrow@4x.png deleted file mode 100644 index 6fbfee3..0000000 Binary files a/qml/images/extra/qt-arrow@4x.png and /dev/null differ diff --git a/qml/images/extra/qt-logo.png b/qml/images/extra/qt-logo.png deleted file mode 100644 index 90e6f90..0000000 Binary files a/qml/images/extra/qt-logo.png and /dev/null differ diff --git a/qml/images/extra/qt-logo@2x.png b/qml/images/extra/qt-logo@2x.png deleted file mode 100644 index 22d111a..0000000 Binary files a/qml/images/extra/qt-logo@2x.png and /dev/null differ diff --git a/qml/images/extra/qt-logo@3x.png b/qml/images/extra/qt-logo@3x.png deleted file mode 100644 index 627746c..0000000 Binary files a/qml/images/extra/qt-logo@3x.png and /dev/null differ diff --git a/qml/images/extra/qt-logo@4x.png b/qml/images/extra/qt-logo@4x.png deleted file mode 100644 index dc62286..0000000 Binary files a/qml/images/extra/qt-logo@4x.png and /dev/null differ diff --git a/qml/images/extra/robot.png b/qml/images/extra/robot.png deleted file mode 100644 index 1cd46b4..0000000 Binary files a/qml/images/extra/robot.png and /dev/null differ diff --git a/qml/main.qml b/qml/main.qml index 9884034..eb40fa8 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -18,7 +18,7 @@ Item { Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS - color: "#FFEEEE" + color: "#D2C0DD" Layout.fillHeight: true Layout.minimumWidth: 200 Layout.maximumWidth: 200 diff --git a/qml/main.qmlc b/qml/main.qmlc index 587d8fa..4334c8a 100644 Binary files a/qml/main.qmlc and b/qml/main.qmlc differ diff --git a/qml/widgets/Contact.qml b/qml/widgets/Contact.qml index e09e353..d07b1e1 100644 --- a/qml/widgets/Contact.qml +++ b/qml/widgets/Contact.qml @@ -10,22 +10,22 @@ RowLayout { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY anchors.right: parent.right property alias nick: cn.text - property alias image: img.source + property alias image: imgProfile.source property string onion property string badge property bool isActive property bool isHover property bool isTrusted - property int status + property alias status: imgProfile.status Rectangle { // CONTACT ENTRY BACKGROUND COLOR id: root anchors.left: parent.left anchors.right: parent.right - height: childrenRect.height + height: childrenRect.height + 3 width: parent.width - color: isHover ? "#EEEEFF" : (isActive ? "#EEEEFF" : "#FFEEEE") + color: isHover ? "#D2D2F3" : (isActive ? "#D2D2F3" : "#D2C0DD") RowLayout { @@ -34,39 +34,8 @@ RowLayout { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY anchors.right: parent.right - Item { // PROFILE IMAGE + ContactPicture { id: imgProfile - implicitWidth: 48 - implicitHeight: 48 - anchors.left: parent.left - - - Image { - id: img - anchors.fill: parent - fillMode: Image.PreserveAspectFit - } - - Rectangle { // PRESENCE INDICATOR - color: "#FFFFFF" - width: 8 - height: 8 - radius: 2 - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 4 - - - Rectangle { //-2:WtfCodeError,-1:Untrusted,0:Disconnected,1:Connecting,2:Connected,3:Authenticated,4:Failed,5:Killed - color: status==3?"green":(status==4?"red":(status==-1?"blue":"orange")) - width: 5 - height: 5 - radius: 2 - anchors.right: parent.right - anchors.bottom: parent.bottom - anchors.margins: 1.5 - } - } } Label { // CONTACT NAME @@ -77,6 +46,7 @@ RowLayout { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY anchors.left: imgProfile.right font.pixelSize: 16 font.italic: !isTrusted + textFormat: Text.PlainText } Rectangle { // UNREAD MESSAGES? diff --git a/qml/widgets/Contact.qmlc b/qml/widgets/Contact.qmlc index 350de2b..0738f5c 100644 Binary files a/qml/widgets/Contact.qmlc and b/qml/widgets/Contact.qmlc differ diff --git a/qml/widgets/ContactList.qmlc b/qml/widgets/ContactList.qmlc index a6ddbab..56ab3a4 100644 Binary files a/qml/widgets/ContactList.qmlc and b/qml/widgets/ContactList.qmlc differ diff --git a/qml/widgets/ContactPicture.qml b/qml/widgets/ContactPicture.qml new file mode 100644 index 0000000..ed75791 --- /dev/null +++ b/qml/widgets/ContactPicture.qml @@ -0,0 +1,67 @@ +import QtGraphicalEffects 1.0 +import QtQuick 2.7 +import QtQuick.Controls 2.4 +import QtQuick.Controls.Material 2.0 +import QtQuick.Layouts 1.3 +import CustomQmlTypes 1.0 + +Item { + id: imgProfile + implicitWidth: 48 + implicitHeight: 48 + anchors.left: parent.left + anchors.margins: 5 + + property alias source: img.source + property int status + + + Rectangle { + width: 48 + height: 48 + color: "#FFFFFF" + radius: width / 2 + + + Image { // PROFILE IMAGE + id: img + anchors.fill: parent + fillMode: Image.PreserveAspectFit + visible: false + } + + Image { // CIRCLE MASK + id: mask + fillMode: Image.PreserveAspectFit + visible: false + source: "qrc:/qml/images/extra/clipcircle.png" + } + + OpacityMask { + anchors.fill: img + source: img + maskSource: mask + } + + Rectangle { // PRESENCE INDICATOR + color: "#FFFFFF" + width: 8 + height: 8 + radius: 2 + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 4 + + + Rectangle { //-2:WtfCodeError,-1:Untrusted,0:Disconnected,1:Connecting,2:Connected,3:Authenticated,4:Failed,5:Killed + color: status == 3 ? "green" : status == -1 ? "blue" : status == 1 ? "orange" : status == 2 ? "orange" : "red" + width: 5 + height: 5 + radius: 2 + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 1.5 + } + } + } +} \ No newline at end of file diff --git a/qml/widgets/MessageList.qmlc b/qml/widgets/MessageList.qmlc index 006b839..042aeec 100644 Binary files a/qml/widgets/MessageList.qmlc and b/qml/widgets/MessageList.qmlc differ diff --git a/qml/widgets/MyProfile.qml b/qml/widgets/MyProfile.qml index 9c59b79..912cad3 100644 --- a/qml/widgets/MyProfile.qml +++ b/qml/widgets/MyProfile.qml @@ -23,12 +23,74 @@ ColumnLayout { implicitHeight: 96 anchors.horizontalCenter: parent.horizontalCenter + Rectangle { // WHITE CIRCLE BORDER + width: 96 + height: 96 + color: "#FFFFFF" + radius: width / 2 - Image { - id: imgProfileImg - anchors.fill: parent - fillMode: Image.PreserveAspectFit - source: "qrc:/qml/images/extra/robot.png" + Image { // ACTUAL IMAGE + id: imgProfileImg + anchors.fill: parent + fillMode: Image.PreserveAspectFit + visible: false + } + + Image { // INNER CIRCLE MASK + id: mask + fillMode: Image.PreserveAspectFit + visible: false + source: "qrc:/qml/images/extra/clipcircle.png" + } + + OpacityMask { // WE PUT IT ALL TOGETHER ANNND + anchors.fill: imgProfileImg + source: imgProfileImg + maskSource: mask + } + + + Rectangle { // TOR STATUS INDICATOR + color: "#FFFFFF" + width: 12 + height: 12 + radius: 3 + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 8 + + + Rectangle { //0: no tor, 1: progress 0%, 2: progress 1-99%, 3: DONE + id: rectTorStatus + color: code == 3 ? "green": code == 2 ? "orange" : code == 1 ? "yellow" : "red" + width: 8 + height: 8 + radius: 2 + anchors.right: parent.right + anchors.bottom: parent.bottom + anchors.margins: 2 + + property int code + property string message + property bool hovered + + + MouseArea { + anchors.fill: parent + hoverEnabled: true + + onEntered: rectTorStatus.hovered = true + + onExited: rectTorStatus.hovered = false + } + + + ToolTip.visible: hovered + ToolTip.delay: 400 + ToolTip.timeout: 5000 + ToolTip.text: message + } + } } } @@ -38,7 +100,6 @@ ColumnLayout { width: parent.width onUpdated: { - console.log("sending updatenick ["+nick+"]") gcd.updateNick(lblNick.text) } } @@ -136,5 +197,10 @@ ColumnLayout { onion = _onion image = _image } + + onTorStatus: function(code, str) { + rectTorStatus.code = code + rectTorStatus.message = str + } } } \ No newline at end of file diff --git a/qml/widgets/MyProfile.qmlc b/qml/widgets/MyProfile.qmlc index 3f51307..7956d2e 100644 Binary files a/qml/widgets/MyProfile.qmlc and b/qml/widgets/MyProfile.qmlc differ