initial commit

This commit is contained in:
erinn 2018-10-24 17:13:03 -07:00
parent 50590096ed
commit 5c73cd8b3f
23 changed files with 262 additions and 88 deletions

1
.gitignore vendored
View File

@ -2,3 +2,4 @@
deploy deploy
moc* moc*
rcc* rcc*
*.qmlc

60
gcd.go
View File

@ -1,13 +1,12 @@
package main package main
import ( import (
"github.com/therecipe/qt/core"
"log"
"fmt"
"strings"
"cwtch.im/cwtch/model" "cwtch.im/cwtch/model"
"encoding/base32" "encoding/base32"
) "github.com/therecipe/qt/core"
"log"
"strings"
)
type GrandCentralDispatcher struct { type GrandCentralDispatcher struct {
core.QObject core.QObject
@ -15,29 +14,30 @@ type GrandCentralDispatcher struct {
currentOpenConversation string currentOpenConversation string
// messages pane stuff // messages pane stuff
_ func(from, message string) `signal:"AppendMessage"` _ func(from, message string) `signal:"AppendMessage"`
_ func() `signal:"ClearMessages"` _ func() `signal:"ClearMessages"`
_ func() `signal:"ResetMessagePane"` _ func() `signal:"ResetMessagePane"`
// contact list stuff // contact list stuff
_ func(onion string, num int) `signal:"SetUnread"` _ func(onion string, num int) `signal:"SetUnread"`
_ func(onion string, status int) `signal:"SetConnectionStatus"` _ func(onion string, status int) `signal:"SetConnectionStatus"`
_ func(name, onion, image, badge string, trusted bool) `signal:"AddContact"` _ func(name, onion, image, badge string, trusted bool) `signal:"AddContact"`
_ func(onion string) `signal:"MarkTrusted"` _ func(onion string) `signal:"MarkTrusted"`
// profile-area stuff // profile-area stuff
_ func(name, onion, image string) `signal:"UpdateMyProfile"` _ func(name, onion, image string) `signal:"UpdateMyProfile"`
_ func(status int, str string) `signal:"TorStatus"`
// other stuff i can't ontologize atm // other stuff i can't ontologize atm
_ func(str string) `signal:"InvokePopup"` _ func(str string) `signal:"InvokePopup"`
// exfiltrated signals (written in go, below) // exfiltrated signals (written in go, below)
_ func(message string) `signal:"sendMessage,auto"` _ func(message string) `signal:"sendMessage,auto"`
_ func(onion string) `signal:"loadMessagesPane,auto"` _ func(onion string) `signal:"loadMessagesPane,auto"`
_ func(signal string) `signal:"broadcast,auto"` // convenience relay signal _ func(signal string) `signal:"broadcast,auto"` // convenience relay signal
_ func(str string) `signal:"importString,auto"` _ func(str string) `signal:"importString,auto"`
_ func(str string) `signal:"popup,auto"` _ func(str string) `signal:"popup,auto"`
_ func(nick string) `signal:"updateNick,auto"` _ func(nick string) `signal:"updateNick,auto"`
} }
func (this *GrandCentralDispatcher) sendMessage(message string) { func (this *GrandCentralDispatcher) sendMessage(message string) {
@ -57,11 +57,12 @@ func (this *GrandCentralDispatcher) sendMessage(message string) {
gcd.MarkTrusted(gcd.currentOpenConversation) gcd.MarkTrusted(gcd.currentOpenConversation)
} }
log.Printf("SENDING MESSAGE %v to %v...\n", message, gcd.currentOpenConversation) select { // fancy trick to do a non-blocking send. this means the user can only send a limited number of messages
connection := peer.PeerWithOnion(gcd.currentOpenConversation) // 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) DeliverMessageToUI(gcd.currentOpenConversation, message, true)
} }
@ -99,10 +100,15 @@ func (this *GrandCentralDispatcher) importString(str string) {
onion := str onion := str
name := onion 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, "~") { if strings.Contains(str, "~") {
parts := strings.Split(str, "~") parts := strings.Split(str, "~")
onion = parts[len(parts) - 1] onion = parts[len(parts)-1]
name = strings.Join(parts[:len(parts) - 1], " ") name = strings.Join(parts[:len(parts)-1], " ")
} }
if len(onion) != 56 { if len(onion) != 56 {
@ -117,7 +123,7 @@ func (this *GrandCentralDispatcher) importString(str string) {
} }
if len(name) > 32 { 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])) 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") _, exists = peer.GetProfile().GetCustomAttribute(onion + "_name")
if exists { if exists {
gcd.InvokePopup("already have this contact") gcd.InvokePopup("already have this contact")
return//TODO: bring them to the duplicate return //TODO: bring them to the duplicate
} }
pp := model.PublicProfile{ pp := model.PublicProfile{
@ -168,4 +174,4 @@ func (this *GrandCentralDispatcher) popup(str string) {
func (this *GrandCentralDispatcher) updateNick(nick string) { func (this *GrandCentralDispatcher) updateNick(nick string) {
peer.GetProfile().Name = nick peer.GetProfile().Name = nick
peer.Save() peer.Save()
} }

100
main.go
View File

@ -1,31 +1,36 @@
package main package main
import ( 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" libpeer "cwtch.im/cwtch/peer"
"path" "cwtch.im/cwtch/peer/connections"
"os/user"
"fmt"
"encoding/base32" "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" "strings"
"cwtch.im/cwtch/peer/connections" "time"
) "strconv"
"git.openprivacy.ca/openprivacy/asaur"
)
var gcd *GrandCentralDispatcher var gcd *GrandCentralDispatcher
type ContactManager map[string]*Contact type ContactManager map[string]*Contact
var contactMgr ContactManager var contactMgr ContactManager
var peer libpeer.CwtchPeer var peer libpeer.CwtchPeer
var outgoingMessages chan Message
type Contact struct { type Contact struct {
Messages []Message Messages []Message
Unread int Unread int
Status connections.ConnectionState Status connections.ConnectionState
} }
func (this *Contact) AddMessage(m Message) { func (this *Contact) AddMessage(m Message) {
@ -34,7 +39,7 @@ func (this *Contact) AddMessage(m Message) {
type Message struct { type Message struct {
With, Message string With, Message string
FromMe bool FromMe bool
} }
func DeliverMessageToUI(from, message string, fromMe bool) { func DeliverMessageToUI(from, message string, fromMe bool) {
@ -77,8 +82,12 @@ func main() {
view.SetSource(core.NewQUrl3("qrc:/qml/main.qml", 0)) view.SetSource(core.NewQUrl3("qrc:/qml/main.qml", 0))
} }
outgoingMessages = make(chan Message, 1000)
go postmanPat()
initialize(view) initialize(view)
view.Show() view.Show()
go torStatusPoller()
go presencePoller() go presencePoller()
go ricochetListener() go ricochetListener()
go alice() 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() { func ricochetListener() {
processData := func(onion string, data []byte) []byte { processData := func(onion string, data []byte) []byte {
/* _, exists := peer.GetProfile().GetCustomAttribute(onion + "_name") /* _, 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) { 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 //TODO: this section is ported over and has a lot of printf errors, need to show them in the ui
var dirname, filename string var dirname, filename string
@ -203,11 +267,11 @@ func initialize(view *quick.QQuickView) {
// temporary until we do real picture selection // temporary until we do real picture selection
func randomProfileImage(onion string) string { 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)) barr, err := base32.StdEncoding.DecodeString(strings.ToUpper(onion))
if err != nil || len(barr) != 35 { if err != nil || len(barr) != 35 {
fmt.Printf("error: %v %v %v\n", onion, err, barr) fmt.Printf("error: %v %v %v\n", onion, err, barr)
return "qrc:/qml/images/extra/openprivacy.png" return "qrc:/qml/images/extra/openprivacy.png"
} }
return "qrc:/qml/images/profiles/" + choices[int(barr[33]) % len(choices)] + ".png" return "qrc:/qml/images/profiles/" + choices[int(barr[33])%len(choices)] + ".png"
} }

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.0 MiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.0 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 11 KiB

View File

@ -18,7 +18,7 @@ Item {
Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS Rectangle { // THE LEFT PANE WITH TOOLS AND CONTACTS
color: "#FFEEEE" color: "#D2C0DD"
Layout.fillHeight: true Layout.fillHeight: true
Layout.minimumWidth: 200 Layout.minimumWidth: 200
Layout.maximumWidth: 200 Layout.maximumWidth: 200

Binary file not shown.

View File

@ -10,22 +10,22 @@ RowLayout { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
anchors.right: parent.right anchors.right: parent.right
property alias nick: cn.text property alias nick: cn.text
property alias image: img.source property alias image: imgProfile.source
property string onion property string onion
property string badge property string badge
property bool isActive property bool isActive
property bool isHover property bool isHover
property bool isTrusted property bool isTrusted
property int status property alias status: imgProfile.status
Rectangle { // CONTACT ENTRY BACKGROUND COLOR Rectangle { // CONTACT ENTRY BACKGROUND COLOR
id: root id: root
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
height: childrenRect.height height: childrenRect.height + 3
width: parent.width width: parent.width
color: isHover ? "#EEEEFF" : (isActive ? "#EEEEFF" : "#FFEEEE") color: isHover ? "#D2D2F3" : (isActive ? "#D2D2F3" : "#D2C0DD")
RowLayout { RowLayout {
@ -34,39 +34,8 @@ RowLayout { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
anchors.right: parent.right anchors.right: parent.right
Item { // PROFILE IMAGE ContactPicture {
id: imgProfile 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 Label { // CONTACT NAME
@ -77,6 +46,7 @@ RowLayout { // LOTS OF NESTING TO DEAL WITH QT WEIRDNESS, SORRY
anchors.left: imgProfile.right anchors.left: imgProfile.right
font.pixelSize: 16 font.pixelSize: 16
font.italic: !isTrusted font.italic: !isTrusted
textFormat: Text.PlainText
} }
Rectangle { // UNREAD MESSAGES? Rectangle { // UNREAD MESSAGES?

Binary file not shown.

Binary file not shown.

View File

@ -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
}
}
}
}

Binary file not shown.

View File

@ -23,12 +23,74 @@ ColumnLayout {
implicitHeight: 96 implicitHeight: 96
anchors.horizontalCenter: parent.horizontalCenter anchors.horizontalCenter: parent.horizontalCenter
Rectangle { // WHITE CIRCLE BORDER
width: 96
height: 96
color: "#FFFFFF"
radius: width / 2
Image { Image { // ACTUAL IMAGE
id: imgProfileImg id: imgProfileImg
anchors.fill: parent anchors.fill: parent
fillMode: Image.PreserveAspectFit fillMode: Image.PreserveAspectFit
source: "qrc:/qml/images/extra/robot.png" 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 width: parent.width
onUpdated: { onUpdated: {
console.log("sending updatenick ["+nick+"]")
gcd.updateNick(lblNick.text) gcd.updateNick(lblNick.text)
} }
} }
@ -136,5 +197,10 @@ ColumnLayout {
onion = _onion onion = _onion
image = _image image = _image
} }
onTorStatus: function(code, str) {
rectTorStatus.code = code
rectTorStatus.message = str
}
} }
} }

Binary file not shown.