Freedom Scientific Scripting Practices

This document contains a set of standards which is required for all script submissions to Freedom Scientific. Since failure to conform to these standards results in scripts which are either buggy, suffer from degraded performance, or which are not localizable to multiple languages, all submissions of scripts must conform to the following standards in order to be considered for acceptance:

**Critical**: Do not break any currently existing features or intended functionality!
  • In the JSH file, define all script constants and globals. The only exception to this is for constants or globals that must be strictly local to the current script file and that should not be exposed to other script files.

  • In the JSM file, define all messages to be spoken, and use only those message names in the JSS file.

  • In the JSM file, define all strings used for comparison or for identification as constants, and use only those string names in the JSS file.

  • In the JSM file, add a comment for string names whose meaning is not obvious, explaining to translators where to find the text those string names represent in the comparison, or where to use those string names for identification in the program.

  • In the JSM file, define all window names and classes as constants, and use only those constants in the JSS file.

  • In the JSM file, define all string variables that are messages consisting of a specific formatted pattern such that each variable uses a variable placeholder in the formatted message. Then use the FormatString() or SayFormattedMessage() functions in the JSS file to format the message.

  • In the JSM file, document all replaceable parameters for each message that has them with comments explaining what information goes into the replaceable parameters.

  • Name all defined constants and variables for windows or controls so that they are self-documenting in order that a scripter or localizer can recognize the location of the window or control.

    • If it belongs to a dialog, include the dialog name as part of the constant or variable name.

    • If it belongs to a page in a multi-page dialog, include the page name as part of the constant or variable name.

    • If it does not belong to a dialog, name the constant or variable so that the scripter or localizer can determine to which view or component of the program the window or control belongs.

  • Create different constant or variable names for windows or controls that have the same names but appear in more than one area or in unrelated areas of the program. In this way, windows or controls each have their own constant or variable names that refer specifically to where they are used.

