Documentazione dei plugin

Guida per lo sviluppo di plugin per WinGabriel

1. Panoramica

Un plugin per WinGabriel è una libreria dinamica (.NET Framework 4.8.1) che estende le funzionalità dell’ambiente grafico letto dalla sintesi vocale. Attraverso un plugin puoi aggiungere voci al menu principale, inserire comandi nel menu Altre funzioni, registrare azioni accessibili dalla scrivania, inviare notifiche, mostrare elenchi o moduli di inserimento dati e gestire impostazioni personalizzate.

I plugin non devono creare finestre, form o controlli propri (ad esempio finestre WPF o WinForms). Gli elementi visivi propri del plugin non verrebbero letti dalla sintesi vocale di WinGabriel; l’interazione con l’utente deve avvenire esclusivamente tramite i servizi e le interfacce messi a disposizione dall’ambiente.

2. Creare un plugin, passo per passo

  1. Crea un nuovo progetto di tipo Libreria di classi (.NET Framework) impostando la versione del framework a .NET Framework 4.8.1.
  2. Aggiungi il riferimento alla libreria WGPlugin.dll. Nella finestra delle proprietà del riferimento, imposta Copia localmente (Copy Local) su False (No). La libreria è già presente in WinGabriel e non va distribuita con il plugin.
  3. Crea una classe pubblica con un costruttore pubblico senza parametri che eredita dalla classe astratta WGPluginBase.
  4. Scrivi il codice del plugin realizzando le proprietà di identificazione e il metodo Avvia.
  5. Compila il progetto per generare il file .dll.
  6. Copia il file .dll ottenuto nella cartella dei plugin di WinGabriel.

Di seguito trovi l’esempio completo di un plugin scritto in Visual Basic .NET che aggiunge una voce al menu Altre funzioni, saluta l’utente e conta gli accessi salvando il dato nelle impostazioni:

Imports System
Imports WGPlugin

Public Class PluginEsempio
    Inherits WGPluginBase

    Private _servizi As IWGServiziPlugin

    Public Overrides ReadOnly Property Nome As String
        Get
            Return "Esempio"
        End Get
    End Property

    Public Overrides ReadOnly Property Descrizione As String
        Get
            Return "Saluta e conta quante volte lo si è chiesto."
        End Get
    End Property

    Public Overrides ReadOnly Property Autore As String
        Get
            Return "WinGabriel"
        End Get
    End Property

    Public Overrides Sub Avvia(servizi As IWGServiziPlugin)
        Try
            _servizi = servizi
            servizi.AggiungiVoceAltreFunzioni("Saluto", AddressOf Saluta)
        Catch ex As Exception
            servizi.RegistraErrore("PluginEsempio.Avvia", ex)
        End Try
    End Sub

    Private Sub Saluta()
        Try
            Dim volte As Integer = 0
            Integer.TryParse(_servizi.Leggi("volte"), volte)
            volte += 1
            _servizi.Scrivi("volte", volte.ToString())
            _servizi.Messaggio("Ciao, saluto numero " & volte.ToString())
        Catch ex As Exception
            _servizi.RegistraErrore("PluginEsempio.Saluta", ex)
        End Try
    End Sub
End Class

Spiegazione riga per riga del plugin di esempio:

  • Inherits WGPluginBase: dichiara che la classe è un plugin riconosciuto da WinGabriel.
  • Nome, Descrizione, Autore: forniscono le informazioni lette da WinGabriel nelle impostazioni.
  • Avvia(servizi): riceve l’istanza di IWGServiziPlugin con cui il plugin interagisce con l’ambiente. In questo metodo viene registrata la voce di menu.
  • servizi.AggiungiVoceAltreFunzioni("Saluto", AddressOf Saluta): inserisce nel menu Altre funzioni la voce “Saluto” collegandola alla routine Saluta.
  • _servizi.Leggi("volte") e _servizi.Scrivi(...): leggono e aggiornano il valore salvato nella configurazione di WinGabriel.
  • _servizi.Messaggio(...): mostra la risposta dell’utente gestita direttamente dalla sintesi vocale.

Ecco la dichiarazione equivalente della sola classe del plugin espressa in C#:

using System;
using WGPlugin;

public class PluginEsempio : WGPluginBase
{
    private IWGServiziPlugin _servizi;

    public override string Nome => "Esempio";
    public override string Descrizione => "Saluta e conta quante volte lo si è chiesto.";
    public override string Autore => "WinGabriel";

    public override void Avvia(IWGServiziPlugin servizi)
    {
        try
        {
            _servizi = servizi;
            servizi.AggiungiVoceAltreFunzioni("Saluto", Saluta);
        }
        catch (Exception ex)
        {
            servizi.RegistraErrore("PluginEsempio.Avvia", ex);
        }
    }

    private void Saluta()
    {
        try
        {
            int volte = 0;
            int.TryParse(_servizi.Leggi("volte"), out volte);
            volte++;
            _servizi.Scrivi("volte", volte.ToString());
            _servizi.Messaggio("Ciao, saluto numero " + volte.ToString());
        }
        catch (Exception ex)
        {
            _servizi.RegistraErrore("PluginEsempio.Saluta", ex);
        }
    }
}

3. Dove va messo il plugin

I file .dll dei plugin vanno posizionati direttamente all’interno della cartella plugin dell’installazione. Non inserire le librerie dei plugin in sottocartelle. Se il plugin richiede librerie di terze parti (dipendenze extra), queste vanno posizionate nella sottocartella plugin\librerie.

