package main import ( libapp "cwtch.im/cwtch/app" "cwtch.im/cwtch/event/bridge" "cwtch.im/ui/go/characters" "cwtch.im/ui/go/gobjects" "cwtch.im/ui/go/gothings" "cwtch.im/ui/go/gothings/android" "cwtch.im/ui/go/the" "flag" "git.openprivacy.ca/openprivacy/libricochet-go/connectivity" "git.openprivacy.ca/openprivacy/libricochet-go/log" "github.com/therecipe/qt/androidextras" "github.com/therecipe/qt/core" "github.com/therecipe/qt/gui" "github.com/therecipe/qt/network" "github.com/therecipe/qt/qml" "github.com/therecipe/qt/quickcontrols2" "os" "os/user" "path" "path/filepath" "runtime" ) const androidBaseDir = "/data/data/ca.openprivacy.cwtch.ui/" var ( buildVer string buildDate string ) func init() { // make go-defined types available in qml gothings.GrandCentralDispatcher_QmlRegisterType2("CustomQmlTypes", 1, 0, "GrandCentralDispatcher") } func main() { if runtime.GOOS == "windows" { filelogger, err := log.NewFile(log.LevelInfo, "cwtch_log.txt") if err == nil { log.SetStd(filelogger) } } log.Infoln("ui main()\n") flagDebug := flag.Bool("debug", false, "turn on extra logging. WARNING: THIS MAY EXPOSE PRIVATE INFORMATION IN CONSOLE OUTPUT!") flagLocal := flag.Bool("local", false, "load user interface from the local folder \"qml\" instead of the built-in UI") flagService := flag.Bool("service", false, "run this process as an android service") flagClientUI := flag.Bool("clientui", false, "start the UI as a client of a service app instead of a full app") flag.Parse() if *flagDebug { log.SetLevel(log.LevelDebug) } else { log.SetLevel(log.LevelInfo) } // TESTING //log.SetLevel(log.LevelDebug) //log.ExcludeFromPattern("connection/connection") //log.ExcludeFromPattern("outbound/3dhauthchannel") //log.AddNothingExceptFilter("event/eventmanager") 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 { log.Errorf("\nerror: could not load current user: %v\n", err) os.Exit(1) } the.CwtchDir = path.Join(usr.HomeDir, ".cwtch") } the.ACN = nil the.Peer = nil the.IPCBridge = nil the.CwtchApp = nil the.CwtchService = nil os.MkdirAll(the.CwtchDir, 0700) if *flagService { mainService() } else { clientUI := *flagClientUI || runtime.GOOS == "android" mainUi(*flagLocal, clientUI) } if the.ACN != nil { the.ACN.Close() } } // QRunnable is a shim for QAndroidService and QGuiApplication type QRunnable interface { Exec() int } func mainService() { log.Infoln("I am the service") log.Infoln("Starting a cwtch app...") go loadNetworkingAndFiles(nil, true, false) var app QRunnable if runtime.GOOS == "android" { log.Infoln("Making QAndroidService...") app = androidextras.NewQAndroidService(len(os.Args), os.Args) } else { log.Infoln("Making QGuiApplication...") app = gui.NewQGuiApplication(len(os.Args), os.Args) } log.Infoln("Cwtch Service starting app.Exec") app.Exec() } func mainUi(flagLocal bool, flagClientUI bool) { log.Infof("I am the UI (client:%v)\n", flagClientUI) app := gui.NewQGuiApplication(len(os.Args), os.Args) // our globals gcd := gothings.NewGrandCentralDispatcher(nil) gcd.SetOs(runtime.GOOS) if buildVer != "" { gcd.SetVersion(buildVer) gcd.SetBuildDate(buildDate) } else { gcd.SetVersion("development") gcd.SetBuildDate("now") } gcd.UIState = gothings.NewUIState(gcd) 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 flagLocal { qmlSource = core.QUrl_FromLocalFile("./qml/main.qml") } else { qmlSource = core.NewQUrl3("qrc:/qml/main.qml", 0) } app.SetWindowIcon(gui.NewQIcon5(":/qml/images/cwtch-icon.png")) // load english first so it becomes the default in case we don't have a .ts for the user's locale, or if it contains unfinished strings translator := core.NewQTranslator(nil) translator.Load("translation_en", ":/i18n/", "", "") core.QCoreApplication_InstallTranslator(translator) gcd.Translator = core.NewQTranslator(nil) gcd.Translator.Load("translation_"+core.QLocale_System().Name(), ":/i18n/", "", "") core.QCoreApplication_InstallTranslator(gcd.Translator) core.QCoreApplication_SetAttribute(core.Qt__AA_EnableHighDpiScaling, true) quickcontrols2.QQuickStyle_SetStyle("Universe") engine := qml.NewQQmlApplicationEngine(nil) gcd.QMLEngine = engine // 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) // 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) var androidCwtchActivity = android.NewCwtchActivity(nil) engine.RootContext().SetContextProperty("androidCwtchActivity", androidCwtchActivity) engine.Load(qmlSource) go loadNetworkingAndFiles(gcd, false, flagClientUI) log.Infoln("Cwtch App starting app.Exec") app.Exec() } func loadACN() { torpath := "tor" if runtime.GOOS == "android" { torpath = path.Join(androidBaseDir, "lib/libtor.so") } else if runtime.GOOS == "windows" { ex, err := os.Executable() if err != nil { ex = "" } exPath := filepath.Dir(ex) torpath = path.Join(exPath, "tor-0.3.5.7", "Tor", "tor.exe") } 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) { log.Warnln("Cannot find bundled Tor") } else { torpath = path.Join(dir, "deploy", "linux", "tor") } } else { torpath = path.Join(dir, "tor") } } var err error the.ACN, err = connectivity.StartTor(the.CwtchDir, torpath) if err != nil { log.Errorf("Could not start Tor: %v", err) os.Exit(1) } } func loadNetworkingAndFiles(gcd *gothings.GrandCentralDispatcher, service bool, clientUI bool) { if service || clientUI || runtime.GOOS == "android" { clientIn := path.Join(the.CwtchDir, "clientIn") serviceIn := path.Join(the.CwtchDir, "serviceIn") if service { loadACN() serviceBridge := bridge.NewPipeBridgeService(serviceIn, clientIn) log.Infoln("Creating New App Service") the.CwtchService = libapp.NewAppService(the.ACN, the.CwtchDir, serviceBridge) } else { clientBridge := bridge.NewPipeBridgeClient(clientIn, serviceIn) log.Infoln("Creating New App Client") the.CwtchApp = libapp.NewAppClient(the.CwtchDir, clientBridge) } } else { loadACN() log.Infoln("Creating New App") the.CwtchApp = libapp.NewApp(the.ACN, the.CwtchDir) } if !service { the.AppBus = the.CwtchApp.GetPrimaryBus() subscribed := make(chan bool) go characters.AppEventListener(gcd, subscribed) <-subscribed } if !service && !clientUI { the.CwtchApp.LoadProfiles(the.AppPassword) } }