Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Send current synthDriver name and brailleDisplay name to update server for stats gathering. #8217

Merged
merged 10 commits into from Jun 17, 2018
2 changes: 2 additions & 0 deletions source/config/configSpec.py
Expand Up @@ -188,6 +188,8 @@
[update]
autoCheck = boolean(default=true)
startupNotification = boolean(default=true)
allowUsageStats = boolean(default=false)
askedAllowUsageStats = boolean(default=false)

[inputComposition]
autoReportAllCandidates = boolean(default=True)
Expand Down
2 changes: 2 additions & 0 deletions source/core.py
Expand Up @@ -61,6 +61,8 @@ def doStartupDialogs():
gui.messageBox(_("Your gesture map file contains errors.\n"
"More details about the errors can be found in the log file."),
_("gesture map File Error"), wx.OK|wx.ICON_EXCLAMATION)
if not globalVars.appArgs.secure and not config.isAppX and not config.conf['update']['askedAllowUsageStats']:
gui.runScriptModalDialog(gui.AskAllowUsageStatsDialog(None))

def restart(disableAddons=False, debugLogging=False):
"""Restarts NVDA by starting a new copy with -r."""
Expand Down
58 changes: 58 additions & 0 deletions source/gui/__init__.py
Expand Up @@ -924,3 +924,61 @@ def shouldConfigProfileTriggersBeSuspended():

def _isDebug():
return config.conf["debugLog"]["gui"]

class AskAllowUsageStatsDialog(wx.Dialog):
"""A dialog asking if the user wishes to allow NVDA usage stats to be collected by NV Access."""

def __init__(self, parent):
# Translators: The title of the dialog asking if usage data can be collected
super(AskAllowUsageStatsDialog, self).__init__(parent, title=_("NVDA Usage Data Collection"))
mainSizer = wx.BoxSizer(wx.VERTICAL)
sHelper = guiHelper.BoxSizerHelper(self, orientation=wx.VERTICAL)

# Translators: A message asking the user if they want to allow usage stats gathering
message=_("In order to improve NVDA in the future, NV Access wishes to collect usage data from running copies of NVDA.\n\n"
"Data includes Operating System version, NVDA version, language, country of origin, plus certain NVDA configuration such as current synthesizer, braille display and braille table. "
"No spoken or braille content will be ever sent to NV Access. Please refer to the User Guide for a current list of all data collected.\n\n"
"Do you wish to allow NV Access to periodically collect this data in order to improve NVDA?")
sText=sHelper.addItem(wx.StaticText(self, label=message))
# the wx.Window must be constructed before we can get the handle.
import windowUtils
self.scaleFactor = windowUtils.getWindowScalingFactor(self.GetHandle())
sText.Wrap(self.scaleFactor*600) # 600 was fairly arbitrarily chosen by a visual user to look acceptable on their machine.

bHelper = sHelper.addDialogDismissButtons(guiHelper.ButtonHelper(wx.HORIZONTAL))

# Translators: The label of a Yes button in a dialog
yesButton = bHelper.addButton(self, wx.ID_YES, label=_("&Yes"))
yesButton.Bind(wx.EVT_BUTTON, self.onYesButton)

# Translators: The label of a No button in a dialog
noButton = bHelper.addButton(self, wx.ID_NO, label=_("&No"))
noButton.Bind(wx.EVT_BUTTON, self.onNoButton)

# Translators: The label of a button to remind the user later about performing some action.
remindMeButton = bHelper.addButton(self, wx.ID_CANCEL, label=_("Remind me &later"))
remindMeButton.Bind(wx.EVT_BUTTON, self.onLaterButton)
remindMeButton.SetFocus()

mainSizer.Add(sHelper.sizer, border=guiHelper.BORDER_FOR_DIALOGS, flag=wx.ALL)
self.Sizer = mainSizer
mainSizer.Fit(self)
self.Center(wx.BOTH | wx.CENTER_ON_SCREEN)

def onYesButton(self,evt):
log.debug("Usage stats gathering has been allowed")
config.conf['update']['askedAllowUsageStats']=True
config.conf['update']['allowUsageStats']=True
self.EndModal(wx.ID_YES)