La posizione del percorso dipende dalla modalità di installazione di WinGabriel:

  • Installazione standard in Programmi: C:\Program Files\WinGabriel\plugin (oppure Programmi\WinGabriel\plugin).
  • Installazione senza permessi di scrittura in Programmi: %LocalAppData%\WinGabriel\plugin.
  • Versione portatile: la cartella plugin posizionata accanto all’eseguibile principale di WinGabriel.

4. Il ciclo di vita

All’avvio, WinGabriel legge la cartella plugin, carica i file consentiti, istanzia ogni classe pubblica non astratta che deriva da WGPluginBase e chiama il metodo Avvia una sola volta. Durante l’esecuzione di Avvia, il plugin registra le sue azioni e le sue voci di menu. Alla chiusura del programma, WinGabriel chiama il metodo Arresta per ogni plugin attivo.

Se un plugin viene attivato a programma aperto (dalle Impostazioni o confermando l’avviso di sicurezza), il caricatore chiama immediatamente Avvia e le sue voci di menu compaiono senza riavviare WinGabriel. Quando un plugin viene disattivato o disinstallato, viene chiamato Arresta, le sue voci vengono rimosse dai menu e tutti i suoi servizi smettono di rispondere subito (le azioni precedentemente registrate non eseguono più alcuna operazione). Se un plugin viene riattivato, viene creata una nuova istanza della classe e viene richiamato Avvia.

Se il metodo Avvia genera un’eccezione, il plugin viene bloccato, l’utente riceve un avviso e l’esecuzione di WinGabriel prosegue regolarmente. Le azioni del plugin (voci di menu, notifiche, comandi) sono protette: un’eccezione non rilevata durante la loro esecuzione viene intercettata, scritta nel registro e notificata all’utente.

5. I servizi (IWGServiziPlugin)

Ogni plugin riceve un’istanza dedicata dell’interfaccia IWGServiziPlugin all’interno del metodo Avvia. I membri principali forniti da questo servizio sono:

  • Id: stringa contenente il nome del file della libreria senza estensione (es. PluginEsempio).
  • VersioneApi: la versione dell’interfaccia in uso (sempre "1.0").
  • CartellaDati: il percorso completo di una cartella riservata al plugin in cui salvare file proprietari. La cartella viene creata automaticamente.
  • Firmato: valore booleano che indica se il plugin è provvisto di firma digitale valida.
  • Parla(testo, interrompi): pronuncia una frase tramite la sintesi vocale.
  • Messaggio(testo): mostra una finestra di dialogo di WinGabriel che viene letta automaticamente dalla voce e attende la conferma dell’utente.
  • ChiediAsync(domanda): propone una domanda con risposte Sì/No (restituisce True se l’utente risponde Sì; l’uscita con Esc equivale a No).
  • ChiediTesto(titolo, predefinito, alTermine): mostra un campo di testo a riga singola.
  • ChiediTestoLungo(titolo, predefinito, alTermine): mostra un campo di testo multiriga (Invio va a capo, la conferma avviene uscendo con Esc e rispondendo Sì alla domanda di conferma).
  • EseguiConAttesaAsync(testoAttesa, lavoro): esegue una funzione asincrona mostrando e pronunciando un messaggio di attesa. Deve essere richiamato esclusivamente dal thread dell’interfaccia grafica.
  • AggiungiVoceMenuPrincipale(testo, azione): aggiunge una voce al menu principale dopo la voce “Altre funzioni”. Le voci inserite compaiono e spariscono immediatamente senza riaprire il menu.
  • AggiungiVoceAltreFunzioni(testo, azione): inserisce una voce all’interno del menu “Altre funzioni”. Il menu legge queste voci ogni volta che viene aperto.
  • RegistraFunzioneScrivania(nome, azione): rende disponibile un comando rapido sulla scrivania.
  • Notifica(testo, azione): pubblica un messaggio nel centro notifiche. Se viene passata un’azione, questa viene eseguita selezionando la notifica.
  • Leggi(chiave) / Scrivi(chiave, valore): leggono e scrivono impostazioni nella configurazione di WinGabriel. Le chiavi vengono salvate automaticamente con il prefisso plugin_<Id>_.
  • RegistraErrore(origine, ex): scrive un’eccezione nel registro degli errori di WinGabriel.

I metodi Messaggio, ChiediAsync, ChiediTesto, ChiediTestoLungo e i metodi Mostra dei vari controlli eseguono automaticamente il passaggio al thread dell’interfaccia e possono essere chiamati da qualsiasi thread. Al contrario, EseguiConAttesaAsync richiede di essere invocato dal thread dell’interfaccia.

Controlli di WinGabriel per i plugin

I controlli si istanziano tramite i metodi della classe IWGServiziPlugin senza che compaia nulla a schermo. Tutte le configurazioni effettuate prima del metodo Mostra restano in memoria; il controllo visivo reale viene creato soltanto quando si chiama Mostra, sul thread dell’interfaccia. Dopo la chiamata a Mostra, eventuali modifiche (come l’aggiunta o rimozione di voci) vengono applicate direttamente al controllo visivo aperto. Tutte le azioni registrate sui controlli sono protette e l’eventuale eccezione viene scritta nel registro.

Menu (IWGMenuPlugin)

Creato con servizi.NuovoMenu(titolo). Rappresenta il menu standard di WinGabriel. Permette di aggiungere voci tradizionali o opzioni accessibili premendo il tasto Alt.