Do not re-use the same constant or variable name to refer to different windows or controls.
  • In the JSD documentation file, document all your functions and scripts. Take care to document well the synopsis, description, and any parameters or return types for each function and script. For scripts in particular, Be sure that the text of the Synopsis and Description fields is appropriate to be spoken and displayed in Braille by the JAWS Keyboard Help feature.

  • Do not attach the spacebar or any alphanumeric keys to scripts. If special behavior is required for these keys, use the KeyPressedEvent function instead of attaching scripts to the keys.

  • Use the SayString function only as a debugging tool during scripting, and do not use this function to speak anything in the finished product.

  • Do not use braces like "{" and "}" to simulate keystrokes. Use the TypeKey() or TypeCurrentScriptKey() functions when sending a keystroke to the application.

  • Structure your code so that it tests and executes in the most expedient manner possible. When adding code to frequently-run events, be especially careful not to introduce unnecessary sluggishness.

  • Use window or object information as opposed to screen information where possible.

  • Use the JAWS or Invisible Cursor to obtain information only when no other method works. In this case, always use the Invisible Cursor unless the JAWS Cursor is required to perform the action because the Invisible Cursor cannot. If it is necessary to use the JAWS or Invisible Cursor, store the location of the PC Cursor prior to activating the JAWS or Invisible Cursor, or prior to calling a function that activates either of these cursors. Then take care to re-activate the PC Cursor at the stored location as soon as the desired action is completed with the JAWS or Invisible Cursor.

    As of JAWS 2020, you might instead choose to use the UIA Scan Cursors. See the functions related to these cursors available for JAWS 2020 and later. Generally speaking, the same rules apply to the use of these cursors as to the traditional JAWS and Invisible Cursors. There are several methods to work with these cursors. The addition of the JAWS Scan Cursor and Invisible Scan Cursor was because the standard Off-Screen Model (OSM) used by JAWS and Invisible cursors has been largely replaced by what is called User Interface Automation (UIA) to present screen content. This made the JAWS and Invisible cursors less effective in many applications (e.g., when activating the cursoes in applictions the program would return "blank" instead of informative speech.). The UIA Scan Cursors work with the UIA model to provide better access to screen content in many applications.
  • Never use the SpeechOff() and SpeechOn() functions as a means of suppressing speech output temporarily. If you must suppress speech for specific purposes, use a specific global variable for the specific suppression.

  • Always respect the active cursor when defining behavior in a script or function. Do not break the functionality of the JAWS, Invisible Cursor, or the UIA Scan Cursors.

  • When designing your code, always consider whether the desired JAWS behavior should honor the status of the user buffer or the menu state as special or exception cases.

  • When designing your code, always respect the user's speech output configuration, and always honor speech and sound scheme functionality for controls and for text properties.

  • Keep all global variables up to date. If you overwrite a function that has global variables, be sure to update the necessary variables before returning from that function.

  • Overwrite for the specific circumstance when overwriting the behavior of an existing function or script. Then call the default function or script for all other circumstances. Wherever possible, avoid copying the entire default function or script to your script file and then adding exception cases. Doing so renders your code obsolete if the default code changes. Always use the same name for your function or script as the name of the default function or script.

  • Use event-driven code where appropriate. For instance, in lists or trees where first-letter navigation is possible, make sure that any code for outputting speech during navigation uses appropriate event-driven code such as the SayHighlightedText() function (called by the NewTextEvent() function), the ActiveItemChangedEvent() or ValueChangedEvent() function instead of key-driven code like the SayNextLine() or SayPriorLine() scripts.

  • When designing your code for overwriting the functionality of a keystroke native to the application, evaluate whether the keystroke should be passed through to the application if the special testing conditions in your code are not met. In the JSM file, define the key name to be passed through as a constant with a meaningful name that explains the keystroke's functionality. For example: Do not use ksAltI; rather, use ksIgnoreButton since the hotkey for the button may vary from language to language.

  • If overwriting the functionality of a key combination already defined in the default JKM file, do not make any entry in the application-specific JKM file for the key combination.

  • In JAWS hotkey help, include all new non-application and non-standard Windows key maps for all keys added to the JKM file.

  • Ensure that the ScriptFileName() function outputs relevant information for the current application and script file.

  • Do not output any special messages for scripts attached to native Windows keystrokes.

  • Ensure that the appropriate information (Application window information, real window information, and focus window information) is spoken when focus changes. Eliminate any double-speaking, and ensure that only relevant changes are spoken.

  • Ensure that the SayWindowPromptAndText() function speaks the same information about the focus window that was spoken when the window gained focus. In addition, toggle on training mode temporarily for the SayWindowPromptAndText() function when speaking the focus window so that training information is spoken as appropriate.

  • When designing help messages to be shown in the virtual viewer, place a statement at the top of the virtual viewer telling the user the current location. If showing a list of commands for a key layer, show what key should be pressed to start the layer. If showing a list of commands and their associated keystrokes, make each command in the list a link. Then for each command link, describe the command first, and then show the keystroke. Show any more general information in paragraph form without any links. Always end each virtual help screen with a message telling the user how to close the virtual viewer. This message need not be a link.

  • When scripting for special behavior, script both for speech and for Braille behaviors.

  • On controls where the position in group information is available, make sure that the position in the group is announced when focus moves to the control. Also, ensure that your special conditions cause the SayWindowPromptAndText() and SayLine() functions to announce the position in group. However, also ensure that JAWS does not announce the position in group when moving between the items in the group. JAWS should announce position in group information for controls such as lists, combo boxes, and radio buttons. For treeviews, along with position in group information, JAWS should also announce level and state information. Ensure that JAWS announces and displays in Braille the position, level and open/close state changes of treeview items as they are navigated.

Additional Guidelines for Writing Acceptable Scripting Code

Following are some additional guidelines for writing and formatting code so that it can be most easily maintained. Anyone looking at your code in the future (including yourself) will thank you for following these guidelines when writing your code.

Write self-documenting code by using meaningful script, function, variable and constant names. Self-documenting code is much easier to understand, maintain and debug.

