package main import ( libapp "cwtch.im/cwtch/app" "cwtch.im/ui/go/characters" "cwtch.im/ui/go/cwutil" "cwtch.im/ui/go/gobjects" "cwtch.im/ui/go/gothings" "cwtch.im/ui/go/the" "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/log" "github.com/therecipe/qt/gui" "github.com/therecipe/qt/core" "github.com/therecipe/qt/network" "github.com/therecipe/qt/qml" "github.com/therecipe/qt/quickcontrols2" "os" "path" "runtime" "path/filepath" "os/user" "fmt" ) const androidBaseDir = "/data/data/org.qtproject.example.go/" func init() { // make go-defined types available in qml gothings.GrandCentralDispatcher_QmlRegisterType2("CustomQmlTypes", 1, 0, "GrandCentralDispatcher") } func main() { if len(os.Args) >= 3 && os.Args[2] == "-debug" { log.SetLevel(log.LevelDebug) } else { log.SetLevel(log.LevelInfo) } // our globals gcd := gothings.NewGrandCentralDispatcher(nil) gcd.UIState = gothings.NewUIState(gcd) the.AcknowledgementIDs = make(map[string][]*the.AckId) gcd.OutgoingMessages = make(chan gobjects.Letter, 1000) //TODO: put theme stuff somewhere better gcd.SetThemeScale(1.0) // this is to load local qml files quickly when developing var qmlSource *core.QUrl if len(os.Args) >= 2 && os.Args[1] == "-local" { qmlSource = core.QUrl_FromLocalFile("./qml/main.qml") } else { qmlSource = core.NewQUrl3("qrc:/qml/main.qml", 0) } app := gui.NewQGuiApplication(len(os.Args), os.Args) app.SetWindowIcon(gui.NewQIcon5(":/qml/images/cwtch-icon.png")) var translator = core.NewQTranslator(nil) translator.Load("translation_"+core.QLocale_System().Name(), ":/i18n/", "", "") core.QCoreApplication_InstallTranslator(translator) core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true) quickcontrols2.QQuickStyle_SetStyle("Universe") engine := qml.NewQQmlApplicationEngine(nil) // variables we want to access from inside qml if runtime.GOOS == "android" { gcd.SetThemeScale(2.9) } else { gcd.SetThemeScale(1.0) } engine.RootContext().SetContextProperty("gcd", gcd) engine.Load(qmlSource) if os.Getenv("CWTCH_FOLDER") != "" { the.CwtchDir = os.Getenv("CWTCH_FOLDER") } else if runtime.GOOS == "android" { the.CwtchDir = path.Join(androidBaseDir, "files") } else { usr, err := user.Current() if err != nil { fmt.Printf("\nerror: could not load current user: %v\n", err) os.Exit(1) } the.CwtchDir = path.Join(usr.HomeDir, ".cwtch") } torpath := "tor" if runtime.GOOS == "android" { torpath = path.Join(androidBaseDir, "lib/libtor.so") } else { dir, _ := filepath.Abs(filepath.Dir(os.Args[0])) if _, err := os.Stat(path.Join(dir, "tor")); os.IsNotExist(err) { if _, err := os.Stat(path.Join(dir, "deploy", "linux", "tor")); os.IsNotExist(err) { if _, err := os.Stat(path.Join(dir, "deploy", "windows", "tor")); os.IsNotExist(err) { log.Warnln("Cannot find bundled Tor") } else { torpath = path.Join(dir, "deploy", "windows", "tor") } } else { torpath = path.Join(dir, "deploy", "linux", "tor") } } else { torpath = path.Join(dir, "tor") } } acn, err := connectivity.StartTor(the.CwtchDir, torpath) if err != nil { log.Errorf("Could not start Tor: %v", err) os.Exit(1) } // these are long-lived pollers/listeners for incoming messages and status changes loadCwtchData(gcd, acn) go characters.IncomingListener(gcd.UIState.AddMessage, gcd.UIState.AddSendMessageError) go characters.TorStatusPoller(gcd.TorStatus, acn) go characters.PresencePoller(gcd.UIState.GetContact, gcd.UIState.AddContact, gcd.UIState.UpdateContact) go characters.GroupPoller(gcd.UIState.GetContact, gcd.UIState.UpdateContact) // prevent qt from initiating network connections (possible deanon attempts!) factory := qml.NewQQmlNetworkAccessManagerFactory() factory.ConnectCreate(func(parent *core.QObject) *network.QNetworkAccessManager { nam := network.NewQNetworkAccessManager(parent) nam.SetNetworkAccessible(network.QNetworkAccessManager__NotAccessible) proxy := network.NewQNetworkProxy() proxy.SetHostName("0.0.0.0") nam.SetProxy(proxy) //nam.ConnectCreateRequest(func(op network.QNetworkAccessManager__Operation, originalReq *network.QNetworkRequest, outgoingData *core.QIODevice) *network.QNetworkReply { // log.Errorf("network access request detected - possible remote content insertion bug!!!") // return nil //}) return nam }) engine.SetNetworkAccessManagerFactory(factory) app.Exec() acn.Close() } // this is mostly going to get factored out when we add profile support // for now, it loads a single peer and fills the ui with its data func loadCwtchData(gcd *gothings.GrandCentralDispatcher, acn connectivity.ACN) { var err error /*_, err := app2.NewApp(dirname, "/data/data/org.qtproject.example.go/lib/libtor.so") if err != nil { log.Errorf("ERROR CREATING CWTCH APP: %v", err) } time.Sleep(time.Second * 10) */ os.MkdirAll(the.CwtchDir, 0700) the.CwtchApp = libapp.NewApp(acn, the.CwtchDir) err = the.CwtchApp.LoadProfiles("be gay do crime") if err != nil { //TODO no more fatalfs log.Errorf("couldn't load profiles: %v", err) os.Exit(1) } if len(the.CwtchApp.ListPeers()) == 0 { log.Infoln("couldn't load your config file. attempting to create one now") the.Peer, err = the.CwtchApp.CreatePeer("alice", "be gay do crime") if err != nil { log.Errorf("couldn't create one. is your cwtch folder writable?") os.Exit(1) } } else { the.Peer = the.CwtchApp.PrimaryIdentity() } gcd.UpdateMyProfile(the.Peer.GetProfile().Name, the.Peer.GetProfile().Onion, cwutil.RandomProfileImage(the.Peer.GetProfile().Onion)) the.CwtchApp.LaunchPeers() contacts := the.Peer.GetContacts() for i := range contacts { contact, _ := the.Peer.GetProfile().GetContact(contacts[i]) displayName, _ := contact.GetAttribute("nick") deleted, _ := contact.GetAttribute("deleted") if deleted != "deleted" { gcd.UIState.AddContact(&gobjects.Contact{ Handle: contacts[i], DisplayName: displayName, Image: cwutil.RandomProfileImage(contacts[i]), Trusted: contact.Trusted, }) } } groups := the.Peer.GetGroups() for i := range groups { group := the.Peer.GetGroup(groups[i]) nick, exists := group.GetAttribute("nick") if !exists { nick = group.GroupID[:12] } deleted,_ := group.GetAttribute("deleted") // Only join servers for active and explicitly accepted groups. if deleted != "deleted"{ gcd.UIState.AddContact(&gobjects.Contact{ Handle: group.GroupID, DisplayName: nick, Image: cwutil.RandomGroupImage(group.GroupID), Server: group.GroupServer, Trusted: group.Accepted, }) } // Only send a join server packet if we haven't joined this server yet... _,connecting := the.Peer.GetServers()[group.GroupServer] if group.Accepted && !connecting { the.Peer.JoinServer(group.GroupServer) } } }