Dim menu As IWGMenuPlugin = _servizi.NuovoMenu("Gestione")
menu.Aggiungi("Esegui azione", AddressOf MiaAzione)
menu.AggiungiDopo("Esegui azione", "Azione successiva", AddressOf SecondaAzione)
menu.AggiungiOpzione("Azzera dati", AddressOf ResetAzione)
menu.RimuoviVoce("Azione successiva")
menu.RinominaVoce("Esegui azione", "Nuova voce")
menu.Mostra()

Elenco con identificatori (IWGElencoPlugin)

Creato con servizi.NuovoElenco(titolo). Gestisce elenchi a selezione singola o multipla collegati a identificatori univoci. Può essere aperto anche tramite i metodi rapidi MostraElencoConId e MostraElencoMultiplo usando oggetti WGVoceElenco.

Dim elenco As IWGElencoPlugin = _servizi.NuovoElenco("Selezione Elementi")
elenco.Multiplo = False
elenco.Aggiungi("id1", "Primo Elemento")
elenco.Aggiungi("id2", "Secondo Elemento")
elenco.AlScelto = Sub(id As String)
                    _servizi.Messaggio("Scelto identificatore: " & id)
                 End Sub
elenco.Mostra()

Campo di testo grande (IWGCampoPlugin)

Creato con servizi.NuovoCampo(titolo, testo, soloLettura). Utilizzato per visualizzare testi lunghi o per consentirne la modifica. È possibile usare anche le scorciatoie MostraTesto, MostraHtml o ChiediTestoLungo. Il testo modificato si legge tramite la proprietà Testo all’interno del delegato AllaChiusura.

Dim campo As IWGCampoPlugin = _servizi.NuovoCampo("Note", "Testo iniziale", False)
campo.AllaChiusura = Sub()
                         Dim finale As String = campo.Testo
                         _servizi.Messaggio("Testo salvato: " & finale)
                     End Sub
campo.Mostra()

Modulo a più campi (IWGCampiPlugin)

Creato con servizi.NuovoModulo(). Permette di richiedere più informazioni in una sola schermata (campi testo, numero, testo lungo, password, data, orario e campi multipli). La conferma avviene al completamento dell’ultimo campo o tramite il delegato AlConferma.

Dim modulo As IWGCampiPlugin = _servizi.NuovoModulo()
modulo.AggiungiTesto("Nome utente", "utente", True, False, "")
modulo.AggiungiPassword("Chiave di accesso", "pass", True, False, "")
modulo.AlConferma = Sub()
                        If modulo.ObbligatoriCompilati() Then
                            Dim u As String = modulo.Valore("utente")
                            modulo.Chiudi()
                            _servizi.Messaggio("Dati confermati per " & u)
                        End If
                    End Sub
modulo.Mostra()

Griglia (IWGGrigliaPlugin)

Creata con servizi.NuovaGriglia(titolo, elencoVoci). Dispone gli elementi su righe e colonne. AlScelto restituisce l’indice numerico (a partire da zero) dell’elemento selezionato.

Dim elementi As New List(Of String) From {"Icona 1", "Icona 2"}
Dim griglia As IWGGrigliaPlugin = _servizi.NuovaGriglia("Scrivania", elementi)
griglia.AlScelto = Sub(indice As Integer)
                       _servizi.Messaggio("Selezionato elemento numero: " & indice.ToString())
                   End Sub
griglia.Mostra()

Aggiungere voci a qualsiasi menu di WinGabriel

Ogni menu a voci di WinGabriel (rappresentato dalla classe WGMenu) possiede un titolo identificativo. Un istante prima che un menu si apra, WinGabriel lo rende disponibile ai plugin: ogni menu costituisce quindi fin da subito un punto d’innesto, riconosciuto tramite il suo titolo. Questo meccanismo garantisce la compatibilità anche con i menu che verranno aggiunti a WinGabriel in futuro, senza la necessità di aggiornare elenchi predefiniti o modificare il codice del plugin. Il confronto tra i titoli dei menu non distingue tra maiuscole e minuscole.

Tra i titoli dei menu vi sono, a titolo di esempio: “Multimedia”, “Musica”, “Menù Organizer”, “Altre funzioni”, “Menu Impostazioni” e “Aggiornamento”. Il menu iniziale del programma è identificato dal titolo “Menu principale” (sono considerati equivalenti anche i titoli “Menu applicazioni” e “Menù iniziale”).

Per conoscere i titoli dei menu che si sono aperti fino a quel momento è possibile invocare il metodo MenuAperti(). Un titolo relativo a un menu non ancora aperto può essere comunque utilizzato in fase di registrazione: la voce corrispondente comparirà automaticamente quando quel determinato menu verrà aperto dall’utente.

Durante l’esecuzione del metodo Avvia, il plugin può fare uso di tre distinti servizi di registrazione:

  • AggiungiVoceAMenu(titoloMenu, testo, azione, dopoLaVoce opzionale): aggiunge una voce al menu specificato a ogni sua apertura. Se viene valorizzato il parametro facoltativo dopoLaVoce, la nuova voce verrà posizionata subito dopo quella indicata (qualora presente nel menu), altrimenti verrà inserita in fondo. Per il “Menu principale”, la voce compare immediatamente anche se WinGabriel è già aperto.
  • AggiungiOpzioneAMenu(titoloMenu, testo, azione): funziona come il servizio precedente, ma aggiunge un’opzione che l’utente può attivare premendo il tasto Alt.
  • AlAperturaDiMenu(titoloMenu, azione As Action(Of IWGMenuPlugin)): esegue la procedura specificata ogni volta che il menu sta per aprirsi, passando il menu stesso come parametro di tipo IWGMenuPlugin. All’interno di questa procedura, il plugin può chiamare i metodi Aggiungi, AggiungiDopo, AggiungiOpzione, RimuoviVoce, RinominaVoce e Posiziona. Questo servizio è utile per mostrare una voce solo al verificarsi di una condizione, oppure per rimuovere o rinominare voci presenti. Non deve mai essere chiamato il metodo Mostra, poiché l’apertura del menu è gestita direttamente da WinGabriel.