def onNoButton(self,evt):
log.debug("Usage stats gathering has been disallowed")
config.conf['update']['askedAllowUsageStats']=True
config.conf['update']['allowUsageStats']=False
self.EndModal(wx.ID_NO)

def onLaterButton(self,evt):
log.debug("Usage stats gathering question has been deferred")
# evt.Skip() is called since wx.ID_CANCEL is used as the ID for the Ask Later button,
# wx automatically ends the modal itself.
evt.Skip()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm surprised that you don't need to call EndModal here? Could it be because of the evt.Skip()?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why are you calling Skip() on this?

7 changes: 7 additions & 0 deletions source/gui/settingsDialogs.py
Expand Up @@ -633,6 +633,12 @@ def makeSettings(self, settingsSizer):
if globalVars.appArgs.secure:
item.Disable()
settingsSizerHelper.addItem(item)
# Translators: The label of a checkbox in general settings to toggle allowing of usage stats gathering
item=self.allowUsageStatsCheckBox=wx.CheckBox(self,label=_("Allow the NVDA project to gather NVDA usage statistics"))
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually think it makes sense to reposition this checkbox after the notifyForPendingUpdateCheckBox. The current order of check boxes is a bit arbitrary now.

item.Value=config.conf["update"]["allowUsageStats"]
if globalVars.appArgs.secure:
item.Disable()
settingsSizerHelper.addItem(item)
# Translators: The label of a checkbox in general settings to toggle startup notifications
# for a pending NVDA update.
item=self.notifyForPendingUpdateCheckBox=wx.CheckBox(self,label=_("Notify for &pending update on startup"))
Expand Down Expand Up @@ -703,6 +709,7 @@ def onSave(self):
gui.messageBox(_("This change requires administrator privileges."), _("Insufficient Privileges"), style=wx.OK | wx.ICON_ERROR, parent=self)
if updateCheck:
config.conf["update"]["autoCheck"]=self.autoCheckForUpdatesCheckBox.IsChecked()
config.conf["update"]["allowUsageStats"]=self.allowUsageStatsCheckBox.IsChecked()
config.conf["update"]["startupNotification"]=self.notifyForPendingUpdateCheckBox.IsChecked()
updateCheck.terminate()
updateCheck.initialize()
Expand Down
16 changes: 11 additions & 5 deletions source/logHandler.py
Expand Up @@ -32,6 +32,16 @@
RPC_E_DISCONNECTED = -2147417848
LOAD_WITH_ALTERED_SEARCH_PATH=0x8

def isPathExternalToNVDA(path):
""" Checks if the given path is external to NVDA (I.e. not pointing to built-in code). """
if path[0] != "<" and os.path.isabs(path) and not path.startswith(sys.path[0] + "\\"):
# This module is external because:
# the code comes from a file (fn doesn't begin with "<");
# it has an absolute file path (code bundled in binary builds reports relative paths); and
# it is not part of NVDA's Python code (not beneath sys.path[0]).
return True
return False

def getCodePath(f):
"""Using a frame object, gets its module path (relative to the current directory).[className.[funcName]]
@param f: the frame object to use
Expand All @@ -40,11 +50,7 @@ def getCodePath(f):
@rtype: string
"""
fn=f.f_code.co_filename
if fn[0] != "<" and os.path.isabs(fn) and not fn.startswith(sys.path[0] + "\\"):
# This module is external because:
# the code comes from a file (fn doesn't begin with "<");
# it has an absolute file path (code bundled in binary builds reports relative paths); and
# it is not part of NVDA's Python code (not beneath sys.path[0]).
if isPathExternalToNVDA(fn):
path="external:"
else:
path=""
Expand Down
43 changes: 40 additions & 3 deletions source/updateCheck.py
Expand Up @@ -20,6 +20,7 @@

import winVersion
import os
import inspect
import threading
import time
import cPickle
Expand All @@ -30,9 +31,12 @@
import ssl
import wx
import languageHandler
import speech
import braille
import gui
from gui import guiHelper
from logHandler import log
from addonHandler import getCodeAddon, AddonError
from logHandler import log, isPathExternalToNVDA
import config
import shellapi
import winUser
Expand Down Expand Up @@ -62,6 +66,26 @@
#: C{None} if it is disabled.
autoChecker = None

