package ui import ( "cwtch.im/cwtch/app" "cwtch.im/cwtch/event" "cwtch.im/cwtch/model" "cwtch.im/cwtch/model/attr" "cwtch.im/cwtch/protocol/connections" "cwtch.im/ui/go/constants" "cwtch.im/ui/go/features/groups" "cwtch.im/ui/go/ui/android" "encoding/json" "github.com/therecipe/qt/qml" "strconv" "sync" "time" "cwtch.im/ui/go/the" "encoding/base32" "git.openprivacy.ca/openprivacy/log" "github.com/therecipe/qt/core" "strings" ) type GrandCentralDispatcher struct { core.QObject AndroidCwtchActivity *android.CwtchActivity QMLEngine *qml.QQmlApplicationEngine Translator, OpaqueTranslator *core.QTranslator uIManagers map[string]Manager // profile-onion : Manager TimelineInterface *MessageModel GlobalSettings *GlobalSettings profileLock sync.Mutex conversationLock sync.Mutex m_selectedProfile string _ int `property:"torStatus"` _ string `property:"os"` _ bool `property:"firstTime"` _ int `property:"scaleFactor,auto,changed"` _ string `property:"theme,auto,changed"` _ string `property:"locale,auto,changed"` _ string `property:"version"` _ string `property:"torVersion"` _ string `property:"buildDate"` _ string `property:"assetPath"` _ string `property:"selectedProfile,auto"` _ string `property:"selectedConversation,auto,changed"` _ bool `property:"experimentsEnabled,auto,changed"` _ map[string]bool `property:"experiments,auto,changed"` // general ui _ func(pant int) `signal:"ChangeRootPane"` _ func(pane int) `signal:"ChangeProfilePane"` // profile management stuff _ func() `signal:"Loaded"` _ func(handle, displayname, image, tag string, online bool) `signal:"AddProfile"` _ func() `signal:"ErrorLoaded0"` _ func() `signal:"ResetProfile"` _ func() `signal:"ResetProfileList"` _ func(failed bool) `signal:"ChangePasswordResponse"` _ func(onion string, online bool) `signal:"UpdateProfileNetworkStatus"` _ func(onion string) `signal:"Notify"` // server management _ func(handle, displayname, image string, status int, autostart bool, bundle string, messages int, key_types []string, keys []string) `signal:"AddServer"` _ func() `signal:"requestServers,auto"` _ func() `signal:"newServer,auto"` _ func(server string) `signal:"startServer,auto"` _ func(server string) `signal:"stopServer,auto"` _ func(server string) `signal:"checkServer,auto"` _ func(server string, enabled bool) `signal:"autostartServer,auto"` // contact list stuff _ func(handle, displayName, image string, badge, status int, authorization string, loading bool, lastMsgTime int) `signal:"AddContact"` _ func(handle, displayName string) `signal:"UpdateContactDisplayName"` _ func(handle, image string) `signal:"UpdateContactPicture"` _ func(handle string, status int, loading bool) `signal:"UpdateContactStatus"` _ func(handle string) `signal:"IncContactUnreadCount"` _ func(handle string) `signal:"RemoveContact"` _ func(handle, key, value string) `signal:"UpdateContactAttribute"` // messages pane stuff _ func() `signal:"ClearMessages"` _ func() `signal:"ResetMessagePane"` _ func(mID string) `signal:"Acknowledged"` _ func(title string) `signal:"SetToolbarTitle"` _ func(signature string, err string) `signal:"GroupSendError"` _ func(loading bool) `signal:"SetLoadingState"` // profile-area stuff _ func(name, onion, image, tag, showBlocked string, online bool) `signal:"UpdateMyProfile"` // settings helpers _ func(str string) `signal:"InvokePopup"` _ func(locale string, scale int, theme string) `signal:"SupplySettings"` _ func(groupID, name, server, invitation string, accepted bool, addrbooknames, addrbookaddrs []string) `signal:"SupplyGroupSettings"` _ func(onion, nick string, authorization string, storage string) `signal:"SupplyPeerSettings"` _ func(server string, key_types []string, keys []string) `signal:"SupplyServerSettings"` // signals emitted from the ui (and implemented in go, below) // ui _ func() `signal:"onActivate,auto"` _ func(pane int) `signal:"setRootPaneState,auto"` _ func(pane int) `signal:"setProfilePaneState,auto"` // profile managemenet _ func(onion, nick string) `signal:"updateNick,auto"` _ func(handle string) `signal:"loadProfile,auto"` _ func(nick string, defaultPass bool, password string) `signal:"createProfile,auto"` _ func(password string) `signal:"unlockProfiles,auto"` _ func() `signal:"reloadProfileList,auto"` _ func(onion string) `signal:"deleteProfile,auto"` _ func(onion, currentPassword, newPassword string, defaultPass bool) `signal:"changePassword,auto"` _ func(key, val string) `signal:"storeSetting,auto"` // operating a profile _ func(message string) `signal:"sendMessage,auto"` _ func(onion string, auth string) `signal:"setPeerAuthorization,auto"` _ func(onion string) `signal:"loadMessagesPane,auto"` _ func(signal string) `signal:"broadcast,auto"` // convenience relay signal _ func(name, address string) `signal:"addPeer,auto"` _ func(address string) `signal:"addGroup,auto"` _ func(str string) `signal:"createContact,auto"` _ func(str string) `signal:"popup,auto"` _ func(server, groupName string) `signal:"createGroup,auto"` _ func(groupID string) `signal:"leaveGroup,auto"` _ func(groupID string) `signal:"acceptGroup,auto"` _ func() `signal:"requestSettings,auto"` _ func(groupID string) `signal:"requestGroupSettings,auto"` _ func(groupID, nick string) `signal:"saveGroupSettings,auto"` _ func(handle string) `signal:"requestPeerSettings,auto"` _ func(onion, nick string) `signal:"savePeerSettings,auto"` _ func(onion, groupID string) `signal:"inviteToGroup,auto"` _ func(onion string) `signal:"deleteContact,auto"` _ func() `signal:"allowUnknownPeers,auto"` _ func() `signal:"blockUnknownPeers,auto"` _ func(onion string) `signal:"storeHistoryForPeer,auto"` _ func(onion string) `signal:"deleteHistoryForPeer,auto"` // chat _ func(mID string) `slot:"peerAckAlert,auto"` _ func(handle string) `signal:"requestServerSettings,auto"` _ func() `constructor:"init"` // legacy overlay model support _ func(onion string) `signal:"legacyLoadOverlay,auto"` _ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts int64, ackd bool, error bool) `signal:"AppendMessage"` _ func(handle, from, displayName, message, image string, mID string, fromMe bool, ts int64, ackd bool, error bool) `signal:"PrependMessage"` } func (this *GrandCentralDispatcher) init() { this.uIManagers = make(map[string]Manager) firstTime := false this.GlobalSettings, firstTime = ReadGlobalSettings() this.SetFirstTime(firstTime) this.SetScaleFactor(this.GlobalSettings.Scale) this.SetTheme(this.GlobalSettings.Theme) this.SetExperimentsEnabled(this.GlobalSettings.ExperimentsEnabled) this.SetExperiments(this.GlobalSettings.Experiments) this.AndroidCwtchActivity = android.NewCwtchActivity(nil) // Per main.qml // managementPane:0 settingsPane:1 addEditProfilePane:2 profilePane:3 addEditServerPane:4 // We can't support addEditProfile(2) or addEditServer(4) as we don't store the target id for those yet: TODO // We don't switch here to profilePane(3) as we need to wait for appHandler to identify and set the selectedProfile // managementPane(0) is a NOP as it's default pane if this.GlobalSettings.StateRootPane == 1 { this.ChangeRootPane(this.GlobalSettings.StateRootPane) } } // GetUiManager gets (and creates if required) a ui Manager for the supplied profile id func (this *GrandCentralDispatcher) GetUiManager(profile string) Manager { this.profileLock.Lock() defer this.profileLock.Unlock() if manager, exists := this.uIManagers[profile]; exists { return manager } else { this.uIManagers[profile] = NewManager(profile, this) return this.uIManagers[profile] } } func (this *GrandCentralDispatcher) selectedProfile() string { this.profileLock.Lock() defer this.profileLock.Unlock() return this.m_selectedProfile } func (this *GrandCentralDispatcher) setSelectedProfile(onion string) { this.profileLock.Lock() defer this.profileLock.Unlock() p := the.CwtchApp.GetPeer(onion) if p != nil { p.SetAttribute(attr.GetSettingsScope(constants.StateSelectedProfileTime), strconv.FormatInt(time.Now().Unix(), 10)) } this.m_selectedProfile = onion } func (this *GrandCentralDispatcher) selectedProfileChanged(onion string) { this.SelectedProfileChanged(onion) } func (this *GrandCentralDispatcher) setRootPaneState(pane int) { this.GlobalSettings.StateRootPane = pane WriteGlobalSettings(this.GlobalSettings) } // DoIfProfile performs a gcd action for a profile IF it is the currently selected profile in the UI // otherwise it does nothing. it also locks profile switching for the duration of the action func (this *GrandCentralDispatcher) DoIfProfile(profile string, fn func()) { this.profileLock.Lock() defer this.profileLock.Unlock() if this.m_selectedProfile == profile { fn() } } // Like DoIfProfile() but runs elseFn() if profile isn't the currently selected one in the UI func (this *GrandCentralDispatcher) DoIfProfileElse(profile string, fn func(), elseFn func()) { this.profileLock.Lock() defer this.profileLock.Unlock() if this.m_selectedProfile == profile { fn() } else { elseFn() } } func (this *GrandCentralDispatcher) setProfilePaneState(pane int) { this.profileLock.Lock() defer this.profileLock.Unlock() the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateProfilePane), strconv.Itoa(pane)) } /*func (this *GrandCentralDispatcher) selectedConversation() string { this.conversationLock.Lock() defer this.conversationLock.Unlock() return this.m_selectedConversation } func (this *GrandCentralDispatcher) setSelectedConversation(handle string) { this.conversationLock.Lock() defer this.conversationLock.Unlock() the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation), handle) this.m_selectedConversation = handle }*/ func (this *GrandCentralDispatcher) selectedConversationChanged(handle string) { the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation), handle) } // DoIfConversation performs a gcd action for a conversation IF it is the currently selected conversation in the UI // otherwise it does nothing. it also locks conversation switching for the duration of the action func (this *GrandCentralDispatcher) DoIfConversation(conversation string, fn func()) { this.conversationLock.Lock() defer this.conversationLock.Unlock() if this.SelectedConversation() == conversation { fn() } } // like DoIfConversation() but func (this *GrandCentralDispatcher) DoIfConversationElse(conversation string, fn func(), elseFn func()) { this.conversationLock.Lock() defer this.conversationLock.Unlock() if this.SelectedConversation() == conversation { fn() } else { elseFn() } } func (this *GrandCentralDispatcher) sendMessage(message string) { if len(message) > 65530 { this.InvokePopup("message is too long") return } if this.SelectedConversation() == "" { this.InvokePopup("ui error") return } if isGroup(this.SelectedConversation()) { if gf, err := groups.ExperimentGate(this.GlobalSettings.Experiments); err == nil { groupHandle := this.SelectedConversation() this.TimelineInterface.AddMessage(this.TimelineInterface.num()) err := gf.SendMessage(groupHandle, message) this.TimelineInterface.RequestEIR() if err != nil { this.InvokePopup("failed to send message " + err.Error()) return } } else { this.InvokePopup("Groups are currently disabled by an experiment gate. turn it on in Settings") return } } else { this.TimelineInterface.AddMessage(this.TimelineInterface.num()) the.Peer.SendMessageToPeer(this.SelectedConversation(), message) this.TimelineInterface.RequestEIR() } } func (this *GrandCentralDispatcher) loadMessagesPane(handle string) { go this.loadMessagesPaneHelper(handle) } func (this *GrandCentralDispatcher) loadMessagesPaneHelper(handle string) { if handle == the.Peer.GetOnion() { return } this.ClearMessages() this.SetSelectedConversation(handle) if isGroup(handle) { // LOAD GROUP group := the.Peer.GetGroup(handle) loading := false state := connections.ConnectionStateToType[group.State] if state == connections.AUTHENTICATED { loading = true } this.UpdateContactStatus(group.GroupID, int(state), loading) this.requestGroupSettings(handle) nick := GetNick(handle) updateLastReadTime(group.GroupID) if nick == "" { this.SetToolbarTitle(handle) } else { this.SetToolbarTitle(nick) } this.legacyLoadOverlay(handle) return } // ELSE LOAD CONTACT contact := the.Peer.GetContact(handle) this.UpdateContactStatus(handle, int(connections.ConnectionStateToType[contact.State]), false) this.requestPeerSettings(handle) var nick string if contact != nil { nick = GetNick(contact.Onion) } updateLastReadTime(contact.Onion) this.SetToolbarTitle(nick) this.legacyLoadOverlay(handle) } func (this *GrandCentralDispatcher) legacyLoadOverlay(handle string) { // only do this for overlays 2 (bulletin) and 4 (lists) go this.legacyLoadOverlay_helper(handle, []int{2,4}) } func contains(arr []int, x int) bool { for _, v := range arr { if v == x { return true } } return false } func (this *GrandCentralDispatcher) legacyLoadOverlay_helper(handle string, overlays []int) { if isGroup(handle) { group := the.CwtchApp.GetPeer(this.selectedProfile()).GetGroup(handle) tl := group.GetTimeline() for i := len(tl) - 1; i >= 0; i-- { if tl[i].PeerID == this.selectedProfile() { handle = "me" } else { handle = tl[i].PeerID } name := GetNick(tl[i].PeerID) image := GetProfilePic(tl[i].PeerID) obj := &OverlayJSONObject{} err := json.Unmarshal([]byte(tl[i].Message), obj) if err == nil && contains(overlays, obj.Overlay) { this.PrependMessage( handle, tl[i].PeerID, name, tl[i].Message, image, string(tl[i].Signature), tl[i].PeerID == this.selectedProfile(), tl[i].Timestamp.Unix(), tl[i].Received.Equal(time.Unix(0, 0)) == false, // If the received timestamp is epoch, we have not yet received this message through an active server false, ) } } } else {// !isGroup messages := the.CwtchApp.GetPeer(this.selectedProfile()).GetContact(handle).Timeline.GetMessages() for i := len(messages) - 1; i >= 0; i-- { from := messages[i].PeerID fromMe := messages[i].PeerID == the.Peer.GetOnion() if fromMe { from = "me" } displayname := GetNick(messages[i].PeerID) image := GetProfilePic(messages[i].PeerID) obj := &OverlayJSONObject{} err := json.Unmarshal([]byte(messages[i].Message), obj) if err == nil && contains(overlays, obj.Overlay) { this.PrependMessage( from, messages[i].PeerID, displayname, messages[i].Message, image, string(messages[i].Signature), fromMe, messages[i].Timestamp.Unix(), messages[i].Acknowledged, messages[i].Error != "", ) } } } } func (this *GrandCentralDispatcher) requestSettings() { this.SupplySettings(this.GlobalSettings.Locale, this.GlobalSettings.Scale, this.GlobalSettings.Theme) } func (this *GrandCentralDispatcher) saveSettings(zoom, locale string) { } func (this *GrandCentralDispatcher) requestPeerSettings(handle string) { contact := the.Peer.GetContact(handle) if contact == nil { log.Errorf("error: requested settings for unknown contact %v?", handle) this.SupplyPeerSettings(this.SelectedConversation(), this.SelectedConversation(), string(model.AuthUnknown), "") return } name := GetNick(contact.Onion) // Todo: Move to profile settings //blockunkownpeers, _ := the.Peer.GetAttribute(attr.GetPeerScope(constants.BlockUnknownPeersSetting)) // Whether Cwtch should save the history of the peer saveHistory, exists := contact.GetAttribute(event.SaveHistoryKey) if !exists { saveHistory = event.DeleteHistoryDefault } this.SupplyPeerSettings(contact.Onion, name, string(contact.Authorization), saveHistory) } func (this *GrandCentralDispatcher) savePeerSettings(onion, nick string) { the.Peer.SetContactAttribute(onion, attr.GetLocalScope(constants.Name), nick) newNick := GetNick(onion) this.UpdateContactDisplayName(onion, newNick) } func (this *GrandCentralDispatcher) storeHistoryForPeer(onion string) { the.Peer.SetContactAttribute(onion, event.SaveHistoryKey, event.SaveHistoryConfirmed) } func (this *GrandCentralDispatcher) deleteHistoryForPeer(onion string) { the.Peer.SetContactAttribute(onion, event.SaveHistoryKey, event.DeleteHistoryConfirmed) } func (this *GrandCentralDispatcher) requestServerSettings(groupID string) { group := the.Peer.GetGroup(groupID) if group == nil { log.Errorf("couldn't find group %v", groupID) return } serverInfo := the.Peer.GetContact(group.GroupServer) if serverInfo == nil { // This should never happen...there is a bug.... log.Errorf("No server info found for ", group.GroupServer) return } key_types := []model.KeyType{model.KeyTypeServerOnion, model.KeyTypeTokenOnion, model.KeyTypePrivacyPass} var keyNames []string var keys []string for _, key_type := range key_types { log.Debugf("Looking up %v %v", key_type, keyNames) if key, has := serverInfo.GetAttribute(string(key_type)); has { keyNames = append(keyNames, string(key_type)) keys = append(keys, key) } } this.SupplyServerSettings(group.GroupServer, keyNames, keys) } func (this *GrandCentralDispatcher) requestServers() { the.AppBus.Publish(event.NewEvent(constants.ListServers, map[event.Field]string{})) } func (this *GrandCentralDispatcher) newServer() { the.AppBus.Publish(event.NewEvent(constants.NewServer, map[event.Field]string{})) } func (this *GrandCentralDispatcher) startServer(onion string) { log.Debugf("Requesting Start Server: %v", onion) the.AppBus.Publish(event.NewEvent(constants.StartServer, map[event.Field]string{ event.Onion: onion, })) } func (this *GrandCentralDispatcher) stopServer(onion string) { log.Debugf("Requesting Stop Server: %v", onion) the.AppBus.Publish(event.NewEvent(constants.StopServer, map[event.Field]string{ event.Onion: onion, })) } func (this *GrandCentralDispatcher) checkServer(onion string) { log.Debugf("Requesting Stop Server: %v", onion) the.AppBus.Publish(event.NewEvent(constants.CheckServerStatus, map[event.Field]string{ event.Onion: onion, })) } func (this *GrandCentralDispatcher) autostartServer(onion string, enabled bool) { log.Debugf("Requesting Autostart Toggle: %v %v", onion, enabled) value := event.False if enabled { value = event.True } the.AppBus.Publish(event.NewEvent(constants.AutoStart, map[event.Field]string{ event.Onion: onion, constants.AutoStartEnabled: value, })) } func (this *GrandCentralDispatcher) requestGroupSettings(groupID string) { group := the.Peer.GetGroup(groupID) if group == nil { log.Errorf("couldn't find group %v", groupID) return } nick := GetNick(groupID) invite, _ := the.Peer.ExportGroup(groupID) contactaddrs := the.Peer.GetContacts() contactnames := make([]string, len(contactaddrs)) for i, contact := range contactaddrs { contactnames[i] = GetNick(contact) } this.SupplyGroupSettings(group.GroupID, nick, group.GroupServer, invite, group.Accepted, contactnames, contactaddrs) status := connections.ConnectionStateToType[group.State] log.Debugf("Sending New Group Status: %v %v", group.GroupServer, status) this.UpdateContactStatus(group.GroupServer, int(status), false) } func (this *GrandCentralDispatcher) saveGroupSettings(groupID, nick string) { the.Peer.SetGroupAttribute(groupID, attr.GetLocalScope(constants.Name), nick) this.UpdateContactDisplayName(groupID, nick) } func (this *GrandCentralDispatcher) broadcast(signal string) { switch signal { default: log.Debugf("unhandled broadcast signal: %v", signal) case "ResetMessagePane": this.ResetMessagePane() case "ResetProfile": this.ResetProfile() } } func (this *GrandCentralDispatcher) createContact(onion string) { if contact := the.Peer.GetContact(onion); contact != nil { return } the.Peer.AddContact(onion, onion, model.AuthApproved) the.Peer.PeerWithOnion(onion) } func (this *GrandCentralDispatcher) addGroup(address string) { log.Debugf("importing group: %s\n", address) address = strings.TrimSpace(address) if gf, err := groups.ExperimentGate(this.GlobalSettings.Experiments); err == nil { if gf.ValidPrefix(address) { err = gf.HandleImportString(address) if err == nil { // TODO: We need a better way of signaling the success of "invisible" actions like adding server bundles this.InvokePopup("successfully imported") return } this.InvokePopup("failed import: " + err.Error()) return } // drop through to peer import strings } } func (this *GrandCentralDispatcher) addPeer(name, address string) { log.Debugf("importing peer: %s\n", address) name = strings.TrimSpace(name) address = strings.TrimSpace(address) if len(address) != 56 { this.InvokePopup("invalid peer onion format") return } _, err := base32.StdEncoding.DecodeString(strings.ToUpper(address[:56])) if err != nil { log.Debugln(err) this.InvokePopup("bad format. missing handlers?") return } checkc := the.Peer.GetContact(address) if checkc != nil { this.InvokePopup("already have this contact") return //TODO: bring them to the duplicate } else { the.Peer.AddContact(name, address, model.AuthApproved) if name != "" { the.Peer.SetContactAttribute(address, attr.GetLocalScope(constants.Name), name) } the.Peer.PeerWithOnion(address) } this.GetUiManager(this.selectedProfile()).AddContact(address) } func (this *GrandCentralDispatcher) popup(str string) { this.InvokePopup(str) } func (this *GrandCentralDispatcher) updateNick(onion, nick string) { p := the.CwtchApp.GetPeer(onion) if p != nil { p.SetName(nick) p.SetAttribute(attr.GetPublicScope(constants.Name), nick) the.CwtchApp.GetEventBus(onion).Publish(event.NewEvent(event.SetProfileName, map[event.Field]string{ event.ProfileName: nick, })) } } func (this *GrandCentralDispatcher) createGroup(server, groupName string) { groupID, _, err := the.Peer.StartGroup(server) if err != nil { this.popup("group creation failed :(") return } this.GetUiManager(this.selectedProfile()).AddContact(groupID) the.Peer.SetGroupAttribute(groupID, attr.GetLocalScope(constants.Name), groupName) the.Peer.JoinServer(server) } func (this *GrandCentralDispatcher) setPeerAuthorization(onion string, authorization string) { log.Debugf("Setting peer auth level to %v for %v\n", authorization, onion) err := the.Peer.SetContactAuthorization(onion, model.Authorization(authorization)) if err != nil { log.Errorf("Could not set peer authorization %v to %v\n", onion, authorization) return } this.RemoveContact(onion) this.GetUiManager(this.selectedProfile()).AddContact(onion) if model.Authorization(authorization) == model.AuthApproved { the.Peer.PeerWithOnion(onion) } } func (this *GrandCentralDispatcher) inviteToGroup(onion, groupID string) { err := the.Peer.InviteOnionToGroup(onion, groupID) if err != nil { log.Errorf("inviting %v to %v: %v", onion, groupID, err) } } func (this *GrandCentralDispatcher) leaveGroup(groupID string) { the.Peer.DeleteGroup(groupID) this.RemoveContact(groupID) } func (this *GrandCentralDispatcher) deleteContact(onion string) { the.Peer.DeleteContact(onion) this.RemoveContact(onion) } func (this *GrandCentralDispatcher) acceptGroup(groupID string) { if the.Peer.GetGroup(groupID) != nil { the.Peer.AcceptInvite(groupID) } } func (this *GrandCentralDispatcher) blockUnknownPeers() { the.Peer.SetAttribute(attr.GetSettingsScope(constants.BlockUnknownPeersSetting), "true") the.EventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{})) } func (this *GrandCentralDispatcher) allowUnknownPeers() { the.Peer.SetAttribute(attr.GetSettingsScope(constants.BlockUnknownPeersSetting), "false") the.EventBus.Publish(event.NewEvent(event.AllowUnknownPeers, map[event.Field]string{})) } func (this *GrandCentralDispatcher) localeChanged(locale string) { this.GlobalSettings.Locale = locale WriteGlobalSettings(this.GlobalSettings) this.setLocaleHelper(locale) } func (this *GrandCentralDispatcher) setLocaleHelper(locale string) { log.Debugf("Loading translators for '%v'\n", locale) newTranslator := core.NewQTranslator(nil) success := newTranslator.Load("translation_"+locale, ":/i18n/", "", "") if success { core.QCoreApplication_RemoveTranslator(this.Translator) this.Translator = newTranslator core.QCoreApplication_InstallTranslator(this.Translator) } else { log.Errorf("Could not load translator for '%v'\n", locale) } newOpaqueTranslator := core.NewQTranslator(nil) success = newOpaqueTranslator.Load("translation_"+locale, ":/qml/opaque/i18n/", "", "") if success { core.QCoreApplication_RemoveTranslator(this.OpaqueTranslator) this.OpaqueTranslator = newOpaqueTranslator core.QCoreApplication_InstallTranslator(this.OpaqueTranslator) } else { log.Errorf("Could not load opaque translator for '%v'\n", locale) } this.QMLEngine.Retranslate() } func (this *GrandCentralDispatcher) scaleFactorChanged(newScale int) { this.GlobalSettings.Scale = newScale WriteGlobalSettings(this.GlobalSettings) } // Turn on/off global experiments func (this *GrandCentralDispatcher) experimentsEnabledChanged(enabled bool) { this.GlobalSettings.ExperimentsEnabled = enabled log.Debugf("Experiments Enabled: %v %v", enabled, this.GlobalSettings.ExperimentsEnabled) WriteGlobalSettings(this.GlobalSettings) } // Turn on/off global experiments func (this *GrandCentralDispatcher) experimentsChanged(experiments map[string]bool) { for k, v := range experiments { this.GlobalSettings.Experiments[k] = v } log.Debugf("Experiments: %v", experiments) WriteGlobalSettings(this.GlobalSettings) } func (this *GrandCentralDispatcher) themeChanged(newTheme string) { this.GlobalSettings.Theme = newTheme WriteGlobalSettings(this.GlobalSettings) } func (this *GrandCentralDispatcher) onActivate() { log.Debugln("onActivate") if the.CwtchApp != nil { go the.CwtchApp.QueryACNStatus() } } func (this *GrandCentralDispatcher) unlockProfiles(password string) { the.CwtchApp.LoadProfiles(password) } func (this *GrandCentralDispatcher) loadProfile(onion string) { the.Peer = the.CwtchApp.GetPeer(onion) if the.Peer == nil { return } the.EventBus = the.CwtchApp.GetEventBus(onion) picVal, exists := the.Peer.GetAttribute(attr.GetPublicScope(constants.Picture)) if !exists { picVal = ImageToString(NewImage(RandomProfileImage(onion), TypeImageDistro)) the.Peer.SetAttribute(attr.GetPublicScope(constants.Picture), picVal) } pic, err := StringToImage(picVal) if err != nil { pic = NewImage(RandomProfileImage(onion), TypeImageDistro) the.Peer.SetAttribute(attr.GetPublicScope(constants.Picture), ImageToString(pic)) } tag, _ := the.Peer.GetAttribute(app.AttributeTag) showBlocked, exists := the.Peer.GetAttribute(attr.GetSettingsScope(constants.ShowBlocked)) if !exists { showBlocked = "false" the.Peer.SetAttribute(attr.GetSettingsScope(constants.ShowBlocked), showBlocked) } online, _ := the.Peer.GetAttribute(attr.GetLocalScope(constants.PeerOnline)) this.UpdateMyProfile(the.Peer.GetName(), the.Peer.GetOnion(), getPicturePath(pic), tag, showBlocked, online == event.True) contacts := the.Peer.GetContacts() for i := range contacts { if the.Peer.GetContact(contacts[i]).IsServer() == false { this.GetUiManager(this.selectedProfile()).AddContact(contacts[i]) } } // Groups Gating if _, err := groups.ExperimentGate(this.GlobalSettings.Experiments); err == nil { the.Peer.StartServerConnections() groups := the.Peer.GetGroups() for i := range groups { // Only join servers for active and explicitly accepted groups. this.GetUiManager(this.selectedProfile()).AddContact(groups[i]) } } else { // Leave all active groups for this Peer. groups := the.Peer.GetGroups() for i := range groups { group := the.Peer.GetGroup(groups[i]) the.EventBus.Publish(event.NewEvent(event.LeaveServer, map[event.Field]string{ event.GroupServer: group.GroupServer, })) } } selectedPane, pok := the.Peer.GetAttribute(attr.GetSettingsScope(constants.StateProfilePane)) if pok { selectedPaneId, err := strconv.Atoi(selectedPane) if err == nil { // emptyPane:0 addPeerGroupPane:4 main.qml if selectedPaneId != 0 && selectedPaneId != 4 { selectedContact, cok := the.Peer.GetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation)) if cok { this.Broadcast("ResetMessagePane") this.SetSelectedConversation(selectedContact) this.TimelineInterface.handle = selectedContact this.loadMessagesPane(selectedContact) if isPeer(selectedContact) { this.requestPeerSettings(selectedContact) } else { this.requestGroupSettings(selectedContact) } this.ChangeProfilePane(selectedPaneId) } } else { this.ChangeProfilePane(selectedPaneId) } } } } func (this *GrandCentralDispatcher) createProfile(nick string, defaultPass bool, password string) { if defaultPass { the.CwtchApp.CreateTaggedPeer(nick, the.AppPassword, constants.ProfileTypeV1DefaultPassword) } else { the.CwtchApp.CreateTaggedPeer(nick, password, constants.ProfileTypeV1Password) } } func (this *GrandCentralDispatcher) changePassword(onion, currentPassword, newPassword string, defaultPass bool) { tag, _ := the.CwtchApp.GetPeer(onion).GetAttribute(app.AttributeTag) if tag == constants.ProfileTypeV1DefaultPassword { currentPassword = the.AppPassword } if defaultPass { newPassword = the.AppPassword } the.CwtchApp.ChangePeerPassword(onion, currentPassword, newPassword) } func (this *GrandCentralDispatcher) storeSetting(key, val string) { the.Peer.SetAttribute(attr.GetSettingsScope(key), val) } func (this *GrandCentralDispatcher) reloadProfileList() { this.ResetProfileList() for onion := range the.CwtchApp.ListPeers() { AddProfile(this, onion) } } func (this *GrandCentralDispatcher) deleteProfile(onion string) { log.Infof("deleteProfile %v\n", onion) the.CwtchApp.DeletePeer(onion) } func (this *GrandCentralDispatcher) peerAckAlert(mID string) { idx, _ := strconv.Atoi(mID) this.TimelineInterface.EditMessage(idx) }