L’esecuzione delle azioni associate alle voci è protetta: nel caso in cui il plugin generi un errore non gestito, questo viene registrato nel log dell’applicazione e all’utente viene mostrato un messaggio informativo, evitando l’interruzione o il blocco di WinGabriel.

Quando un plugin viene disattivato o disinstallato, le sue voci non compaiono più nei menu che si aprono successivamente. Se la disattivazione avviene a menu già aperto, la voce rimane visibile fino alla sua chiusura, ma non eseguirà alcuna operazione se selezionata.

Mentre per il “Menu principale” le voci possono essere inserite o rimosse anche mentre il menu è aperto, per tutti gli altri menu le modifiche hanno effetto a partire dall’apertura successiva.

Limitazioni e scorciatoie: i servizi descritti riguardano esclusivamente i menu basati su voci (WGMenu). Gli elenchi gestiti tramite identificatori (WGMenuLista) e le altre finestre del programma non costituiscono punti d’innesto. Per inserire rapidamente una voce nel menu “Altre funzioni”, resta comunque valido l’uso della scorciatoia AggiungiVoceAltreFunzioni.

Esempio completo in Visual Basic:

Imports WGPlugin

Public Class PluginMenu
    Inherits WGPluginBase

    Public Overrides ReadOnly Property Nome As String
        Get
            Return "Esempio di menu"
        End Get
    End Property

    Public Overrides Sub Avvia(servizi As IWGServiziPlugin)
        ' Una voce dopo "Agenda" nel menu Organizer.
        servizi.AggiungiVoceAMenu("Menù Organizer", "Promemoria a intervalli", AddressOf ApriPromemoria, "Agenda")

        ' Una voce nel menu Multimedia solo se l'utente ha già concesso il permesso per le note: si decide a ogni apertura del menu.
        servizi.AlAperturaDiMenu("Multimedia", Sub(menu As IWGMenuPlugin)
                                                   If servizi.AccessoConcesso(WGPermessi.Note) Then
                                                       menu.Aggiungi("Le mie note", AddressOf ApriMusica)
                                                   End If
                                               End Sub)
    End Sub

    Private Sub ApriPromemoria()
        ' ...
    End Sub

    Private Sub ApriMusica()
        ' ...
    End Sub
End Class

6. Regole di interfaccia

  • Nessun controllo proprietario: non aprire mai form o finestre personalizzate. Usa esclusivamente i controlli forniti da IWGServiziPlugin per garantire la completa lettura da parte della sintesi vocale.
  • Feedback costante: ogni informazione, esito o avviso all’utente deve passare da Messaggio (es. “Avviato”, “Timer annullato”).
  • Uso limitato di Parla: il metodo Parla pronuncia il testo immediatamente ma non lascia traccia da rileggere. Usalo solo per messaggi estemporanei e preferisci Messaggio per tutte le conferme.
  • Gestione delle attese: le operazioni lunghe devono essere avvolte in EseguiConAttesaAsync, in modo che WinGabriel mostri e annunci lo stato di caricamento.
  • Segnalazione errori: comunica un errore con Messaggio senza chiedere conferme Sì/No.
  • Elenchi vuoti: un elenco privo di elementi non deve essere aperto. Mostra prima un Messaggio che avvisa l’utente che non ci sono dati.
  • Titoli essenziali: i titoli degli elenchi e dei menu devono contenere solo il nome del contenuto, senza istruzioni sui tasti da premere.
  • Stile dei testi: scrivi messaggi brevi, chiari e in italiano naturale, formulati come verrebbero detti a voce.

7. Sicurezza, controlli e permessi

La sicurezza è una priorità per WinGabriel. Per proteggere i tuoi dati e garantire un funzionamento stabile, i plugin sono soggetti a controlli rigorosi e operano in un ambiente con permessi limitati. Questa sezione ti illustra come funziona il sistema di sicurezza e quali sono le regole che devi rispettare nello sviluppo dei tuoi plugin.

Firma del plugin

Ogni plugin, identificato dal file nome.dll, può essere accompagnato da un file nome.dll.firma. Questo file contiene la firma RSA SHA-256 del tuo plugin, codificata in Base64, generata con la chiave degli sviluppatori di WinGabriel (puoi usare lo script firma-plugin.ps1 presente nella cartella scripts).

  • Un plugin correttamente firmato viene caricato senza richiedere alcuna conferma all’utente.
  • Se un plugin non è firmato, WinGabriel mostrerà un avviso all’utente. L’avviso specifica che il plugin non è stato creato dagli sviluppatori di WinGabriel, che il suo codice viene controllato e non può accedere ai dati dell’utente senza un permesso esplicito, ma può utilizzare Internet e mostrare messaggi. Il plugin verrà caricato solo se l’utente acconsente.
  • La decisione dell’utente è legata al contenuto specifico del file. Se il file .dll del plugin o una delle librerie nella cartella plugin\librerie cambia, anche di un solo byte, WinGabriel richiederà nuovamente la conferma all’utente, con una domanda specifica che indica la modifica del file.
  • Se un plugin era precedentemente firmato e ora non lo è più, la domanda di conferma sarà più seria, evidenziando il cambio di stato della firma.
  • Un plugin firmato che viene aggiornato ma mantiene una firma valida non richiederà ulteriori conferme.
  • È fondamentale che anche le librerie di cui il tuo plugin dipende, situate nella cartella plugin\librerie, abbiano una firma valida se il plugin principale è firmato.

