WiiBuilder 1.9.0

La version 1.9.0 de WiiBuilder est maintenant disponible. Cette nouvelle version permet l’utilisation de tous les points de code disponibles dans Unicode.

WiiBuilder 1.9.0
WiiBuilder 1.9.0

Au départ, je croyais qu’il suffisait seulement d’ajouter deux caractères supplémentaires dans la zone d’entrée de texte comme je l’avais déjà fait dans la version 1.8.1. Je me suis trompé, ce fut pas mal plus compliqué.

Le problème, c’est que dans C++Builder, wchar_t est sur deux octets. Ce qui veut dire que sa valeur maximale est de 65535. La plupart des points de code des émojis se trouvent en haut de cette valeur. Il fallait donc que je trouve comment encoder ces points de code en UTF-16. Heureusement, la page Wikipedia de UTF-16 explique comment faire. Puisque je suis un peu paresseux, j’ai finalement pris une partie du code sur ce site web. Voici ce que ça donne dans une fonction C++Builder.

String __fastcall CodePointToString(uint32_t ACodePoint)
{
    static const uint32_t LEAD_OFFSET = 0xD800 - (0x10000 >> 10);

    String Result;

    if(ACodePoint > 0xFFFF)
    {
        wchar_t wc[3];
        wc[0] = LEAD_OFFSET + (ACodePoint >> 10);
        wc[1] = 0xDC00 + (ACodePoint & 0x3FF);
        wc[2] = L'\0';

        Result = String(wc);
    }
    else
    {
        Result = wchar_t(ACodePoint);
    }

    return Result;
}

Mettre à jour la médiathèque de Kodi

Kodi est un lecteur multimédia libre. Je l’utilise à partir de LibreELEC sur un Raspberry Pi. Au moment d’écrire ces lignes, la version 9 (Leia) est la plus récente. J’ai configuré Kodi pour aller lire des fichiers vidéos sur un ordinateur sur mon réseau par SMB. Donc, il arrive que j’ajoute des fichiers sur ce disque et que je doive mettre à jour manuellement Kodi pour afficher ces nouveaux fichiers. Dans cet article nous allons voir comment provoquer la mise à jour automatiquement à partir d’une application codée avec C++Builder.

Un collègue au travail m’a parlé que Kodi possède un API qui permet d’effectuer plusieurs tâches. Il l’utilise lui-même dans un logiciel de sa conception. Il m’a dit que Kodi Leia avait brisé une des fonctionnalités qu’il utilise. L’API HTTP ne fonctionnait plus pour mettre à jour la médiathèque. Effectivement, sur le Wiki de Kodi on peux y lire le texte suivant.

HTTP (does not work in v18 Leia)

Pour remédier à cette situation il a décidé d’invoquer l’application curl. En général je n’aime pas que mes applications aient des dépendances externes. J’ai donc creusé un peu pour comprendre comment mieux faire cette action.

Tout d’abord avant de débuter il faut activer l’option Autoriser le contrôle à distance via HTTP.

Kodi: Autoriser le contrôle à distance via HTTP

Dans l’image j’ai encerclé où se trouve le port que vous allez devoir utiliser.

Avant d’utiliser mon propre code, je vais tester la fonctionnalité. Sur la page HOW-TO:Remotely update library on y trouve une commande curl que je vais utiliser dans une fenêtre de terminal.

curl --data-binary '{"jsonrpc": "2.0", "method": "VideoLibrary.Scan", "id": "mybash"}' -H 'content-type: application/json;' http://libreelec:8080/jsonrpc

Voici la réponse que j’obtiens.

{"id":"mybash","jsonrpc":"2.0","result":"OK"}

Cette commande fonctionne bien, donc on ne devrait pas avoir de problème à communiquer avec Kodi. Une chose intéressante que l’on peut voir est qu’elle utilise l’étiquette jsonrpc. En cherchant un peu on comprend que Kodi à commencer à migrer de l’API HTTP vers un API JSON-RPC. Voici ce que la documentation dit à ce sujet.

JSON-RPC is a HTTP- and/or raw TCP socket-based interface for communicating with Kodi. It replaces the deprecated HTTP API, and offers a more secure and robust mechanism in the same format.

Voilà maintenant ce qui explique que l’API HTTP ne fonctionne pas complètement sur Leia.