Modularize code so that your scripts or functions do not become large and unwieldy. It is much more time-efficient to maintain and debug modular, smaller scripts and functions than to step through a large section of code line-by-line.

Capitalize script, function, variable and constant names so that JAWS speaks them in a recognizable manner. This is best accomplished either by capitalizing the individual "words" of the name, or by using underscore ("_") to separate the component "words" of the name.

In this document we favor using Pascal Case, which is Capitalizing each word making up a variable name. I find this the easiest to remember and the most readable. However, there are other acceptable formats for naming variables. The following table lists some of the most common formats for naming variables. The table also provides examples of each format.

Naming Scheme Description Example Common Use


Snake Case Snail Case Pothole Case Use underscore "_" to separate words; all lowercase var_name_in_snake_case Python (PEP 8 functions/variables)Rust (functions/variables)Ruby (methods/locals)Elixir (functions/variables)C (functions, varies)SQL schemas Kebab Case Lisp Case dash-case Use hyphens "-" between words; all lowercase var-name-in-kebab-case CSS properties/classesHTML data attributesURLs/filenamesnpm package names Camel Case Dromedary Case Lower camel case: first word lowercase, subsequent words capitalized varNameInCamelCase Java (methods/variables)JavaScript/TypeScript (functions/variables)C#SwiftKotlin,Dart (methods/variables) Pascal Case Upper Camel Case Each word capitalized; no separators VarNameInPascalCase Class/type names across most languages; public members in .NET (C#)modules in Elixir Flat case No separators; all lowercase varnameinflatcase Rare; sometimes short identifiers or legacy contexts Camel Snake Case Combination of camelCase and snake_case var_Name_In_Camel_Snake_Case Not standard; appears in mixed-style codebases or auto-generated code Pascal Snake Case Title Case Pascal-cased words separated by underscores Var_Name_In_Pascal_Snake_Case Niche; sometimes database/codegen conventions Macro Case Constant Case Screaming Snake Case Uppercase words with underscores; typically for constants/macros VAR_NAME_IN_MACRO_CASE C/C++ macrosconstants in PythonRustJava (static final)KotlinRuby environment variables COBOL Case Screaming Kebab Case Kebab case but ALL CAPS VAR-NAME-IN-COBOL-CASE COBOL identifiers and legacy systems Train Case HTTP Header Case Title-cased words separated by hyphens Var-Name-In-Train-Case Documentation headingsdesign systemssome HTTP headers and config keys

: Multiple-word Identifier Formats For Variables

When defining local variables, place "Var" on a line by itself at the left margin, and then place each succeeding local variable on a line by itself indented one tab stop. All varaibles except the final one in the list need to be followed by a comma. This makes the variable list easy to read quickly and to modify if items need to be added or deleted.

Var
    String sFirstVariable, ; first String variable followed by a comma
    Int iSecondVariable,   ; second integer variable followed by a comma
    Object oThirdVariable, ; third Object variable without a comma
    Handle hThirdVariable  ; final Handle variable without a comma

Place all level-0 statements at the left margin, and indent once using tab (not spaces) for each level. Line up the matching components of If-ElIf-Else-EndIf statements and of While-EndWhile statements. When an If or While statement contains compound conditions, place each condition on a line by itself. Line up the operator for each line of the compound test with the If, the ElIf, the Else, and the Endif, or the While-Endwhile statement. Following this convention makes it easy to find code blocks displayed in Braille and by using the JAWS indentation announcement capabilities.