Controllo del codice prima del caricamento

Prima di caricare qualsiasi plugin, WinGabriel esegue un’analisi dei metadati del file .dll (senza eseguirlo) per verificare che utilizzi solo un elenco ristretto e consentito di tipi del framework .NET. Questo controllo si applica a tutti i plugin e alle loro librerie, inclusi quelli firmati.

  • Se il codice del plugin tenta di utilizzare qualcosa di non consentito, il plugin non verrà caricato. Nelle impostazioni di WinGabriel, il suo stato apparirà come “codice non consentito” e i dettagli dell’infrazione verranno registrati nel registro di WinGabriel.
  • Lo scopo principale di questo controllo è impedire che un plugin possa leggere o scrivere file di WinGabriel o altri file del sistema, sia tramite le funzioni di .NET sia tramite quelle di Windows, poiché tali funzioni non sono esposte all’ambiente del plugin.
Tipi e funzionalità vietate (il plugin non si carica):
  • Accesso a file e cartelle: System.IO.File, System.IO.Directory, System.IO.FileStream, System.IO.StreamReader (su un file), System.IO.FileInfo e tipi simili; Microsoft.VisualBasic.FileIO, My.Computer.FileSystem, FileOpen.
  • Rete: System.Net (tutti i tipi tranne WebUtility), System.Net.Http.HttpClient, System.Net.WebClient, socket.
  • Registro di Windows: Microsoft.Win32.
  • Processi: System.Diagnostics.Process.
  • Riflessione e caricamento dinamico: System.Reflection, Type.GetType, Activator, System.Reflection.Assembly.Load, late binding di Visual Basic (con Option Strict Off).
  • Codice nativo: P/Invoke con DllImport, System.Runtime.InteropServices, System.Runtime.InteropServices.Marshal.
  • Puntatori e codice non verificabile: Uso di puntatori, codice unsafe.
  • Interfacce utente grafiche: System.Windows.Forms, System.Drawing, WPF (Windows Presentation Foundation).
  • Dati strutturati complessi: XML, database, serializzazione binaria.
  • Terminazione applicazione: System.Environment.Exit.
  • Uso di altri assembly di WinGabriel: Non puoi fare riferimento direttamente ad altri assembly interni di WinGabriel.
Tipi e funzionalità consentite:
  • Tipi base di System: stringhe, numeri, date, System.Math, System.Random, System.Guid, System.Uri, System.Convert, System.Enum, System.Tuple, System.Func, System.Action e simili.
  • Collezioni: System.Collections e System.Collections.Generic.
  • Query: System.Linq.
  • Manipolazione testo: System.Text (StringBuilder, Encoding), System.Text.RegularExpressions.
  • Cultura: System.Globalization.
  • Threading e asincronia: System.Threading e System.Threading.Tasks (Task, Timer, Thread, async/await).
  • Misurazione tempo: System.Diagnostics.Stopwatch.
  • Flussi in memoria: System.IO soltanto per i flussi in memoria (MemoryStream, StringReader, StringWriter, BinaryReader, BinaryWriter). System.IO.Path è consentito per manipolare nomi di percorso senza accedere al file system.
  • Crittografia (selettiva): Alcune classi di System.Security.Cryptography (SHA256, SHA1, MD5, HMAC, generatore di numeri casuali, Rfc2898DeriveBytes).
  • Suoni di sistema: System.Media.SystemSounds.
  • Utilità web: System.Net.WebUtility.
  • Funzioni di base del runtime di Visual Basic: Strings, Conversion, DateAndTime, IIf.
Regole pratiche per lo sviluppo:
  • Nei progetti Visual Basic non serve cambiare nessuna impostazione del progetto: il codice che Visual Basic genera da solo (il modello My) viene accettato. Restano però vietate le sue parti che toccano file, rete e sistema, come My.Computer.FileSystem e My.Computer.Network: usale tramite i servizi di WinGabriel.
  • Utilizza sempre Option Strict On per evitare il late binding e migliorare la robustezza del codice.
  • Le librerie di terze parti che includi nel tuo progetto sono soggette allo stesso controllo. Librerie che utilizzano funzionalità vietate come l’accesso a file, la rete o la riflessione (ad esempio, Newtonsoft.Json) non potranno essere utilizzate.

Funzioni sicure offerte da WinGabriel

Per permettere ai plugin di svolgere compiti comuni in modo sicuro, WinGabriel offre un set di funzioni specifiche tramite l’interfaccia IWGServiziPlugin, che sostituiscono le funzionalità vietate del framework.

Gestione dei file propri del plugin:

I file e le impostazioni di ogni plugin (quelle di Leggi e Scrivi) stanno nella cartella dei dati di WinGabriel, nella sottocartella plugin. Per questo si sincronizzano nel cloud e si esportano insieme agli altri dati: nelle impostazioni del cloud la cartella plugin si può includere o escludere come le altre.

I plugin possono gestire i propri file di dati in una cartella dedicata e isolata. I nomi dei file devono essere semplici (lettere, numeri, punto, trattino, fino a 64 caratteri) e non possono contenere percorsi. I file sono limitati a 1 MB ciascuno, con un massimo di 5 MB totali per plugin.

  • LeggiFileDati(nome As String) As Byte(): Legge il contenuto di un file. Restituisce Nothing se il file non esiste.
  • ScriviFileDati(nome As String, dati As Byte()): Scrive o sovrascrive un file.
  • ElencaFileDati() As IList(Of String): Restituisce un elenco dei nomi dei file presenti nella cartella del plugin.
  • EliminaFileDati(nome As String): Elimina un file.