Maintenant que l’on sait quoi faire, débutons l’application. La première étape est de créer un nouveau projet FireMonkey. Dans la Form il faut insérer un TIdHTTP et un contrôle TButton.

Étant donnée que JSON sera utilisé, il faut ajouter cette ligne à votre fichier d’en-tête.

#include <System.JSON.hpp>

J’ai décidé de faire une petite classe minimaliste pour supporter JSON-RPC. Voici un autre bout de code à ajouter dans le fichier d’en-tête.

class TJsonRpc
{
public:
    __fastcall TJsonRpc() : Version("2.0") {}
    inline virtual __fastcall ~TJsonRpc() {}

    String Version;
    String Method;
    String Id;

    String __fastcall ToString()
    {
        TJSONObject* Obj = new TJSONObject();
        Obj->AddPair("jsonrpc", Version);
        Obj->AddPair("method", Method);
        Obj->AddPair("id", Id);
        TJSONObject* Params = new TJSONObject();
        Obj->AddPair("params", Params);
        const String Result = Obj->ToString();
        delete Obj;
        return Result;
    }
};

Finalement, il faut mettre ce code dans l’évènement OnClick du bouton.

    IdHTTP1->Request->ContentType = "application/json";

    TJsonRpc LJsonRpc;
    LJsonRpc.Method = "VideoLibrary.Scan";
    LJsonRpc.Id = "mybash";

    const String LUrl =  "http://libreelec:8080/jsonrpc";

    TStringStream* LData = NULL;
    try
    {
        LData = new TStringStream(LJsonRpc.ToString());

        const String LAnswer = IdHTTP1->Post(LUrl, LData);

        TJSONObject* Obj = static_cast<TJSONObject*>(TJSONObject::ParseJSONValue(LAnswer));
        try
        {
            TJSONPair* Pair;
            if((Pair = Obj->Get("error")) != NULL)
            {
                String LMessage;
                int LCode;
                TJSONObject* ErrorObj = static_cast<TJSONObject*>(Pair->JsonValue);
                if((Pair = ErrorObj->Get("message")) != NULL)
                {
                    TJSONString* Answer = static_cast<TJSONString*>(Pair->JsonValue);
                    LMessage = AnsiDequotedStr(Answer->ToString(), '\"');
                }
                if((Pair = ErrorObj->Get("code")) != NULL)
                {
                    TJSONNumber* Answer = static_cast<TJSONNumber*>(Pair->JsonValue);
                    LCode = Answer->AsInt;
                }
                throw Sysutils::Exception("Erreur " + String(LCode) + ": " + LMessage);
            }
        }
        __finally
        {
            delete Obj;
        }
    }
    __finally
    {
        delete LData;
    }

Même si je gère les erreurs d’API, il ne s’agit pas de code de production. Je ne vérifie pas que le ID de retour correspond à celui envoyé. Au lieu de tout mettre dans un évènement de bouton on aurait mieux fait de mettre dans une méthode séparée. On aurait pu aussi gérer la communication dans un thread séparé pour ne pas bloquer l’interface. Mais bon… l’idée c’était de montrer que la meilleure manière de communiquer avec Kodi est l’utilisation de JSON-RPC par HTTP.

J’espère que cet article vous sera utile.

Problème de client et/ou serveur web?

Durant le développement d’une application sous C++Builder j’ai frappé un mur assez solide. Pendant presque qu’un mois il y a eu un problème que je n’ai su résoudre. Maintenant j’ai la solution et je vais vous la partager.

L’application en question est un client HTTP qui utilise les composants TIdHTTP et TIdSSLIOHandlerSocketOpenSSL. Elle possède la possibilité de se connecter à plusieurs serveur web dont certains qui utilisent une connexion sécurisée. Les serveurs fournissent un service à l’aide d’un API JSON.

La plupart de mes tests se faisaient avec un serveur Windows Server 2012 R2 qui utilise un certificat SSL généré gratuitement par Let’s Encrypt. Le certificat est renouvelé automatiquement par l’intermédiaire de l’application Windows ACME Simple, anciennement connu sous le nom letsencrypt-win-simple.

Le développement de l’application allait bon train, jusqu’au moment où j’ai commencé à recevoir l’exception suivante lors de l’utilisation de la méthode Get:

Project app.exe raised exception class EIdSocketError with message ‘Socket Error # 10054 Connection reset by peer.’.