def getQualifiedDriverClassNameForStats(cls):
""" fetches the name from a given synthDriver or brailleDisplay class, and appends core for in-built code, the add-on name for code from an add-on, or external for code in the NVDA user profile.
Some examples:
espeak (core)
newfon (external)
eloquence (addon:CodeFactory)
noBraille (core)
"""
name=cls.name
try:
addon=getCodeAddon(cls)
except AddonError:
addon=None
if addon:
return "%s (addon:%s)"%(name,addon.name)
path=inspect.getsourcefile(cls)
if isPathExternalToNVDA(path):
return "%s (external)"%name
return "%s (core)"%name

def checkForUpdate(auto=False):
"""Check for an updated version of NVDA.
This will block, so it generally shouldn't be called from the main thread.
Expand All @@ -71,15 +95,28 @@ def checkForUpdate(auto=False):
@rtype: dict
@raise RuntimeError: If there is an error checking for an update.
"""
allowUsageStats=config.conf["update"]['allowUsageStats']
params = {
"autoCheck": auto,
"allowUsageStats":allowUsageStats,
"version": versionInfo.version,
"versionType": versionInfo.updateVersionType,
"osVersion": winVersion.winVersionText,
"x64": os.environ.get("PROCESSOR_ARCHITEW6432") == "AMD64",
"language": languageHandler.getLanguage(),
"installed": config.isInstalledCopy(),
}
if auto and allowUsageStats:
synthDriverClass=speech.getSynth().__class__
brailleDisplayClass=braille.handler.display.__class__ if braille.handler else None
# Following are parameters sent purely for stats gathering.
# If new parameters are added here, they must be documented in the userGuide for transparency.
extraParams={
"language": languageHandler.getLanguage(),
"installed": config.isInstalledCopy(),
"synthDriver":getQualifiedDriverClassNameForStats(synthDriverClass) if synthDriverClass else None,
"brailleDisplay":getQualifiedDriverClassNameForStats(brailleDisplayClass) if brailleDisplayClass else None,
"outputBrailleTable":config.conf['braille']['translationTable'] if brailleDisplayClass else None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Might be worth while adding a comment here to say that any additions must be added to the userguide.

}
params.update(extraParams)
url = "%s?%s" % (CHECK_URL, urllib.urlencode(params))
try:
res = urllib.urlopen(url)
Expand Down
19 changes: 19 additions & 0 deletions user_docs/en/userGuide.t2t
Expand Up @@ -968,6 +968,25 @@ This option is only available for installed copies of NVDA.
==== Automatically check for updates to NVDA ====[GeneralSettingsCheckForUpdates]
If this is enabled, NVDA will automatically check for updated versions of NVDA and inform you when an update is available.
You can also manually check for updates by selecting Check for updates under Help in the NVDA menu.
When manually or automatically checking for updates, it is necessary for NVDA to send some information to the update server in order to receive the correct update for your system.
The following information is always sent:
- Current NVDA version
- Operating System version
- Whether the Operating System is 64 or 32 bit


==== Allow the NVDA project to gather NVDA usage statistics ====[GeneralSettingsGatherUsageStats]
If this is enabled, NV Access will use the information from update checks in order to track the number of NVDA users including particular demographics such as Operating system and country of origin.
Note that although your IP address will be used to calculate your country during the update check, the IP address is never kept.
Apart from the mandatory information required to check for updates, the following extra information is also currently sent:
- NVDA interface language
- Whether this copy of NVDA is portable or installed
- Name of the current speech synthesizer in use (including the name of the add-on the driver comes from)
- Name of the current Braille display in use (including the name of the add-on the driver comes from)
- The current output Braille table (if Braille is in use)


This information greatly aides NV Access to prioritize future development of NVDA.

==== Notify for pending updates on startup ====[GeneralSettingsNotifyPendingUpdates]
If this is enabled, NVDA will inform you when there is a pending update on startup, offering you the possibility to install it.
Expand Down