Per piccole impostazioni persistenti, puoi continuare a usare i metodi Leggi(chiave As String) As String e Scrivi(chiave As String, valore As String), che supportano chiavi fino a 64 caratteri e valori fino a 16 KB.

Accesso a Internet:

Il plugin può effettuare richieste HTTP sicure verso Internet.

  • ScaricaTestoAsync(url As String) As Task(Of String): Scarica il contenuto testuale di una risorsa web.

Questa funzione ha le seguenti restrizioni:

  • Accetta solo indirizzi https.
  • Può connettersi solo a siti pubblici; indirizzi locali o privati (come localhost, 192.168.x.x) sono rifiutati, anche in caso di reindirizzamento.
  • Utilizza solo la porta standard (443 per HTTPS).
  • La risposta è limitata a 2 MB.
  • Il tempo massimo di attesa per una risposta è di 15 secondi.
  • È consentito un massimo di 10 richieste al minuto.
  • La funzione restituisce Nothing se la richiesta non è consentita o non riesce.
Accesso ai dati di WinGabriel (con permesso dell’utente):

Per accedere a dati sensibili di WinGabriel come note, rubrica o agenda, il tuo plugin deve esplicitamente chiedere il permesso all’utente. I permessi sono rappresentati dalle costanti di WGPermessi: Note, Rubrica, Agenda.

  • RichiediAccessoAsync(permesso As String, motivo As String) As Task(Of Boolean): Richiede il permesso specificato all’utente. WinGabriel mostrerà un avviso con il motivo (un testo descrittivo fornito dal plugin, fino a 120 caratteri). Restituisce True se il permesso è stato concesso, False altrimenti. Se il permesso è già stato concesso, la funzione restituisce True senza chiedere nuovamente. Se l’utente rifiuta, la richiesta non verrà riproposta per la durata della sessione corrente.
  • AccessoConcesso(permesso As String) As Boolean: Verifica se un permesso è già stato concesso, senza mostrare alcuna richiesta all’utente.

Una volta ottenuto il permesso, puoi utilizzare le seguenti funzioni di sola lettura:

  • ElencaNote() As IList(Of WGVoceElenco): Restituisce l’identificatore e il titolo delle note dell’utente, fino a 200, dalla più recente. Non include le note protette da password.
  • LeggiNota(id As String) As String: Restituisce il testo di una nota, dato il suo identificatore. Non legge le note protette da password.
  • CercaContatti(testo As String) As IList(Of WGContattoPlugin): Cerca contatti nella rubrica. Non accede alle rubriche protette da password. Restituisce un elenco di oggetti WGContattoPlugin (con proprietà Nome, Telefono, Email), fino a un massimo di 100 risultati.
  • LeggiAppuntamenti(da As DateTime, a As DateTime) As IList(Of WGVoceElenco): Restituisce gli appuntamenti in un intervallo di date (nel testo la data e l’oggetto), fino a un massimo di 200.

Se il permesso necessario non è stato concesso, queste funzioni restituiranno un elenco vuoto o Nothing, senza generare errori.

Scrittura e dati protetti da password

Oltre ai permessi di lettura (come WGPermessi.Note, WGPermessi.Rubrica e WGPermessi.Agenda), WinGabriel offre permessi specifici per la scrittura e per l’accesso a dati protetti da password.

Puoi richiedere questi permessi utilizzando la funzione RichiediAccessoAsync(permesso As String, motivo As String) As Task(Of Boolean), esattamente come faresti per gli altri. L’utente ha sempre il controllo e può decidere di concedere o revocare i permessi in qualsiasi momento dalle impostazioni del plugin (sezione “Plugin”, selezionando il tuo plugin e poi “Permessi”). Senza il permesso appropriato, le funzioni non eseguiranno alcuna operazione e restituiranno False, Nothing o un elenco vuoto, a seconda del loro tipo di ritorno.

Permessi di scrittura

I permessi di scrittura consentono al tuo plugin di aggiungere nuovi dati. È importante notare che un plugin non può modificare né cancellare i dati esistenti, ma solo aggiungerne di nuovi.

I permessi disponibili sono:

  • WGPermessi.NoteScrittura
  • WGPermessi.RubricaScrittura
  • WGPermessi.AgendaScrittura

Le funzioni che puoi usare per la scrittura sono:

  • AggiungiNota(titolo As String, testo As String) As Boolean: Per aggiungere una nota non protetta.
  • AggiungiContatto(contatto As WGContattoPlugin) As Boolean: Per aggiungere un contatto nella rubrica predefinita. Il campo Nome del contatto può contenere sia il nome che il cognome. Questa funzione restituisce False se la rubrica predefinita è protetta da password.
  • AggiungiAppuntamento(oggetto As String, data As DateTime, oraInizio As String, oraFine As String, note As String) As Boolean: Per aggiungere un appuntamento nell’agenda predefinita. Le ore devono essere nel formato “HH:mm” oppure possono essere vuote.

Tieni presente che è consentito un massimo di 10 operazioni di scrittura al minuto.

Permessi per i dati protetti da password

Questi permessi ti consentono di accedere a note, rubriche e agende che l’utente ha protetto con una password.

I permessi specifici sono:

  • WGPermessi.NoteProtette
  • WGPermessi.RubricaProtette
  • WGPermessi.AgendaProtette