; This is pseudocode, Not an actual JAWS Script
    While IsAppActive(appName)
    And (iteration < maxIterations)
  And (Not UserPressedKey("ESC"))

        SetFocusToForegroundWindow()

    While HasFocusChangedRecently(announceIntervalMs)
      And (GetObjectClass(GetFocus()) = "Edit")
      And (IsTextSelectable(GetFocus()))

      If (IsSelectionNonEmpty(GetFocus()))
        And (IsControlVisible(GetFocus()))
        And (Not IsControlProtected(GetFocus()))
        SayString("Selected text detected.")
                SayHighlightedText(GetFocus())
      ElseIf (IsCaretOnLineStart(GetFocus()))
        And (IsLineNonEmpty(GetFocus()))
        SayString("Line start. Reading line.")
                SayCurrentLine(GetFocus())
      Else
        SayString("Reading word under caret.")
                SayCurrentWord(GetFocus())
            EndIf

      If (IsDialog(GetForegroundWindow()))
        And (HasDefaultButton(GetForegroundWindow()))
        SayString("Dialog detected. Default button is " + GetDefaultButtonName(GetForegroundWindow()))
            EndIf

      If (UserPressedKey("CTRL+H"))
        And (IsHelpAvailable(appName))
        SayString("Opening Script help For " + appName)
        SayString(GetScriptDescription("FocusMonitor"))
        OpenHelpTopic("FocusMonitor")
            EndIf
        EndWhile

    If (UserPressedKey("CTRL+F"))
        And (GetObjectClass(GetFocus()) = "Edit")
        And (IsTextSearchable(GetFocus()))
        SayString("Find requested. Prompting For search term.")
        PromptUserForText("Enter search term:", "SearchTerm")
        If (SearchText(GetFocus(), GetVariable("SearchTerm")))
          SayString("Found: " + GetVariable("SearchTerm"))
                SayCurrentLine(GetFocus())
      Else
        SayString("Not found: " + GetVariable("SearchTerm"))
            EndIf
        EndIf

        iteration = iteration + 1
EndWhile

SayString("Focus monitor stopped.")

Comment your code judiciously. If the code itself is self-documenting1, comments are unnecessary. However, if you are coding in a specific manner or employing a technique whose purpose may not be readily apparent simply by reading the code, then comments are critical. Remember that your code and comments may be read by anyone. So please keep the comments professional and appropriate for all readers.


; FocusMonitor.jss -- Monitors focus changes and announces relevant text
; Author: 
; Purpose: Demonstrate comment guidelines with JAWS Scripting conventions
; Notes: Comments Use semicolons. Only non-obvious techniques are documented.
SayString("Starting focus monitor For " + appName)

While IsAppActive(appName)
    And (iteration < maxIterations)
    And (Not UserPressedKey("ESC"))

        SetFocusToForegroundWindow()

        ; Technique: Throttle announcements using a "recent change" window
        ; Reason: Prevents rapid, repetitive speech when focus flickers
        While HasFocusChangedRecently(announceIntervalMs)
            And (GetObjectClass(GetFocus()) = "Edit")
            And (IsTextSelectable(GetFocus()))

            ; Strategy: Prioritize what To read (selection \textrightarrow line \textrightarrow word)
            If (IsSelectionNonEmpty(GetFocus()))
                And (IsControlVisible(GetFocus()))
                And (Not IsControlProtected(GetFocus()))
                SayString("Selected text.")
                SayHighlightedText(GetFocus())
            ElseIf (IsCaretOnLineStart(GetFocus()))
                And (IsLineNonEmpty(GetFocus()))
                SayString("Line start.")
                SayCurrentLine(GetFocus())
            Else
                SayCurrentWord(GetFocus())
            EndIf

            ; Context hint: If a dialog is present, announce default action
            If (IsDialog(GetForegroundWindow()))
                And (HasDefaultButton(GetForegroundWindow()))
                SayString("Default: " + GetDefaultButtonName(GetForegroundWindow()))
            EndIf
        EndWhile

        ; Non-obvious UX: inline help On CTRL+H To reduce cognitive load
        If (UserPressedKey("CTRL+H"))
          And (IsHelpAvailable(appName))
          SayString(GetScriptDescription("FocusMonitor"))
          OpenHelpTopic("FocusMonitor")
        EndIf

        ; Avoid unbounded loops
        iteration \textleftarrow iteration + 1
EndWhile

SayString("Focus monitor stopped.")

Naming Conventions for Variables and Constants

Use a standard and consistent method for naming variables and constants. The Freedom Scientific scripting code adheres mostly to the following naming conventions for notating types of variables and constants.