Bien évidemment j’ai pensé que j’avais changé quelque chose dans mon code. Alors j’ai tenté d’utiliser le code de soumissions antérieures. J’utilise toujours Git, même pour mes projets personnels de petite envergure. Dans ce cas, cela aurait pu me sauver la vie, mais non, mon code qui fonctionnait ne fonctionne plus!

Je n’utilisais pas les derniers fichiers DLL de OpenSSL, alors j’ai téléchargé la dernière version. Au moment d’écrire ces lignes la version 1.0.2o était la version LTS la plus à jour. Malheureusement ceci ne provoqua aucun changement de comportement.

Il est important de mentionner que le problème survient seulement avec mon serveur Windows Server 2012 R2 et que l’API répond correctement sur celui-ci avec Chrome et Firefox. Donc, mon serveur fonctionne avec certains clients et mon application fonctionne avec tous les autres serveurs testés! Est-ce un problème de client ou de serveur?

Étant donné que je voulais continuer de développer mon application j’ai décidé d’utiliser un autre serveur pour faire mes tests. La bonne nouvelle, c’est que mon application pouvait progresser, la mauvaise c’est qu’il est possible que certains utilisateurs éprouvent le même problème. Pendant presque un mois ce problème est resté dans ma tête.

L’application tirant à sa fin, je n’avais plus qu’un seul problème à régler. Aux grand maux, les grands moyens, je décide d’utiliser Wireshark pour regarder ce qui se trame lors de la connexion. La seule chose que j’observe est qu’après une requête HTTPS (Client Hello) au serveur, celui-ci semble se déconnecter.

Je me dis alors que c’est peut-être une mise à jour manquante sur le serveur! Alors je fais toutes mes mises à jour Windows, même celles facultatives. J’en profite même pour désactivé TLS version 1.0. Je redémarre le serveur et malheureusement, ceci ne règle rien. C’était quand même une bonne idée de fixer les trous de sécurités sur le serveur.

Par la suite, je vais dans la console de IIS et pour la première fois j’y vois une alerte qui attire mon attention:

No default SSL site has been created. To support browsers without SNI capabilities, it is recommended to create a default SSL site.

Je commence donc à lire un peu sur le sujet et je me rappelle que dans l’outil SSL Server Test de SSL Labs il y avait une bannière bleue avec le texte suivant:

This site works only in browsers with SNI support.

Ma lecture m’amène à comprendre que Server Name Indication (SNI) fait partie du Client Hello. Tiens, cela me rappelle ce que j’ai vu dans Wireshark. C’est le client qui doit supporter cette extension. Dans mon cas, le client est la bibliothèque Indy. Est-ce qu’il y a des problèmes connus avec SNI dans Indy? Le support SNI a été introduit dans la soumission 5321 du 11 janvier 2016. J’utilise RAD Studio XE8 qui date de 2015. Pour être certain, je regarde aussi le fichier Idglobal.hpp où l’on trouve l’information sur la version de Indy utilisé:

#define gsIdVersionMajor 10
#define gsIdVersionMinor 6
#define gsIdVersionRelease 2
#define gsIdVersionBuild 5263

Eh bien, la version de Indy que j’utilise ne supporte pas SNI!

Pour faire un test rapide, je désactive SNI sur mon serveur:

Require Server Name Indication
La case à cocher Require Server Name Indication

Tout fonctionne!

En conclusion, je présume que ce qui s’est passé durant le développement de l’application est que le serveur sur Windows Server 2012 R2 a dû renouveler le certificat SSL et que la configuration du serveur à changer. D’ailleurs lorsque l’on regarde l’historique de soumission du logiciel Windows ACME Simple, plusieurs messages sont en lien avec SNI. J’ai fait une mise à jour à la version 1.9.10.1 et j’espère que lors du prochain renouvellement il n’y aura pas de problèmes. S’il y en a, au moins je sais où regarder en premier.

Afficher le temps universel coordonné en texte

Dans cet article nous allons voir comment afficher le temps universel coordonné en texte avec C++Builder.

Tout d’abord, il faut ajouter le fichier d’en-tête suivant:

#include <System.DateUtils.hpp>

Ensuite on va créer une fonction comme celle-ci:

String __fastcall UtcOffsetToString()
{
    String Result = "UTC±00:00";
 
    const int Bias = TTimeZone::Local->UtcOffset.Negate().TotalMinutes;
    if(Bias != 0)
    {
        const wchar_t Neg[] = {L'+', L'-'};
        Result.sprintf(L"UTC%c%02d:%02d",
            Neg[Bias > 0],
            abs(Bias) / Sysutils::MinsPerHour,
            abs(Bias) % Sysutils::MinsPerHour);
    }
 
    return Result;
}

Il est important de mentionner que ce code est fortement inspiré de la fonction DateToISO8601.

Simuler des évènements de touche sur Android

Dans cet article nous allons voir comment simuler des évènements de touche sur Android dans une application FireMonkey codée en C++. Pour faire cela nous allons utiliser la classe Instrumentation de l’API Android. Malheureusement cette classe n’est pas exposée dans la RTL. La bonne nouvelle est qu’il existe un outil pour générer les fichiers qui nous sont nécessaires. C’est à l’aide de Java2OP.exe que nous allons y arriver. Cette application par ligne de commande sert à générer des fichiers Delphi à partir de bibliothèques Java, d’où le nom Java To Object Pascal (Java2OP).

Donc avec cette commande nous obtiendrons le fichier Androidapi.Instrumentation.pas :

Java2OP.exe -classes android.app.Instrumentation -unit Androidapi.Instrumentation

La première fois que j’ai exécuté la commande, une erreur est survenue me disant que JDK était manquant. En cas de problème, je vous conseille de vous référer à l’aide où il y a une section Dépannage.

Pour commencer, on doit démarrer RAD Studio et créer un nouveau projet multi-périphériques C++Builder. Nous allons insérer un TMemo dans la Form. C’est dans ce contrôle que les touches de clavier simulées apparaitront. On doit bien sûr ajouter le fichier Androidapi.Instrumentation.pas au projet. À la compilation le fichier Androidapi.Instrumentation.hpp sera créé.

La simulation de touche de frappe doit se faire dans un thread, donc il faut aller dans le menu File / New / Other… et choisir Thread Object dans la section C++Builder Files. Vous pouvez donner le nom TInjectInput à la classe. Vous pouvez enregistrer le fichier et le nommer InjectInput.cpp. Dans ce fichier, il faut ajouter les en-têtes suivantes:

#include <Androidapi.JNI.GraphicsContentViewText.hpp>
#include "Androidapi.Instrumentation.hpp" // Fichier généré par Java2OP.exe

Dans l’évènement Execute on ajoute le code suivant:

    while(Terminated == false)
    {
        try
        {
            _di_JInstrumentation Instrumentation = TJInstrumentation::JavaClass->init();

            Graphicscontentviewtext::_di_JKeyEvent KeyEventDown = TJKeyEvent::JavaClass->init(
                TJKeyEvent::JavaClass->ACTION_DOWN, TJKeyEvent::JavaClass->KEYCODE_A);
            Instrumentation->sendKeySync(KeyEventDown);

            Graphicscontentviewtext::_di_JKeyEvent KeyEventUp = TJKeyEvent::JavaClass->init(
                TJKeyEvent::JavaClass->ACTION_UP, TJKeyEvent::JavaClass->KEYCODE_A);
            Instrumentation->sendKeySync(KeyEventUp);
        }
        catch(const Exception &)
        {
        }

        Sleep(5000);
    }

Cela aura comme effet de générer un événement de frappe avec la touche a enfoncée et ensuite un évènement avec la même touche relâchée. La méthode injectKeyEvent aurait aussi pu être meilleure, mais je voulais utiliser KeyEvent. Il est important de mettre le code dans un try/catch car lorsqu’on quitte l’application l’exception suivante est lancée:

java.lang.SecurityException: Injecting to another application requires INJECT_EVENTS permission

Il faut savoir que même si la permission INJECT_EVENTS est accordée, l’exception sera quand même lancée si l’application n’est pas signée.

Dans le fichier .h de la Form on ajoute le code suivant:

//---------------------------------------------------------------------------
#ifndef Unit1H
#define Unit1H
//---------------------------------------------------------------------------
#include <System.Classes.hpp>
#include <FMX.Controls.hpp>
#include <FMX.Forms.hpp>
#include <FMX.Controls.Presentation.hpp>
#include <FMX.Memo.hpp>
#include <FMX.ScrollBox.hpp>
#include <FMX.Types.hpp>
#include "InjectInput.h"
//---------------------------------------------------------------------------
class TForm1 : public TForm
{
__published:	// IDE-managed Components
    TMemo *Memo1;
private:	// User declarations
    TInjectInput* FInjectInput;
public:		// User declarations
    __fastcall TForm1(TComponent* Owner);
    virtual __fastcall ~TForm1();
};
//---------------------------------------------------------------------------
extern PACKAGE TForm1 *Form1;
//---------------------------------------------------------------------------
#endif

À la ligne 12 on ajoute l’en-tête. À la ligne 19 on ajoute une propriété privée. Finalement à la ligne 22 c’est le destructeur de Form.

Dans le fichier .cpp de la Form on ajoute le code suivant:

//---------------------------------------------------------------------------
#include <fmx.h>
#pragma hdrstop

#include "Unit1.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.fmx"
TForm1 *Form1;
//---------------------------------------------------------------------------
__fastcall TForm1::TForm1(TComponent* Owner)
    : TForm(Owner)
{
    FInjectInput = new TInjectInput(true);
    FInjectInput->Start();
}
//---------------------------------------------------------------------------

__fastcall TForm1::~TForm1()
{
    delete FInjectInput;
}
//---------------------------------------------------------------------------

Dans le constructeur on va créer le thread et le démarrer. On ajoute le destructeur pour libérer la mémoire.

Maintenant, il suffit de choisir le Target Android, compiler et déployer le code. On doit cliquer sur le contrôle TMemo et des a devraient apparaître à toutes les 5 secondes.

Traduction avec l’API MyMemory et CCAN/JSON

Dans cet article nous allons voir comment traduire du texte avec l’API web MyMemory. Je voulais utiliser celui de Google, mais il est payant. MyMemory n’a pas la même qualité de traduction que Google, mais le but est de faire une démonstration et non un produit commercial.

Pour analyser la réponse en JSON, au lieu d’utiliser la classe TJSONObject de la RTL, c’est la bibliothèque CCAN/JSON qui sera utilisée. Cette bibliothèque à code source ouvert est codée en C. Je l’ai choisie car dans une étude comparative sur 41 bibliothèques JSON elle semblait bien classée.

JSON Parsing Time
Temps d’analyse des bibliothèques JSON

J’ai dû exclure toutes celles en C++11, car j’utilise encore principalement le compilateur bcc32. Le facteur principal était simplement la facilité d’intégration. Si ça ne compilait pas dans les minutes suivant le téléchargement, je passais à la prochaine dans la liste.

La première étape est de créer un nouveau projet FireMonkey. Dans la Form il faut insérer un TRESTClient, un TRESTRequest, un TRESTResponse, trois contrôles TEdit, un TComboBox et un TButton.Vous pouvez donner comme texte à votre bouton le mot « Traduire ».

Voici ce à quoi l’interface devrait ressembler:

Il faut ajouter le fichier json.c qui se trouve trouve dans le dossier \ccan\json au projet pour qu’il soit compilé. Je n’ai pas eu de problème pour l’utiliser avec les plateformes Win32, Win64 et OS X.

Dans votre fichier cpp voici l’en-tête à utiliser.

extern "C"
{
    #include "json.h"
}

Il ne faut pas oublier qu’il s’agit de code en C et non C++, c’est pourquoi il faut ajouter un peu plus de code.

Voici le code à ajouter dans votre constructeur pour initialiser les contrôles:

    TListBoxItem* ListItems;

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Anglais";
    ListItems->TagString = L"en";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Chinois (simplifié)";
    ListItems->TagString = L"zh-CN";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Russe";
    ListItems->TagString = L"ru";

    ListItems = new TListBoxItem(this);
    ListItems->Parent = ComboBox1;
    ListItems->Text = L"Latin";
    ListItems->TagString = L"la";

    // On sélectionne le premier item par défaut
    ComboBox1->ItemIndex = 0;

    // Message à afficher quand la propriété Text est vide
    Edit1->TextPrompt = L"Texte à traduire";

    // Le nombre de match par défaut est mis à zéro
    Edit3->Text = L"0";

    // On n'écrit pas dans les champs de résultat
    Edit2->ReadOnly = true;
    Edit3->ReadOnly = true;