Anche con il permesso, il tuo plugin non riceverà mai le password. A ogni chiamata di una funzione che richiede l’accesso a dati protetti, WinGabriel aprirà un campo dedicato dove l’utente potrà inserire la password. Il titolo di questo campo includerà il nome del tuo plugin. Il tuo plugin riceverà solo i dati, mai la password stessa. Se l’utente annulla l’operazione o inserisce una password errata, non verrà letto alcun dato. Inoltre, un’agenda o una rubrica che era chiusa tornerà chiusa subito dopo la lettura.

Le funzioni disponibili per i dati protetti sono:

  • ElencaNoteProtette() As IList(Of WGVoceElenco): Restituisce solo i titoli delle note protette, senza le password.
  • LeggiNotaProtettaAsync(id As String) As Task(Of String)
  • CercaContattiProtettiAsync(testo As String) As Task(Of IList(Of WGContattoPlugin))
  • LeggiAppuntamentiProtettiAsync(da As DateTime, a As DateTime) As Task(Of IList(Of WGVoceElenco))

La password inserita dall’utente identifica la specifica rubrica o agenda da aprire. È consentito un massimo di 3 richieste di password al minuto.

È importante sapere che le funzioni di lettura normali (come ElencaNote, LeggiNota, CercaContatti, LeggiAppuntamenti) non leggeranno mai note, rubriche o agende protette da password, nemmeno se l’utente le avesse aperte manualmente in quella sessione di WinGabriel.

Revoca dei permessi:

L’utente ha il pieno controllo sui permessi concessi ai plugin. Può revocare un permesso in qualsiasi momento dalle impostazioni di WinGabriel, navigando su “Plugin”, selezionando il plugin desiderato e poi “Permessi”. La revoca ha effetto immediato. Quando un plugin viene disinstallato, tutti i permessi ad esso associati vengono automaticamente cancellati.

Limiti e interruttore automatico

Per garantire la stabilità e la reattività di WinGabriel, ogni plugin è soggetto a limiti di utilizzo per diverse operazioni, calcolati su base minuto. Le richieste che superano questi limiti vengono semplicemente scartate.

  • Messaggi mostrati: massimo 6 al minuto.
  • Notifiche inviate: massimo 6 al minuto.
  • Letture vocali con Parla: massimo 12 al minuto.
  • Controlli aperti (es. elenchi, input): massimo 20 al minuto.
  • Scritture di file propri del plugin: massimo 60 al minuto.
  • Salvataggi di impostazioni (con Scrivi): massimo 120 al minuto.
  • Richieste a Internet (con ScaricaTestoAsync): massimo 10 al minuto.
  • Letture di dati di WinGabriel (note, rubrica, agenda): massimo 30 al minuto.
  • Richieste di permesso: massimo 3 al minuto.

Il metodo Avvia del tuo plugin deve completare la sua esecuzione entro 5 secondi.

WinGabriel disattiverà automaticamente un plugin in queste circostanze:

  • Se il plugin genera 3 errori in un minuto (errori interni del plugin, errori registrati con RegistraErrore, o azioni che durano più di 10 secondi).
  • Se il plugin supera persistentemente i limiti, con 10 richieste scartate in un intervallo di due minuti.
  • Se il metodo Avvia impiega troppo tempo per completarsi.

Quando un plugin viene disattivato, WinGabriel mostrerà un messaggio all’utente, che potrà riattivarlo manualmente dalle impostazioni. Un plugin può aggiungere al massimo 5 voci al menu principale di WinGabriel e un totale di 40 voci o opzioni a tutti gli altri menu.

Le impostazioni salvate dal tuo plugin tramite il metodo Scrivi sono isolate e non interferiscono con le impostazioni di WinGabriel o quelle di altri plugin.

Elenco dei plugin bloccati

WinGabriel scarica automaticamente un elenco di plugin bloccati dagli sviluppatori di WinGabriel a ogni avvio e ogni 12 ore. Questo elenco è firmato con la stessa chiave utilizzata per i plugin e serve come ulteriore misura di sicurezza. Se un plugin è presente in questo elenco (identificato tramite l’impronta del file o il nome), non verrà caricato. Se è già attivo, verrà immediatamente disattivato.

Questo meccanismo offre uno strumento aggiuntivo per gli sviluppatori di WinGabriel, che possono utilizzare lo script revoca-plugin.ps1 per aggiungere plugin all’elenco. Un elenco bloccato con una firma non valida o più vecchio di quello già in uso viene scartato. Un plugin bloccato apparirà nelle impostazioni di WinGabriel con lo stato “bloccato”.

Avvio sicuro

Durante la fase di caricamento dei plugin, WinGabriel tiene traccia di quale plugin sta caricando in quel momento. Se WinGabriel si chiude in modo anomalo durante il caricamento di un plugin (o nei 20 secondi successivi al caricamento) per due avvii consecutivi, il plugin responsabile viene automaticamente disattivato. Se non è possibile identificare il plugin specifico, nessun plugin verrà caricato per quell’avvio. L’utente viene sempre avvisato di queste azioni. È inoltre disponibile l’opzione di avvio /senzaplugin per avviare WinGabriel senza alcun plugin.

Limite noto

È importante notare che il controllo di sicurezza implementato si basa sull’analisi dei tipi e delle funzionalità consentite e non è un isolamento completo in un processo separato. Teoricamente, un file .dll scritto manualmente in linguaggio intermedio (IL) che utilizzi aritmetica sui puntatori nativi potrebbe, in linea di principio, leggere la memoria del processo di WinGabriel (sebbene non possa aprire file o accedere ad altre risorse esterne). Per questa ragione, il consiglio fondamentale per gli utenti rimane quello di attivare solo plugin di cui si fidano pienamente.