Precede handle variable names with "h", string variable names with "s", integer variable names with "i" and object variable names with "o".

Precede global variable names with "g". For example, a global integer variable name is preceded with "gi".

When naming constants, precede window names with "wn", and window classes with "wc".

When defining messages to be spoken, precede the name with "msg"; use "sc" as a prefix to constant strings defined for on-screen comparison.

PrefixDescriptionExample
iLocal variable of type IntiLocalIntVariable
oLocal variable of type ObjectoLocalObjectVariable
hLocal variable of type HandlehLocalHandleVariable
sLocal variable of type StringsLocalStringVariable
giGlobal variable of type IntgiGlobalIntVariable
goGlobal variable of type ObjectgoGlobalObjectVariable
ghGlobal variable of type HandleghGlobalHandleVariable
gsGlobal variable of type StringgsGlobalStringVariable
wnVariable of containing window name constantwnWindowConstant
wcVariable containing window classwcWindowClass
msgmessagemsgMessageX
scconstant stringscStringConstant

This method is called Hungarian Notation. Using this notation makes it easier to identify the type of variable or constant being used in the code.

Definition:

: Hungarian Notation is a naming convention for variables where each name is prefixed with a short code (called a type tag) that indicates the variable's type or intended use. For example:

> strName (string), iCount (integer), oBuffer (object), bIsReady (boolean)

Purpose:

: This convention was introduced by Charles Simonyi at Microsoft during early Windows development. Its goals were:

1.  **Improve readability in weakly typed languages:** Early C and C++ lacked strong type checking and IDE hints. Prefixes helped developers quickly identify data types.

2.  **Reduce type-related bugs:** Seeing iCount vs. strName made accidental misuse less likely.

3.  **Aid maintainability in large codebases:** Prefixes allowed developers to scan code without jumping to declarations.

Variants:

: - Systems Hungarian: Prefix indicates the actual data type (e.g., i, o, str).

- **Apps Hungarian:** Prefix indicates semantic meaning or intended use (e.g., rwFileName for read/write filename, usPrice for U.S. dollars).

Why it is less common today:

: Modern languages (C#, Java, Python) provide strong typing, IDE support, and type inference, making Hungarian Notation largely unnecessary. Most style guides now recommend descriptive names instead (e.g., customerCount instead of iCustomerCount).

Criticism:

: - Can clutter names and reduce readability.

- Brittle when types change (e.g., iCount becomes a float).

- Adds cognitive overhead for new developers.

Modern Alternative:

: Use clear, descriptive names that explain the variable's purpose2.

AspectHungarian NotationModern Naming (Descriptive)Pros / Cons
Primary ideaPrefix encodes type or purpose (e.g., iCount, strName, bIsReady)Descriptive names without type prefixes (e.g., customerCount, displayName, isReady)Hungarian: Quick type hints in weakly typed contexts. Modern: Clear, readable names; relies on language/IDE types.
VariantsSystems Hungarian: actual type (i, p, str); Apps Hungarian: semantic role (rwFileName, usPrice)Usually just descriptive + context (domain terms, units) and optional suffixes/prefixes for semantics (e.g., priceUSD, filePathRW)Hungarian: distinguishes type vs. intent via tags. Modern: embeds semantics in the name itself.
Examples (variables)iTotal, strTitle, pBuffer, bHasFocustotalCount, title, bufferPtr, hasFocusHungarian: fast visual type cues. Modern: easier to read, fewer prefixes to parse.
Examples (functions)fnComputeTotal, bIsActive(), pGetBuffer()computeTotal(), isActive(), getBuffer()Hungarian: consistent tags across APIs. Modern: conventional verb phrases; language enforces types.
Typical usage contextsLegacy C/C++/Win32 codebases, scripts without strong typing or toolingMost contemporary languages and frameworks (Java, C#, Python, Swift, Kotlin, Rust, TS)Hungarian: helpful where tooling is minimal. Modern: standard across modern ecosystems.
Tooling impactRedundant with modern IDEs (tooltips, static analysis, intellisense)Complements IDEs (readable names + quick symbol navigation)Hungarian: can clutter names. Modern: relies on tooling for type info.
MaintainabilityCan be brittle if type changes (e.g., iCount changing to 64-bit or decimal)Names remain stable as types evolve; type is handled in declarations/signaturesHungarian: risks mismatched prefixes. Modern: fewer rename hazards.
Readability for non-expertsPrefixes add cognitive overhead (must learn tag system)Plain-language names are self-documentingHungarian: steeper learning curve. Modern: approachable.
Guideline trendRarely recommended in current style guidesWidely recommended: meaningful, domain-specific namesConsensus has shifted toward descriptive naming without type tags.
::::

Here is an example of a script that uses Hungarian Notation for its variable names, along with proper formatting and comments as described in the previous sections.

; Script: FocusAnnouncer.jss
; Purpose: Demonstrate Hungarian-style prefixes + JAWS scripting conventions with judicious comments
; Notes: Comments use semicolons. Tabs for indentation. Aligned If/ElseIf/Else/EndIf and While/EndWhile.

Var 
    Int iMaxIterations,      ; maximum number of iterations to prevent infinite loops
    Int iIteration,         ; current iteration counter
    Int iAnnounceIntervalMs, ; interval in milliseconds to throttle announcements
    String strTargetApp,    ; target application name to monitor
    Bool bEnabled,          ; flag to enable/disable the announcer
    Object oFocusObj        ; reference to the current focus object

Let iMaxIterations = 100
Let iIteration = 0
Let iAnnounceIntervalMs = 1200
Let strTargetApp = "Notepad"
Let bEnabled = TRUE
Let oFocusObj = NULL

SayString("Focus announcer starting for " + strTargetApp)

While bEnabled
    And (IsAppActive(strTargetApp))
    And (iIteration < iMaxIterations)

        ; Obtain the current focus object (handle-like reference).
        ; Rationale: Centralize acquisition to avoid stale references.
        pFocusObj \textleftarrow GetFocus()

        ; Throttle announcements so rapid focus flicker doesn't overwhelm speech.
        While HasFocusChangedRecently(iAnnounceIntervalMs)
            And (IsTextSelectable(pFocusObj))
            And (GetObjectClass(pFocusObj) = "Edit")

            ; Read priority: selection \textrightarrow line \textrightarrow word.
            If (IsSelectionNonEmpty(pFocusObj))
                And (IsControlVisible(pFocusObj))
                And (Not IsControlProtected(pFocusObj))
                SayString("Selection.")
                SayHighlightedText(pFocusObj)
            ElseIf (IsCaretOnLineStart(pFocusObj))
                And (IsLineNonEmpty(pFocusObj))
                SayString("Line start.")
                SayCurrentLine(pFocusObj)
            Else
                SayCurrentWord(pFocusObj)
            EndIf

            ; Surface contextual default action when a dialog is foreground.
            If (IsDialog(GetForegroundWindow()))
                And (HasDefaultButton(GetForegroundWindow()))
                Initialize strDefaultBtn \textleftarrow GetDefaultButtonName(GetForegroundWindow())
                SayString("Default: " + strDefaultBtn)
            EndIf
        EndWhile

        ; Inline help reduces cognitive load for discoverability.
        If (UserPressedKey("CTRL+H"))
          And (IsHelpAvailable(strTargetApp))
          SayString(GetScriptDescription("FocusAnnouncer"))
          OpenHelpTopic("FocusAnnouncer")
        EndIf

        ; Allow graceful exit.
        If (UserPressedKey("ESC"))
          SayString("Focus announcer stopping.")
            bEnabled \textleftarrow FALSE
        EndIf

        iIteration = iIteration + 1
EndWhile

Be consistent with your convention, and choose a meaningful convention if an appropriate one does not already exist. Using this type of notation makes it easier to know instantly what type of variable is being used without having to look back at the declaration list in JSH or JSM files.

1

Self-documenting code means all variables and functions are names in such a way that they explain what they do

2

Self-documenting code means all variables and functions are named in such a way that they explain what they do.