Voici le code à ajouter dans l’évènement OnClick du bouton:

    if(Edit1->Text.IsEmpty() == true || ComboBox1->ItemIndex < 0)
    {
        Edit2->Text = "";
        Edit3->Text = "0";
        return;
    }

    String LQuery = Edit1->Text;
    String LTarget = ComboBox1->ListItems[ComboBox1->ItemIndex]->TagString;

    RESTClient1->BaseURL = "http://api.mymemory.translated.net/get" \
        "?q=" + Edit1->Text +
        "&langpair=fr|" + LTarget;

    RESTRequest1->Execute();

    // Conversion en UTF-8
    TBytes LUTF8Array = TEncoding::UTF8->GetBytes(RESTResponse1->JSONText + "0");
    LUTF8Array[LUTF8Array.High] = '\0'; // Le caractère nul de terminaison est requis

    JsonNode* LRootNode = json_decode((char *)&LUTF8Array[0]);
    if(LRootNode != NULL)
    {
        JsonNode* LResponseNode = json_find_member(LRootNode, "responseData");
        if(LResponseNode != NULL && LResponseNode->tag == JSON_OBJECT)
        {
            JsonNode* LTextNode = json_find_member(LResponseNode, "translatedText");
            if(LTextNode != NULL && LTextNode->tag == JSON_STRING)
            {
                TBytes LUTFAnswer;
                LUTFAnswer.Length = strlen(LTextNode->string_);
                memcpy(&LUTFAnswer[0], LTextNode->string_, LUTFAnswer.Length);
                Edit2->Text = TEncoding::UTF8->GetString(LUTFAnswer);
            }
            JsonNode* LMatchNode = json_find_member(LResponseNode, "match");
            if(LMatchNode != NULL && LMatchNode->tag == JSON_NUMBER)
            {
                Edit3->Text = LMatchNode->number_;
            }
        }
        json_delete(LRootNode);
    }

Le texte utilisé par CCAN/JSON utilise l’encodage UTF8, c’est pourquoi il y a des conversions. Sinon le code ressemble étrangement à ce que RAD Studio nous a habitués avec sa classe JSON.

Maintenant il ne reste qu’à exécuter l’application pour la tester.
Traduction macOS

Déboguer sur CyanogenMod avec RAD Studio

Dans un article précédant, datant de novembre 2014, j’expliquais comment déboguer sur la OUYA avec RAD Studio. Plusieurs choses ont changé depuis ce temps. Le plus important étant la vente de OUYA a Razer et le fait que le matériel cessera d’être supporté. Je croyais bien que ma petite boîte grise allait rester pour toujours à l’intérieur de l’un de mes tiroirs, jusqu’à ce que je tombe sur un article expliquant comment faire rouler CyanogenMod sur ma OUYA. CyanogenMod version 11 (CM11) est basé sur Android KitKat 4.4.x. Donc, présentement ma OUYA fonctionne comme Android 4.4.4. C’est ce que l’on peut voir sur la capture d’écran qui suit.

About tablet
Information sur la OUYA et le système d’exploitation

Ma motivation première pour transformer ma OUYA en appareil Android standard n’était pas nécessairement le développement d’applications. Comme beaucoup de gens, c’était de faire fonctionner Kodi version 16.1 (Jarvis). Le reste de l’article va se concentrer par contre sur ma seconde raison.

Pour utiliser RAD Studio avec CM11, il s’agit de faire les étapes habituelles que l’on ferait sur Android. La première étant de faire afficher la section avec les options pour développeur. Pour faire cela on doit aller dans les Settings dans la section About tablet. Ensuite on clique sept fois sur Build number.

Maintenant que la section Developer options des Settings est disponible, il faut s’assurer que les options Android debugging et ADB over network sont activées:

ADB over network
Activation du débogage par le réseau