Esempio di plugin

Ecco un esempio completo di un plugin in Visual Basic che illustra l’utilizzo delle funzioni sicure di WinGabriel per accedere alle note dell’utente.

Imports WGPlugin

Public Class PluginNoteDiOggi
    Inherits WGPluginBase

    Private _servizi As IWGServiziPlugin

    Public Overrides ReadOnly Property Nome As String
        Get
            Return "Note di oggi"
        End Get
    End Property

    Public Overrides Sub Avvia(servizi As IWGServiziPlugin)
        Try
            _servizi = servizi
            servizi.AggiungiVoceAltreFunzioni("Note di oggi", AddressOf MostraNote)
        Catch ex As Exception
            servizi.RegistraErrore("PluginNoteDiOggi.Avvia", ex)
        End Try
    End Sub

    Private Async Sub MostraNote()
        Try
            ' Se il permesso non c'è, WinGabriel lo chiede all'utente (e lo ricorda). Se l'utente rifiuta, si esce.
            Dim concesso As Boolean = Await _servizi.RichiediAccessoAsync(WGPermessi.Note, "Per mostrarti le note")
            If Not concesso Then Return
            Dim note As IList(Of WGVoceElenco) = _servizi.ElencaNote()
            If note.Count = 0 Then
                _servizi.Messaggio("Non ci sono note.")
                Return
            End If
            _servizi.MostraElencoConId("Note", note, AddressOf LeggiUnaNota)
        Catch ex As Exception
            _servizi.RegistraErrore("PluginNoteDiOggi.MostraNote", ex)
        End Try
    End Sub

    Private Sub LeggiUnaNota(id As String)
        Try
            Dim testo As String = _servizi.LeggiNota(id)
            If String.IsNullOrEmpty(testo) Then
                _servizi.Messaggio("Non riesco a leggere questa nota.")
            Else
                _servizi.MostraTesto("Nota", testo)
            End If
        Catch ex As Exception
            _servizi.RegistraErrore("PluginNoteDiOggi.LeggiUnaNota", ex)
        End Try
    End Sub

End Class

8. Gestione da parte dell’utente

Nelle Impostazioni di WinGabriel, alla voce Plugin, l’utente può consultare l’elenco dei plugin rilevati e il loro stato attuale (attivo, disattivato, non firmato, non partito, bloccato, codice non consentito). Dalla finestra è possibile:

  • Attivare o disattivare un plugin in tempo reale.
  • Vedere e revocare i permessi concessi al plugin (voce Permessi): la revoca vale subito.
  • Disinstallare un plugin: l’azione arresta la libreria e rimuove i file nome.dll e nome.dll.firma oltre alle preferenze salvate (la cartella dati del plugin viene invece conservata).
  • Visualizzare le informazioni dettagliate (nome, descrizione, autore, versione, firma, stato).

Se un file .dll viene copiato nella cartella plugin a WinGabriel aperto, viene rilevato aprendo il menu Plugin e può essere attivato subito senza riavviare l’applicazione.

In caso di malfunzionamenti gravi che impediscono l’avvio dell’ambiente, è possibile avviare WinGabriel da riga di comando passando il parametro /senzaplugin: il programma si avvierà ignorando tutti i plugin installati.

9. Versione e compatibilità

La versione dell’interfaccia WGPluginApi.Versione è fissata a "1.0" e non cambia mai. Il file di interfaccia WGPlugin.vb può ricevere soltanto aggiunte: non è consentito rimuovere membri, modificare i nomi, cambiare i tipi o alterare l’ordine dei parametri. In questo modo un plugin compilato con la versione 1.0 resta compatibile con le versioni successive di WinGabriel.

WinGabriel carica esclusivamente librerie compilate con la versione 1.0 di WGPlugin.dll. Se la versione non corrisponde, il plugin viene ignorato e viene mostrato un messaggio all’utente. La versione specifica del plugin (definita tramite la proprietà Versione) è invece gestita a discrezione del singolo sviluppatore.

10. Registro degli errori

Per prevenire blocchi dell’applicazione, ogni metodo ed evento del plugin deve contenere un blocco Try/Catch che intercetta le eccezioni e le invia a RegistraErrore. Di seguito viene mostrata la struttura standard:

Private Sub MiaProcedura()
    Try
        ' Codice del plugin
    Catch ex As Exception
        _servizi.RegistraErrore("NomeClasse.MiaProcedura", ex)
    End Try
End Sub

11. Problemi frequenti

  • Il plugin non compare nell’elenco: verifica che il file .dll si trovi direttamente nella cartella plugin (non in una sottocartella), che la classe sia Public, derivi da WGPluginBase e possieda un costruttore pubblico senza parametri. Se il file è stato copiato mentre WinGabriel era in esecuzione, apri la voce Plugin nelle Impostazioni per visualizzarlo e attivarlo.
  • Stato “non firmato”: indica che la libreria non possiede il file .firma sottoscritto dalla chiave ufficiale. È una condizione normale per i plugin terzi: è sufficiente confermare l’attivazione al primo avvio.
  • Stato “non partito”: si è verificata un’eccezione non gestita all’interno del metodo Avvia. Consulta il registro degli errori di WinGabriel per analizzare il problema.
  • Versione non compatibile: il plugin è stato compilato facendo riferimento a una versione di WGPlugin.dll diversa dalla versione 1.0. Recompila il progetto includendo il riferimento corretto.