Browse Source

initial commit

erinn 5 months ago
parent
commit
5c73cd8b3f

+ 1 - 0
.gitignore

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

+ 33 - 27
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()
-}
+}

+ 82 - 18
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"
-}
+	return "qrc:/qml/images/profiles/" + choices[int(barr[33])%len(choices)] + ".png"
+}

BIN
qml/images/extra/clipcircle.png


BIN
qml/images/extra/mermaid.png


BIN
qml/images/extra/qt-arrow.png


BIN
qml/images/extra/qt-arrow@2x.png


BIN
qml/images/extra/qt-arrow@3x.png


BIN
qml/images/extra/qt-arrow@4x.png


BIN
qml/images/extra/qt-logo.png


BIN
qml/images/extra/qt-logo@2x.png


BIN
qml/images/extra/qt-logo@3x.png


BIN
qml/images/extra/qt-logo@4x.png


BIN
qml/images/extra/robot.png


+ 1 - 1
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

BIN
qml/main.qmlc


+ 6 - 36
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?

BIN
qml/widgets/Contact.qmlc


BIN
qml/widgets/ContactList.qmlc


+ 67 - 0
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
+			}
+		}
+	}
+}

BIN
qml/widgets/MessageList.qmlc


+ 72 - 6
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 { // 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
 
-		Image {
-			id: imgProfileImg
-			anchors.fill: parent
-			fillMode: Image.PreserveAspectFit
-			source: "qrc:/qml/images/extra/robot.png"
+
+					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
+		}
 	}
 }

BIN
qml/widgets/MyProfile.qmlc