L’adresse IP et le numéro de port qui se trouve juste en dessous de l’option l’option ADB over network vous seront utiles bientôt. Il faut d’abord se connecter à la OUYA. Pour cela l’application Android Debug Bridge (adb) située dans le dossier /<sdk>/platform-tools sera utilisée. Dans le SDK Manager pour Android on trouve ce chemin dans la section Adb location. Vous devez aller dans ce dossier et taper la ligne de commande suivante:
adb connect 192.168.1.115:5555
Si tout fonctionne le résultat devrait être: connected to 192.168.1.115:5555
This ingredient performs well in the human body to prevent cheap levitra canada testosterone generation. Depression is another common issue that causes penile weakness appalachianmagazine.com purchase levitra due to imbalance in the production of nitric oxide to increase. This article aims to provide an explanation of the possible matter behind click for source buy cialis without prescription snoring. The MarketWatch report causes it to be practically obvious as water, regardless of tadalafil online no prescription‘s powerful maintain on the marketplace, Bayer recorded a 16% advance in sales of its erectile dysfunction drug, to 73 million euros this year.The figures clearly display that levitra won’t await prolonged to defeat its competition.
Vous pouvez aussi taper cette commande pour voir la liste des dispositifs connectés:
adb devices
Si la liste est vide, c’est que la commande de connexion à échouer.

Ensuite, ouvrez ou créez un nouveau projet de type Multi-Device Application dans RAD Studio. Sélectionnez le Target Android. Dans la section Target de celui-ci, OUYA devrait apparaitre. Si ce n’est pas le cas il faut faire un Refresh.

OUYA Target
Le Target OUYA est sélectionné

Il est maintenant possible de compiler le fichier APK et de tester l’application directement sur la console. Une fois fermée, elle devrait être disponible avec les autres applications.

Si vous voulez simplement installer l’application, vous pouvez utiliser cette ligne de commande:
adb install "C:\Projets\Application\Android\Release\application\bin\application.apk"

Ping avec TIdIcmpClient

Dans cet article, je vais vous donner le code pour faire une méthode qui effectue un Ping. Nous utiliserons la classe TIdIcmpClient de Indy.

Voici la déclaration de la méthode:

    bool __fastcall Ping(const String AIP, int ATimeout = 5000);

Le paramètre ATimeout est facultatif et sa valeur par défaut est 5 secondes.

Pour utiliser TIdIcmpClient, il faut bien sûr ajouter le fichier d’en-tête suivant à votre fichier cpp:

#include <IdIcmpClient.hpp>

Voici le code de la méthode:

bool __fastcall TForm1::Ping(const String AIP, int ATimeout)
{
    bool Result = false;

    TIdIcmpClient* LIcmpClient = NULL;
    try
    {
        LIcmpClient = new TIdIcmpClient(NULL);
        LIcmpClient->Host = AIP;
        LIcmpClient->Port = 0;
        LIcmpClient->ReceiveTimeout = ATimeout;
        LIcmpClient->IPVersion = TIdIPVersion::Id_IPv4;
        LIcmpClient->Protocol = Id_IPPROTO_ICMP;

        try
        {
            const String LToSend = " !\"#$%&'()*+,-./0123456789:;<=>?";
            LIcmpClient->Ping(LToSend, 0);
            if(LIcmpClient->ReplyStatus->ReplyStatusType == TReplyStatusTypes::rsEcho &&
                LIcmpClient->ReplyStatus->BytesReceived > 0 &&
                LIcmpClient->ReplyData.Pos(LToSend) > 0)
            {
                Result = true;
            }
        }
        catch(...)
        {
        }
    }
    __finally
    {
        delete LIcmpClient;
    }

    return Result;
}

Si tout fonctionne, la méthode retourne true. Ce n’est pas plus compliqué que cela. Par contre, il est important de dire que ce code est bloquant. Donc, pour ne pas geler l’interface utilisateur c’est important de le rouler dans un thread séparé.

Utilisation de TWebBrowser avec JavaScript

Dans cet article, je vais interagir avec du contenu HTML à l’intérieur d’un composant TWebBrowser. Le but est de valider une page web avec l’API de Nu Html Checker (v.Nu) et d’afficher le résultat. On aurait pu utiliser les composants REST inclut dans RAD Studio, mais cette fois-ci, j’ai décidé d’utiliser la bibliothèque jQuery. Donc, il va y avoir plus de code JavaScript que de code C++. Cet article sert à démontrer certaines fonctionnalités de FireMonkey et non d’avoir une application parfaitement codée et prête à mettre sur un magasin en ligne.

Tout d’abord, il faut créer une application multi-périphériques dans laquelle il faut ajouter quelques composants. Un TPanel aligné à Top dans lequel on insère un TEdit pour l’entrée du URL et un TSpeedButton pour déclencher le chargement du résultat. On ajoute un TWebBrowser aligné à Top et un TSplitter lui aussi avec le même alignement. Par la suite, on ajoute un TMemo et on met Align à Client. Il va contenir le code HTML et JavaScript. Pour cette raison, le code suivant sera ajouté à la propriété Lines:

<html>
    <head>
        <script src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js" type="text/javascript"></script>
        <script type="text/javascript">
            function go(urlToValidate) {
                $.ajax({
                    type: "GET",
                    url: 'http://validator.w3.org/nu/?out=json&doc=' + urlToValidate,
                    dataType: "jsonp",
                    cache: false,
                    crossDomain: true,
                    success: function (data) {
                        $("#out").append('<ul>');
                        $.each(data.messages, function() {
                            $.each(this, function(k, v) {
                                if(k == 'message') {
                                    $("#out").append('<li>' + v + '</li>');
                                }
                            });
                        });
                        $("#out").append('</ul>');
                    },
                    error: function (XMLHttpRequest, textStatus, errorThrown) {
                        $("#out").html("Erreur!");
                    }
                });
            }
        </script>
    </head>
    <body>
        <div id="out"></div>
    </body>
</html>

Pour ceux qui on déjà utilisé jQuery, ce code n’a rien d’impressionnant. Il ne fait que faire une requête AJAX de type GET à l’API v.Nu. Si l’appel fonctionne, une liste de message sera alors affichée. Si elle échoue, le mot Erreur sera présenté. La fonction JavaScript go ne semble pas appeler. Effectivement, elle sera invoquée par du code C++.

Alors, nous allons débuter la partie C++ du projet. L’encodage du URL aurait pu se faire en JavaScript mais je voulais tester TURLEncoding. Pour cela, il faut ajouter le fichier d’en-tête suivant

#include <System.NetEncoding.hpp>

Dans le constructeur, on ajoute ce code pour définir l’URL par défaut et charger le code HTML dans le navigateur web.

    Edit1->Text = "http://www.google.com";
    WebBrowser1->LoadFromStrings(Memo1->Text, "");

Dans l’évènement OnClick du bouton on ajoute ceci:

    String LUrl = System::Netencoding::TURLEncoding::URL->Encode(Edit1->Text);
    WebBrowser1->EvaluateJavaScript("go('" + LUrl + "');");

Ici on encode l’URL et on l’utilise comme paramètre lors de l’appel à la fonction go JavaScript.

Si tout fonctionne bien, vous devriez obtenir un résultat similaire à cette capture d’écran:
Nu Html Checker

Afficher une image Gravatar

Dans cet article, nous allons voir comment afficher une image Gravatar dans une application conçue avec C++Builder.

Tout d’abord, il faut créer un projet FireMonkey dans lequel il faut ajouter un composant TEdit, un TButton et un TImage.

Ensuite, il faut ajouter les fichiers d’en-têtes suivants:

#include <System.Hash.hpp>
#include <IPPeerClient.hpp>
#include <REST.Client.hpp>

Par la suite, vous pouvez ajouter ce code dans l’évènement OnClick du bouton:

String LUrl = Edit1->Text.Trim().LowerCase();
LUrl= System::Hash::THashMD5::GetHashString(LUrl);
LUrl = "http://www.gravatar.com/avatar/" + LUrl + "?s=80";
 
System::Classes::TMemoryStream* LStream = NULL;
try
{
    LStream = new System::Classes::TMemoryStream();
    Rest::Client::TDownloadURL::DownloadRawBytes(LUrl, LStream);
    Image1->Bitmap->LoadFromStream(LStream);
}
__finally
{
    delete LStream;
}

Le fonctionnement est simple: vous devez entrer une adresse de courrier électronique dans la zone de texte et cliquer sur le bouton par la suite. Le Gravatar correspondant à l’adresse va être téléchargé et affiché dans le composant TImage. S’il n’y a pas d’image liées à l’adresse, alors une image par défaut sera chargée.

Dans l’URL qui correspond à l’image à télécharger, on peut spécifier la grandeur de l’image à l’aide du paramètre s= ou size=. Par défaut, la grandeur est de 80 pixels par 80 pixels si elle n’est pas spécifiée. La grandeur peut être de 1 pixel jusqu’à 2048 pixels.

Je vous laisse avec un exemple de ce que à quoi pourrait ressembler l’application:

Gravatar