NoCV
J'ai été consultant en SSII pendant presque huit ans. J'ai appris l'exercice de la présentation de parcours : adapter le contenu, cadencer le rythme, être lisible, répondre aux questions qui n'ont pas encore été posées, équilibrer le coté technique et le coté humain... rassurer le client. J'avais de bons résultats.
J'ai ensuite travaillé comme responsable technique de projet et architecte dans l'aéronautique pendant quatre ans. Je recevais des CV sélectionnés selon des mots clefs, des rangs d'écoles, ... Je les lisais sans savoir quoi y chercher et sans y trouver grand chose. A l'entretien technique, que la réponse ait été ou pas dans le CV, ce que j'essayais de savoir n'était jamais quelles quantité de tel langage la personne avait écrit, ni quelle version de telle librairie ou tel framework elle connaissait, mais si la personne savait dire "je ne sais pas", si elle arrivait à comprendre mes explications1, si elle était plus intéressée par comment faire les choses ou par pourquoi les faire comme ça... Toutes ces choses qui font que justement, les informations que j'avais avant l'entretient n'étaient pas très importantes. Je n'ai pas vraiment trouvé à l'époque le format d'entretien que j'aurais voulu. Avec le recul, j'aurais pu me contenter de raconter le projet aux personnes et de leur demander ce qu'elles auraient voulu essayer d'y apporter et ce qu'elles espéraient qu'on sache leur apporter...
J'ai appris plus tard à comprendre ce que j'avais perdu malgré mes efforts dans cette période de ma vie, puis à retrouver petit à petit le plaisir que j'avais eu à programmer quand j'avais dix, quinze ou vingt ans2. Je suis devenu freelance et ai commencé à beaucoup moins "travailler" - si travailler veut dire "facturer son temps" - et à beaucoup plus "faire des trucs". Enfin à essayer.
Je me suis demandé, beaucoup, comment faire part de tout ceci dans un CV ; comment y raconter qui je suis, comment laisser le lecteur décider de ce qu'il pense que je pourrais ou pas apporter à son projet et/ou à son équipe. Et puis j'ai admis que ca ne tiendrait pas sur une page A4 en trois parties avec les mot-clés nécessaires. J'ai essayé d'autres choses 3. J'ai fais ce site un peu pour ceci, beaucoup pour écrire tout un tat de choses que je voudrais ne pas oublier de mon parcours.
Il faudra forcément un peu plus de temps pour le consulter que pour screener un CV : si vous vouliez vraiment regarder les vidéos, lire le code... il faudrait probablement un après-midi entier. Mais ce n'est pas l'idée, je voudrais plutôt vous inviter à trouver et à vous attarder sur ce qui pourrait avoir de la valeur pour vous.
Si je devais synthétiser de quelle façon je voudrais contribuer à une équipe, ce pourrait être ça : "Inviter à prendre le temps de mieux faire ce qui pourrait vraiment avoir de la valeur pour nous."
J'ai, je crois, beaucoup progressé en explications depuis. Je l'espère en tout cas.
J'en ai bientôt quarante-quatre.
Ce tweet reste d'actualité, mais je mettrais Rust en têtes des choses que je voudrais pratiquer. (Et Bruxelles serait remplacé par l'est de la Belgique ou Luxembourg.)
Prise de parole en publique
Dans le chapitre suivant, je raconte comment j'ai commencé à parler en public vers quarante ans.
Je n'aime pas les success stories pleines de promesses - en bonne parties dûes aux hasards et à des avantages pas forcément visibles - non reproductibles. Pourtant en discutant avec beaucoup de "speakers" depuis quelques années, j'ai retrouvé très souvent des éléments dans leur histoire : des personnes qui pensaient qu'elles n'avaient rien à raconter, d'autres qui les ont accueillies, des choses qu'on ne se croyait pas capable de faire et pourtant... des orateurs dont j'adore le travail qui sont pleins de doutes et de trac et n'acceptent qu'à moitié l'idée que ce qu'ils et elles proposent est important pour d'autres.
J'ai essayé de trouver des formats d'ateliers qui aideraient des personnes intéressées à se mettre à parler en public. J'ai donné quelques coups de pouce, quelques conseils, j'ai essayé retransmettre ceux qui m'avaient aidé. Mes ces conseils ne touchent que celles ou ceux qui en font la demande, c'est à dire qui ont déjà fait une bonne partie du chemin, et qui savent que je suis disponible pour les y aider, et pour qui je pourrais être la bonne personne.
J'ai donc essayé, en attendant que les unconferences se généralisent, d'autres choses pour atténuer la barrière imaginaire et bien présente entre les orateurs et les participants.
J'essaie d'en présenter quelques unes ici.
Autointerviews Orateurs
Un jour au hasard d'une conversation sur Slack, quelqu'un qui ne parlait pas dans des conférences me demanda "Ca t'apporte quoi de parler en public ?"
La question m'interpela, à la fois parce que je ne me l'étais jamais posée - je le faisais c'est tout -, et parce qu'elle me rappela les nombreuses questions que je m'étais posées lors de mes premières conférences, quand je mettais les pieds au milieu de tous ces gens qui se connaissaient, avaient l'air de savoir ce qu'il se passait et de trouver ça normal.
Celà m'a donné envie à la fois de poser cette question (et quelques autres), et de rendre les réponses publiques pour désacraliser un peu le "status" de speaker.
Il restait à trouver un format, pour le tester rapidement et pour permettre que chacun.e réponde comme ils ou elles le voulaient. J'ai fais un simple repository github avec un template d'issue - j'avais de bonne chance de toucher en premier des personnes ayant déjà un compte -, j'ai posté sur twitter, ça a parlé au gens, le mot a, j'ai eu plein de réponses dont la plupart m'ont étonnées.
Il y a même eu quelques émules (réponses faites en meetup avant les talks) et utilisation (en ateliers de préparation à la prise de parole) qu'on m'a remontées. Il resterait à trouver à ce projet un endroit pour le rendre plus visible ; pour que les personnes à qui il pourrait servir tombent dessus. En attendant, il est là : https://github.com/FabienTregan/autointerview-orateurs
Podcast qui n'a pas encore de nom
On peut avoir trente ans de programmation derrière soit et ne pas tout connaitre. On peut ne pas oser poser une question, on peut oublier d'en prendre le temps. On peut croire déranger une personne qui n'ose pas prendre la parole d'elle-même où qui ne se sent pas légitime ou encore qui pensent que si elle sait, tout le monde sait déjà.
On peut avoir trouvé des réponses longues, précises, détaillées, ou markettées, mais avoir envie d'autre chose.
J'ai eu envie de tester quelque chose :
- Une question simple dont on n'a pas forcément la réponse
- Posée à quelqu'un qu'on a envie d'entendre sur le sujet
- Une réponse courte et a peu prêt accessible
Sur un podcast qui se veut avant tout collaboratif et communautaire. (voir plus bas pour contribuer)
Les épisodes enregistrés
- les Stack Canaries, par Emy
- les Lenses par François
- l'Affichage sur l'atari 2600 par Fabien (c'est moi)
Les question encore en cours
- la Machine de Von Neumann (frederic)
- les différentes formes de Polymorphisme (didier)
- les Lambdas (vincent)
- les tests PCR (kromette)
- le systeme d'exploitation
- reverse proxy et load balancer
- process, thread et coroutine
Contribuer
Je prends les questions, les suggestions de personnes à interroger, les relectures, les avis techniques. En fait il me faudrait même de l'aide pour faire connaitre le projet, lui trouver un nom, faire un site web, le rendre lisible par les lecteurs de podcast, l'indexer, trouver un logo... Il me manque aussi probablement beaucoup de compétences sur le traitement du son et d'autres choses encore que je ne vois même pas. Si quelqu'un veut contribuer, il y a surement de la place pour lui ou elle.
Questions
La bonne question serait avant tout celle que vous n'avez pas posée, pour laquelle vous n'avez toujours pas de réponse simple ou pour laquelle vous avez entendu une réponse dont vous vous êtes dit après coup que finalement, elle était utile.
Les questions portent sur la définition d'un terme technique ou de différences entre deux termes.
Si tu as d'autres questions à poser, si tu veux poser une question dont tu connais très bien la réponse mais tu penses qu'elle mérite qu'on lui accorde quelques minutes, parlons en.
Dans la question, on essaie de présenter très rapidement la personne à qui on la pose - pas pour justifier de sa pertinence mais pour donner un contexte à l'auditeur. On peut suggérer des éléments de réponse, mais la question doit être ouverte, elle ne sera de toute façon enregistrée qu'après la réponse de façon à pouvoir la modifier pour coller à cette dernière. Il n'y a pas de réponse hors sujet.
Suggérer quelqu'un à qui donner la parole
S'il ya quelqu'un que tu as envie qu'on entendre plus, ou si toi-même tu voudrais t'entrainer à prendre la parole et construire une présentation sur un format court et différé, n'hésite pas à me contacter. On trouvera probablement la bonne question.
Répondre
Si je t'ai soumis une question, c'est que quelqu'un a pensé que tu étais un bonne personne à interroger, et je n'en doute pas.
Les réponses sont très libres. Il n'y a pas de réponses hors sujet, ou pas assez complètes. L'idée est plus de montrer que que telle ou telle personne répond à cette question. Il y aura, j'espère, des questions avec plusieurs réponses. Les questions sont enregistrer après les réponses pour introduire celle-ci au mieux et présenter la personne qui répond d'une façon faite en accord avec elle.
Je peux aider à construire la réponse, faire des retours sur la premières version enregistrée, ou aider à trouver la bonne personne pour accompagner la personne qui répond.
Voici quelques guides pour répondre :
- Les réponses sont diffusées en audio (avec peut être un transcript dans le futur), on peut trouver quelqu'un pour lire si tu préfère contribuer par écrit.
- Les réponses sont de préférence relativement anonymes (on donnera bien sûr le compte twitter ou github de la personne qui répond si elle le souhaite, mais on n'est pas là pour montrer qui est super fort ou vend quoi sur tel ou tel sujet)
- Il y a beaucoup de façon d'être "dev", tout le monde ne sait pas ce qu'est un registre, un DMA, le BDD ou DDD. Même si on s'autorise les réponses très techniques, essayons de s'assurer que tout "dev" comprenne au moins l'idée.
- La réponse est relativement courte, peut être entre deux et cinq minutes.
- Il peut y avoir une slide (une imagine d'illustration), mais il est probable que tout le monde ne l'ai pas sous les yeux en écoutant la réponse et il faudrait qu'elle ne soit pas nécessaire à la compréhension.
L'idée est d'avoir une question et une réponse que l'on puisse écouter en marchant tranquillement le matin en allant au travail.
Comment j'ai commencé
J'ai commencé à animer des ateliers par hasard : j'avais participé à un atelier EventStorming lors d'un Agile Open France, je voulais en savoir plus, j'ai proposé au groupe Software Crafters Toulouse une soirée sur le sujet pour en apprendre plus. Une bonne dizaine de personnes était intéressée, mais celles qui sont venues connaissaient EventStorming que de nom. J'ai proposé d'essayer de rejouer l'atelier auquel j'avais participé, et ca a fonctionné, il y a eu de la demande et donc d'autres dates.
J'ai commencé à parler dans des conférences je ne sais pas trop comment : Après avoir participé à une grosse conférence, j'ai eu le sentiment que les sujets n'étaient pas ceux que j'aurais aimé voir traités, qu'on y parlait plus de frameworks bientôt à la mode que de notre métier. De fil en aiguille, aidé par les premiers ateliers que j'avais animés et les rencontres, j'ai voulu essayer de proposer un talk sur les boucles (le for
, le while
, ...) - sujet qui s'est avéré beaucoup plus riche et connexe que je ne l'avais imaginé. Quand j'ai voulu le proposer, j'ai fait le parcours "à vide" une première fois. Comme c'était sur le site du DevFest local dont j'avais déjà croisé plusieurs des organisateurs et que je voulais leur éviter la lecture d'un formulaire rempli de "qslmdkfjqskldfgjqsdjklf", j'ai faussement proposé de parler d'un sujet que je faisais dans mon atelier et dont je parlais peu ("des portes logiques pneumatiques en bois"). Le parcours test à (presque) blanc fait, j'ai proposé mon vrai sujet. Quelques semaines plus tard, j'apprenais avec incrédulité que c'est le premier sujet qui avait été retenu. J'ai donné ce premier talk entre midi et deux, devant peut être trente ou quarante personnes dont la moitié mangeaient leur sandwich dans une salle où ils étaient interdits.
Une de ces personnes me contacta quelques semaines plus tard, pour me dire que le sujet de ma présentation collerait bien avec le thème d'une conférence qu'il organisait. Un court appel skype plus tard, j'acceptais d'y participer sans avoir vraiment compris qu'il parlait d'une keynote devant un amphi plein à craquer et retransmise dans l'amphi d'à coté. Ca, et les retours que j'ai eu sur le fait que ce que je faisais parlait aux gens, m'ont fait continuer.
Des Portes Logiques Pneumatiques en Bois
Une présentation issue d'un projet personnel, qui parle d'orgues de barbaries, de transistors, d'électronique, mais aussi de rater des choses, de procrastination, de trouver des choses qui marchent pour soi, de baby step, d'artisanat, d'esthétique de la technique...
Captation au BreizhCamp 2018 (youtube)
Conférences
- DevFest Toulouse 2017, format 20 minutes entre midi et deux
- BreizhCamp 2018, en keynote (35 minutes)
- MiXiT 2018 en format Alien, public plus orienté UX et Accessibilité
- THSF 2018, format 45 minutes, grand public
Fichiers de la présentation
Les fichiers sont disponibles sur github.
The OldMan Glitch
The OldMan Glitch est un glitch1 dans la première génération de Pokémon2 sur Gameboy. Dans cette présentation, effectuée entièrement sur un émulateur, je propose de montrer la manipulation tout en expliquant dans le détails ses mécanismes : de parler d'off by one error, d'underflow, de buffer overflow, de payload, d'injection de code et d'assembleur, tout ça en s'amusant à faire déconner Pokémon autant que possible.
Captation au DevFest Toulouse 2018 (youtube)
J'ai donné cette présentation en espérant faire prendre conscience à des développeurs ou développeuses que ne pas complètement abandonner la sécurité est à la fois possible, intéressant et utile. Et qu'un bug, ça peut être beau.
Conférences
- Meetup Toulouse JUG, lightning talk 25 minutes
- DevFest Toulouse 2018, format 40 minutes
- RivieraDev 2019, format 35 minutes
- Sunny Tech format 45 minutes
- THSF 2019, format 45 minutes, grand public averti
- Meetup Lyon Software Crafters Community, format 60 minutes avec discussion, à distance (twitch)
Fichiers de la présentation
La liste des outils, les sauvegardes, le script détaillé des manipulations et des explications, ainsi que la description du setup - c'est à dire tout ce qu'il faut3 pour rejouer la présentation chez soi ou la redonner - sont disponibles sur github.
Un état non voulu par les développeurs dans lequel on peut mettre de façon temporaire un jeu et qui permet d'obtenir des effets non prévus.
Pokémon Rouge et Pokémon Bleu, sortis en 1996 au japon et en 1999 en France.
A l'exception de la ROM
Des trucs et des machins qui aident à faire des choses
Cette présentation devait porter à l'origine sur la similitudes de topologie des problèmes que cherchent à résoudre un métier à tisser et ordinateur. C'est la première présentation que j'ai accepté de donner avant de l'avoir entièrement écrite ou réfléchie, et après quatre mois de rechercher et de travail, le sujet a beaucoup évolué.
J'y parle finalement de l'évolution des abaques de la mésopotamie à la machine de Babbage, en essayant montrer les mécanismes et de parler de certaines problématiques traversées pendant cet histoire qui résonnent toujours avec certaines questions que posent aujourd'hui l'informatique et le développement logiciel.
Captation au DevFest Toulouse 2019 (Si vous prenez de le temps de regarder cette présentation, merci de lire aussi ce twitt de complément.)
Je devais y parler aussi de métiers à tisser, mais l'impossibilité de trier parmi tout ce que j'aurais eu envie de raconter de ces quatre mois ne m'en ont pas laissé le temps.
C'est une présentation qui a été très frustrante (tellement peu de temps pour tant de travail et de choses à dire). J'ai fini par me réconcilier avec elle et j'y reviendrais peut être sous forme d'atelier quand les rencontres physiques redeviendront plus faciles.
Conférence
- DevFest Toulouse 2019, Keynote d'ouverture, 30 minutes.
Fichiers de la présentation
Les fichiers de la présentation n'ont pas été publiés, n'hésitez pas à me les demander s'ils vous intéressent.
Rust macros from a beginner point of view
This tracks what I understood writting my first real rust macro.
The goal was to simplify creating test data while writting a text adventure game framework of library in Rust.
The model is a simplifier first version of what I want achieve later.
It starts with just a Book
that contains Chapters
, the Chapters
have an Id
, their own text, plus a list of Choices
that associate some descriptions to the Id of the chapter that must be read if the player make that choice.
The current allows writting this:
#![allow(unused)] fn main() { let l = livre![ //. chapter_one_id: { // This is a chapter "text of chapter one", // This is the text of the chapter chapter_one_id: "Make choice one", // This is a choice chapter_two_id: "Make choice two", chapter_three_id: "Make choice three" }, chapter_two_id: { "texte du chapitre deux", chapter_two_id: "Make choice one" }, chapter_three_id: { "texte du chapitre trois", chapter_three_id: "Make choice one" } ]; }
instead of this:
#![allow(unused)] fn main() { let l = Livre { chapitres: HashMap::from([ ( "chapter_one_id".into(), Chapitre { texte: "text of chapter one".into(), choix: vec![ ("chapter_one_id".into(), "Make choice one".into()), ("chapter_two_id".into(), "Make choice two".into()), ("chapter_three_id".into(), "Make choice three".into()), ], }, ), ( "chapter_two_id".into(), Chapitre { texte: "text of chapter two".into(), choix: vec![("chapter_one_id".into(), "Make choice one".into()),], }, ), ( "chapter_three_id".into(), Chapitre { texte: "text of chapter three".into(), choix: vec![("chapter_three_id".into(), "Make choice one".into()),], }, ), ]), }; }
The original code is available on this version of the code.
development environnement
I used:
- the Rust By Example book
- the specification of the Macro By Example from the Rust Reference
- a nightly version of rust which allows the
trace_macros
feature:
$ rustc --version
rustc 1.84.0-nightly (03ee48451 2024-11-18)
the choice!
macro
I tried to start with a macro to generate Chapter
s, but this one hade two separate problems since the pattern for Choice
s and the text of the chapter where a bit different.
After some attempts, I simplified the problem by starting writting a macro for choices only.
It is used like this:
#![allow(unused)] fn main() { choice! { //. id_chapter_one: "Make first choice", id_chapter_three: "Make second choice", }; }
and produces a code Rust code eauivalent to:
#![allow(unused)] fn main() { vec![ ("id_chapter_one".into(), "Make first choice".into()), ("id_chapter_three".into(), "Make second choice".into()), ] }
truvc dont il faut parler :
stringify!()
- comment il arrive a trouver le type du
into()
et comment j'ai pu virer leString::from()
-la macro peut s'appeler avec des crochet ou des accolades aussi (q: peut on faire des matchings differents pour les trois cas ?)
Notes: demarrage d'un projet rust sur STM32F103 (BluePill)
Les tutoriaux et sessions de live coding donnent souvent l'impression que démarrer un projet avec une technologie ou un environnement qu'on ne connait pas est simple. Et pourtant, tout le monde rame. Des fois pendant des jours.
Si vous voulez voir comment j'ai mis un week-end entier à arriver à faire clignoter une LED en Rust la première fois, et ceci malgré le fais que j'en avait fait clignoter des dizaines en assembleur, en C ou en LUA, sur des Pic, des Atmels, des Cortex, des ESP et même sur un 68000, mes notes sont ici.
Apprendre WASM depuis ses spécifications [EN]
Un livre ou un tutorial sur l'utilisation d'une technologie apporte souvent une façon de faire sans forcément donner les éléments pour developper la sienne. En particulier les détails techniques qui ont conduit à ce choix. Quand ils y sont, c'est souvent pour justifier un choix plus que pour permettre d'en comprendre les limites et les aprioris.
Des fois, on peut aussi apprendre directement à partir de documentation de bas niveau et essayer de comprendre comment en faire quelque chose. Cette démarche fonctionne pour moi dans certains domaines (les microcontrolleurs, dans certaines mesure les langages de programmation).
Pour illustrer cette démarche, j'ai publié (originellement ici) mes premiers pas en WASM.
Why ?
tl;dr : because we can.
- Tutorials will give you a pre-owned view, and most of the time you won't even get it, you'll rather just learn to repeat pieces of code without developping the understanding to tell wether it is the good way, in your particular case, to do things nor why you do them that way.
- Learning from specification might help you understand how things work under the hood, and will force you to build your own understanding. Also, it will teach you to read the specification, then you won't hesitate consulting it when appropriate.
- By taking a simple problem and trying to implement a solution using only the specification, you will feel like you are trying to solve a puzzle, understanding what to do with information, clues, and question you find along the way.
- You may have fun.
Which specification ?
Web Assembly specification is available at https://webassembly.github.io/spec/
It gives the specification of both the execution environment and the thing that it will execute. Since I want to focus on the later, I will use fireFox (56.0.2 64bit) as en environment, and need access to the specification of the API that will help me run some WebAssembly in it.
It is found at https://developer.mozilla.org/en-US/docs/WebAssembly#API_reference
Take a starting point
Developers should be proud of their traditions and history. After reading https://en.wikipedia.org/wiki/%22Hello,_World!%22_program , we can agree that this is the first thing we want to do.
Let's read the documentation, skipping any part that does not answer to an actual question I have. My first question is "how do I send some webassembly code to the browser" ?
The answer should be in the MDN documentation. The second object in the documentation is WebAssembly.Module and and it is said to contain the "stateless WebAssembly code". Sounds good. Let's see how to get an instance of it. The first parameter of the constructor is :
bufferSource
A typed array or ArrayBuffer containing the binary code of the .wasm module you want to compile.
Ok, great. Hopefully this ".wasm module" is not tied to browser but is specified by the web assembly thing. Let look at the specification. Use navigator to seach for "module" in the index and we find : https://webassembly.github.io/spec/core/syntax/modules.html
WebAssembly programs are organized into modules, which are the unit of deployment, loading, and compilation. Ok, this is what we are looking for.
Now how to make one ? Just after the first paragph, there is a block of text starting with module ::=
. If you are not familiar with the notation, it might be time to read the conventions used in the specification and come back, but for now a basic understanding is enough : a module is made of types, funcs, tables, mems... all of which are mandatory and can have one or zero start.
Since we want to start, let's have a look at this start
:
The start component of a module optionally declares the function index of a start function that is automatically invoked when the module is instantiated, after tables and memories have been initialized.
Ok, we are probably in the right place. We know we have to make a module with at least those parts :
- types vec(functype)
- funcs vec(func)
- tables vec(table)
- mems vec(mem)
- globals vec(global)
- elem vec(elem)
- data vec(data)
- imports vec(import)
- exports vec(export)
Building an unpopulated module.
We could now try to make a func
, then build a funcs
'vec' (probably a vector ?) from it, and carry on until all the content of a module
is ready, or we can go the other way around : start making a module then populate it. Let's try the later.
The modules
page of the specification tells me what, from a logical point of view, is inside a module, but not how to build a module and actually get the .wasm file we need to give to the WebAssembly API in the browser.
Looking at the index on the right side, we see two top level chapter which are "Binary Format" and "Text Format". Having a look at the "Text Format", going to the part dedicated to "Modules", it describes, as expected, a text format to describe a module.
But remember what we have in the API:
bufferSource
A typed array or ArrayBuffer containing the binary code of the .wasm module you want to compile. It does not expect a text representation. Searching for "text" in this page gives no result, so I probably need to either have a look at the binary format, or find a tool to convert from text to binary format (which hopefully should be called an Assembler ?)
The second option seems the abvious one. But finding and installing the assembler won't be fun. I will probably need to compile it and, since I boot my computer under Windows this morning, I will probably need to install a toolchain before I can compile the assembler. It seems that for now at least, I will have more fun looking at the Binary Format.
We will, of course, skip the Conventions sub-chapter, and go directly to the last chapter : "Modules". The short text introduction is interesting : we basically will need to write one "section" for each record of a module, except for the "function" record that is split in two sections. We skip all the Indices part for now, and go to Sections :
Each section consists of:
- a one-byte section id,
- the u32 size of the contents, in bytes,
- the actual contents, whose structure is depended on the section id.
ok, easy. We then have a table of the section Id's, with a 0 Id-ed "custom" section (which we apparently can ignore), and eleven sections. We have nine mandatory records in the module, one having two corresponding sections, plus the opional "start" section. Eleven section. Everything is here, we should soon be able to write some code. Or opcodes. Bytes.
Binary formats often have headers and footers. It is a bit unexpected that we directly ran into the Sections. Looking at the index, we see that the last sub-chapter is about... modules !
The introduction text makes things easy to understand, and using the binary grammar we are told how a module is made from bytes !
It starts with 0x00 0x61 0x73 0x6D 0x01 0x00 0x00 0x00
(module version 1) and have sections that all can be empty (but still present) and no footer.
So we basically may have succeed writing our unpopulated module !
0x00, 0x61, 0x73, 0x6D, // Magic number, indicates this is a WebAssembly module
0x01, 0x00, 0x00, 0x00, // Version 1 of the WebAssembly binary format
Populating the module with empty sections
Since we need all the (non custom) sections to be present once, let's start with the first one : typesec
, which contains any quantity of functype
. This quantity will be zero for now, making things easyer. clicking on "typesec" we reach it's definition. It has the Id 1 and decodes into a vector of function types :
typesec::=ft∗:section1(vec(functype))⇒ft∗
From the binary grammar in the convention chapter, we have : x:B denotes the same language as the nonterminal B, but also binds the variable x to the attribute synthesized for B. and : Productions are written sym::=B1⇒A1 | … | Bn⇒An, where each Ai is the attribute that is synthesized for sym in the given case, usually from attribute variables bound in Bi. What does that mean ? That typesec will be made ofsection1(vec(functype))
(probably asection1
containing an array offunctype
) that we will name that we will nameft*
, and when this data will be loaded, the result will beft*
. Why some so complicated notation ? Because some of the data before the arrow might be used only to control the creation of the structure / object (e.g. : size, checksum, define nature of data...) but not be part of the output of parsing the data.
We already hade a look at the specification where it defines section1
:
sectionN(B)::=
N:byte size:u32 cont:B⇒cont
|ϵ⇒ϵ
The second alternative means that if B
is empty, then sectionN
is just empty. Great, nothing to do !
You can see that, if we hade a non-empty section, it would start with N (the section Id), then the size (of cont), then cont. But only cont (after the double arrow) will be produced when parsing the data.
Now all the sections that only contains a vector can be empty, which are they ? All but start
, which is optional. So our module should already be valid !
Can we test it ? We should just need to put our byte sequence into a file and load it using the API.
Testing a first module
According to the API, we should be able to instanciate a module from a TypedArray, let's try this in the JS console in FireFox :
var moduleBytes = new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00])
var myModule = new WebAssembly.Module(moduleBytes);
It works !! Does it really ? Try changing the version number from 0x01 to 0x02 and see if you still can instantiate a WebAssembly.Module :
CompileError: at offset 8: binary version 0x2 does not match expected version 0x1
:)
Adding a section
By looking at the JS API, it seems that we need to export something in order to be able to call it from the JS side :
WebAssembly.Module.exports()
Given a
Module
, returns an array containing descriptions of all the declared exports.
Then we probably need to have something in the Export
section, defined as this :
exportsec ::= ex*:section7(vec(export)) ⇒ ex*
export ::= nm:name d:exportdesc ⇒ {name nm,desc d}
exportdesc ::=
0x00 x:funcidx ⇒ func x
|0x01 x:tableidx ⇒ table x
|0x02 x:nameidx ⇒ name x
|0x03 x:memidx ⇒ memid x
So we want a not empty section, containing a vector of exports, the export seems to be a succession of a name and an exportdesc
, which is a function, table, name, or memory ID.
We want our JS to call one of our functions, so we need a funicdx which is a u32
(unsigned 32bit). And digging the specification in /Structure/Modules/Indices, we find :
Definitions are referenced with zero-based indices. Each class of definition has its own index space,
Now we need to understand what is the "index space" for the class funcidx. All I could find for now is :
The index space for functions, tables, memories and globals includes respective imports declared in the same module. The indices of these imports precede the indices of other definitions in the same index space.
If I understand correctly, it means that if I have no import, the function Id will be the (zero based) index of the function in the vector of the function section. Since I have only one function for now, I should be 0.
We'll build the function section later, for now is seems that the exportdesc
is rather simple : a 0 (to indicate we export a function), followed by another 0 (index of the function we will write) : 0 0
From this, we should be able to write the export
.
The export needs a name (nm
), which is probably the name under wich the function will be available in the Instance.prototype.exports
object on the JS side, and the exportdesc
we just encoded.
Let's see how to encode names in the spec binary format / values / name :
Names are encoded as a vector of bytes containing the Unicode UTF-8 encoding of the name’s code point sequence.
name::=b∗:vec(byte)⇒name
Easy ?
We can decide our function to be named "foo" ([102,111,111]
in UTF8), which is hasa length of 3, and voila! only terminal symbols (it mean we no longer have to digg, we can climb up now)
// Export Section
0x07, 0x07, // sectionId 7, 7 bytes
0x01, // content is vector of size 1
// export
// nm:name (which is a vect(byte))
0x03, // 3 bytes
102, 111, 111, // UTF8 for "foo"
// d:exportdesc
0x0, // it is a function
0x0, // funcidx of the first function in function section (if we have no import)
Ok, this might work. But we can not test for now because we do not have the function section but we use a funcidx.
You might have noticed that the u32's have been encoded as a single byte instead of the abvious 4. We'll come back to this later, but the spec in binary format / values / integer tells us that they are encoded in LEB128. All you need to know for now is that u32 smaller than 128 will be encoded in just one byte, saving space.
We can make a simpler exportsec by puting a 0-length vector as the content of the section, dodging the need for the function section for now. I will let you do this as an exercice, but once you add the section to the javascript array we made earlier, you may find this:
var moduleBytes = new Uint8Array([0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00, 0x07, 0x01, 0x00])
var myModule = new WebAssembly.Module(moduleBytes);
This works. If we replace the last 0x01
(size of the section) with 0x02
, we get:
CompileError: at offset 10: failed to start export section
So far, so good !
Adding the Type Section
Section7 (function exports) uses a funcidx, hence needs a function section.
Section3 (function section) just contains a vector of typeidx, hence needs type section.
Section1 (type section) contains a vector of functype's. And functype is rather easy now that we got used to the sepecification and its grammar :
functype::=0x60 t∗1:vec(valtype) t∗2:vec(valtype)⇒[t∗1]→[t∗2]
It's a 0x60 followed by two vectors of valtype describing the type of parameters and return type. We need no parameter (vector of size 0, just a 0x00
) and will return an i32
(we will just return 42
instead of "Hello, World!"
for now). The valtype
for i32's is 0x7F, so Section1 should look like this:
// Type Section
0x01, 0x05, // sectionId 1, 5 bytes
0x01, // vector of size 1, only one function type defined
0x60, // header for function types
0x00, // t1, zero-length vector because we need no parameter for foo()
0x01, 0x7F, // t2, return type is an array of length 1 containg id of i32
The section 1 must be before section 7 in the module, so we can test this :
var moduleBytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, // magic number
0x01, 0x00, 0x00, 0x00, // binary format version 1
// Type Section
0x01, 0x05, // sectionId 1, 5 bytes
0x01, // vector of size 1, only one function type defined
0x60, // header for function types
0x00, // t1, zero-length vector because we need no parameter for foo()
0x01, 0x7F, // t2, return type is an array of length 1 containg id of i32
// Export Section
0x07, 0x07, // sectionId 7, 7 bytes
0x00 //empty vector as content
])
var myModule = new WebAssembly.Module(moduleBytes)
This runs without error, and is more readable than earlier presentation :)
Adding the Function Section
Now that we have defined the type of the foo() function, we can define the function itself. We now have all the training we need to implement this specification :
funcsec::=x∗:section3(vec(typeidx))⇒x∗
Our implementation might be :
// Function Section
0x03, 0x02, // sectionId 3, 2 bytes
0x01, 0x00, // vector of size 1, with one typeId equal to 0
Our function seems to be defined, but we still have no code for it... The specification of the function section tells us why:
The locals and body fields of the respective functions are encoded separately in the code section.
We know what we have to do next :)
the Code Section
We know should be able to read the specification of the code section. It's a section with number 10, containing a vector of code.
A code
is the size of a func
followed by the func
,
which is a vector
of local
s followed by an expression
.
Local's are u32 values followed by a valtype, but we may not need locals for now. The structure / modules / function part of the docs tells us that :
The locals declare a vector of mutable local variables and their types. These variables are referenced through local indices in the function’s body. The index of the first local is the smallest index not referencing a parameter.
We want to return 42 for now, but it's a constant, we may not need locals yet.
Now we need to write the expression
part. It's nice how we just have to clicking
on the word "exp" on the documentation to be presented with the list of
numeric expressions
We should now read the documentation of every instruction, but we are lucky
because the first one is 0x41 and defines an i32 constant. 42 is still smaller
than 128, so we once again don't have to look at the LEB128 documentation
and can define our constant : 0x41 42
. I could not understand if I need some
opcode to return the constant, so I'll just try.
puting it all together
We now should be able to update our section7 (export session) with the function Id and the name of the function and the section10 (code)
var moduleBytes = new Uint8Array([
0x00, 0x61, 0x73, 0x6D, // magic number
0x01, 0x00, 0x00, 0x00, // binary format version 1
// Type Section
0x01, 0x05, // sectionId 1, 5 bytes
0x01, // vector of size 1, only one function type defined
0x60, // header for function types
0x00, // t1, zero-length vector because we need no parameter for foo()
0x01, 0x7F, // t2, return type is an array of length 1 containg id of i32
// Function Section
0x03, 0x02, // sectionId 3, 2 bytes
0x01, 0x00, // vector of size 1, with one typeId equal to 0
// Export Section
0x07, 0x07, // sectionId 7, 7 bytes
0x01, // content is vector of size 1
// export
// nm:name (which is a vect(byte))
0x03, // 3 bytes
102, 111, 111, // UTF8 for "foo"
// d:exportdesc
0x0, // it is a function
0x0, // funcidx of the first function in function section (if we have no import)
// Code Section
0x0a, 0x06, // sectionId 10, 6 bytes
0x01, // vector of 1 code (the implementation for foo() )
//code
0x04, // 4 bytes
// func
//locals
0x00, // vector size 0
//expr
0x41, 42, // opcode for "const 42"
0x0b // footer for expr
])
var myModule = new WebAssembly.Module(moduleBytes)
Now that the module is ready, we just need to get a new instance and call the exported function:
var myInstance = new WebAssembly.Instance(myModule, {})
myInstance.exports.foo()
Running this code in the console shows... 42 \o/
What did I learn ?
There were no really hard concept to grab (at least for me, having experience with reading datasheet for microcontrolers). The only mistake I made was to read the documentation a bit casually in the end, missing the 0x0b footer at the end of an expr (I lost about 20 minutes on this :) )
- I now can navigate rather easily in the specification
- I can read the specification (the grammar)
- I have a really better understanding of what is WebAssembly
What is left to do ?
- I still don't know how to return a variable or a string
- I only took a look at the binary format, not at the specification of the runtime
- I have no tryed to get JS objects and use them from WebAssembly to see what kind of hack could be done
- I have not installed a toolchain
- Making an Elm or Idris compiler would be fun :)
Atelier Introduction à Rust et à l'Embarqué
Cet atelier est complètement en cours de test et d'écriture. Il devrait être possible de commencer à le tester rapidement en petit groupe avec des personnes intéressées et prêtes à essuyer les plâtres.
Format
Cet atelier durera probablement trois jours. Pendant cette durée, les personnes participant assembleront une petite machine dessinant sur des notes adhésives de 78x78mm dont je ne citerais pas la marque.
Il se déroulera en petits groupes (quatre à cinq participants par animateur, un ou deux animateurs, soit entre trois et douze personnes).
Il contiendra trois éléments distincts :
- Une partie de présentation des bases nécessaires d'électronique et de Rust pour démarrer, proche d'une formation standard.
- Des points préparés à l'avance et donnés sur demande sur des sujets spécifiques.
- Une grande partie (au moins la moitié du temps) de programmation en pair ou en mob, dans laquelle les animateurs n'interviennent que pour débloquer, donner les bons pointeurs, ou proposer les points préparés.
Contenu
- Présentation des microcontrôleurs, de notions d'électronique, et de la machine.
- La couche d'abstraction du matériel proposée par Rust
- Flasher un firmware et debugger un STM32F103[^1] avec un STLink V2, OpenOCD et GDB
- Notions de base de programmation en Rust (fonctions, traits, monomorphisation, références, borrow checker / lifetimes, pattern matching, await / asynch )
- Navigation dans les différentes documentations (Datasheet du microcontrôleur, guide du coeur Cortex, Rust Book, Embedded Rust Book, documentation de l'API des crates utilisés, code source de Rust, et peut être le Rustonomicon)
Le but est que chaque personne participant ait une machine dessinant des formes simples (comme un spirographe) à la fin de la seconde journée, et la possibilité et l'envie de continuer après l'atelier les prolongements imaginés et commencés pendant la dernière journée.
Public visé
Il s'adresse à des personnes sachant déjà programmer, ayant si possible deja quelques années de pratique de Java ou du C, et voulant démarrer un premier projet Rust et/ou voulant acquérir des notions de programmation sur microcontrôleur, par exemple pour mieux travailler avec une équipe embarquer s'ils doivent s'occuper du backend d'un projet IoT.
mdbook avec >GitLab CI
Ce site est écrit en CommonMark, la gestion de version est fait avec Git et le rendu HTML est fait avec mdbook, qui génère un site GitLab Pages static qui est aussi déployé via un FTP chez mon hébergeur.
La description du build est presque entièrement gérée par le fichier .gitLab-ci.yml
dans la racine du projet. C'est le fichier qui configure les Jobs exécutés par GitLab CI.
Voici son contenu actuel (disponible comme l'intégralité des sources du site sur GitLab
stages:
- deploy
pages:
stage: deploy
environment:
name: site public
url: https://www.tregan.fr
image: rust:latest
variables:
CARGO_HOME: $CI_PROJECT_DIR/cargo
before_script:
- export PATH="$PATH:$CARGO_HOME/bin"
- mdbook --version || cargo install --debug mdbook
- apt-get update -qy
- apt-get install -y lftp
script:
- mdbook build -d public
- lftp -e "open $FTP_SERVER; user $FTP_USERNAME $FTP_PASSWORD; mirror -R public/ $FTP_DEST_DIR; bye"
only:
- master
artifacts:
paths:
- public
cache:
paths:
- $CARGO_HOME/bin
Voici à quoi servent chaque ligne :
stages:
- deploy
On définit une liste ordonnée de stages. Un stage permet de regrouper un ensemble de jobs (définis juste après) qui pourront s'exécuter en parallèle. Une fois l'ensemble des jobs d'un stage finis, le stage suivant est traité. Nous n'avons pas de jobs parallélisables et définissons donc un seul stage. On aurait pu sauter cette étape, puisqu'en l'absence de définition il y a une liste par défaut (.pre
, build
, test
, deploy
et .post
).
pages:
stage: deploy
On définit ensuite un Job appelé pages
. Ce nom de job spécial fait que s'il existe un répertoire nomé public
et que l'on a définit un artéfact pointant sur ce répertoire, son contenu sera publié sur github pages
environment:
name: site public
url: https://www.tregan.fr
Ce job déploie mon site public, visible à l'adresse https://www.tregan.fr. En donnant un nom et une URL à GitLab CI, celui-ci permettra de voir tous les déploiements qui ont été effectués en se rendant sur la page GitLab du projet, dans opérations -> Environments. L'URL permet de se rendre sur le site en cliquant sur la première icone de la ligne du tableau (Open live environment). D'autres options permettent de préciser à GitLab CI comment démarrer, stopper ou redémarrer un environnement et il devient possible de le faire depuis cette page.
image: rust:latest
le build sera construit à partir de l'image Docker rust:latest
qui fournit un environnement de compilation Rust à jour.
variables:
CARGO_HOME: $CI_PROJECT_DIR/cargo
On définit une variable qui donne le nom de chemin de Cargo sur cette image.
before_script:
- export PATH="$PATH:$CARGO_HOME/bin"
- mdbook --version || cargo install --debug mdbook
- apt-get update -qy
- apt-get install -y lftp
Avant le build:
- On ajoute le répertoire de Cargo au Path
- Si mdbook n'est pas installé, on l'installe avec Cargo.1
- On installe lftp (qui servira a uploader les fichier sur le serveur FTP du serveur Web, inutile si on déploie uniquement sur GitLab Pages)
script:
- mdbook build -d public
- lftp -e "open $FTP_SERVER; user $FTP_USERNAME $FTP_PASSWORD; mirror -R public/ $FTP_DEST_DIR; bye"
La phase de build elle même :
- On génère le HTML du livre avec mdbook, en s'assurant de mettre la sortie dans
public/
- On envoie avec LFTP le contenu du répertoire
public
. Les variables suivantes sont définies dans le projet GitLab, dans settings -> CI/CD -> Variables (expand) -> Add Variable , en s'assurant des les mettres en protected (gestion des droits) et masked (effacement des logs quand c'est possible)- FTP_SERVER : Adresse du serveur FTP (e.g.
ftp.monsite.fr
) - FTP_USERNAME : Nom du compte FTP (e.g.
webmaster
) - FTP_PASSWORD : Mot de passe du compte FTP (e.g.
a3eRrttyf35WxCvBdRy44fS
) - FTP_DEST_DIR : Chemin du FTP dans lequel copier les fichiers (e.g.
www/
)
- FTP_SERVER : Adresse du serveur FTP (e.g.
only:
- master
On n'effectue le build que si le code a été poussé sur la branche master
artifacts:
paths:
- public
On précise qu'il y a un livrable constitué du contenu du répertoire public
. Cela permettra de récupérer le livrable sur la ligne du pipeline correspondant à l'éxecution du job sur la page GitLab du projet dans CI/CD -> Pipelines, mais aussi ce confirmer qu'on veut une mise en ligne sur GitLab Pages dans le cas spécial du job nommé pages
. Les artifacts servent aussi à passer le résultat d'un stage au stage suivant, mais nous ne l'utilisons pas ici.
cache:
paths:
- $CARGO_HOME/bin
Enfin, on précise que le répertoire $CARGO_HOME/bin
et son contenu peuvent être conservés d'une execution de job à l'autre. Si c'est le cas, la ligne du before_script mdbook --version || cargo install --debug mdbook
verra mdbook --version
retourner 0
et n'exécutera pas le cargo install -- debug mdbook
On pourrait se content de télécharger une version pour l'architecture utilisée pour éviter de télécharger et compiler mdbook et ses dépendances. Cependant, recompiler assure la compatibilité du script quelle que soit l'architecture, et dans les lignes suivantes nous ferons en sorte que le binaire de mdbook soit mis en cache, ce qui est possible puisque nous sommes dans la phase before_script
Première pull request
Un ami posait une question sur l'utilisation d'un logiciel libre. C'est un logiciel de dessin i(krita)et un des outils affichait des informations sous sa main lorsqu'il l'utilisait avec un une tablette graphique qui fait aussi écran. Il voulait savoir si on pouvait déplacer cet affichage.
Les sources du logiciel étant disponibles librement, je suis allé y voir si je trouvais la réponse dedans. J'en trouvais une fonctionnelle mais pas très pratique.
Mes contributions au logiciel libre sont assez minces. Je me suis entrainé à une époque à faire du rapport de bug, et je conseil l'exercice à tout le monde. J'ai bien donné deux ou trois coup de mains sur l'utilisation mais celà s'arrêtait là. Je vais essayer de voir si j'arrive à proposer un modification du logiciel, et en profiter pour tracer le parcours.
Un point qui risque d'être délicat, c'est que malgré un certaine expérience en programmation, je n'ai quasiement jamais fait de C++ (pas plus de deix heures il y a plus de vingt ans), et meme utilisé de langage sans garbage collector (c'est à diore géré à la main les allocations ou désalocations de mémoire. Je continue bien à faire un peu d'assembleur pour le plaisir, mais en gérant la mémoire uniquement sur la pile ou en allocation statique.)
Naviguer dans une base de code inconnue.
L'idée étant d'aller voir dans le code source, il fallait d'abord le récupérer. Une recherche rapide de "krita source code" donne directement un repository github dont j'apprends plus tard qu'il n'est pas le bon mais qui a suffit pour commencer.
Premier probleme : par où démarrer la rechercher. Deux grosses options :
- chercher à comprendre en gros l'organisation du code et descendre jusqu'au bon endroit
- faire une recherche pour trouver une méthode ou une classe qui a l'air de parler de mon sujet.
Je tente la seconde, et cherche le terme qui était dans la question d'origine ("color picker"). Je ne trouve pas grand chose (un seul résultat, dans un fichie d'entête KisScreenColorSampler.h
). Ce ne doit pas être le bon mot. Je vais chercher dans la doc de Krita, et le second résultat me confirme que l'outil en question s'appele le Color Sampler. (C'est la référence à la touche control
qui est aussi dans le twitt qui me rassure).
Bref, je retrouve rapidement le pendant de mon header et commence à regarder si le nom d'une des méthodes m'inspire. sampleScreenColor
semble pas trop mal, c'est peut etre à ce moment que l'information mal placée est afichée ou mise à jour ? Ca n'a pas l'air. grabScreenColor
? Non plus, même si ca commence à parler de coordonnées. Je vois d'une part des mise à jour de label text (l'outil n'affiche pas de texte, donc a moins que le code de l'outil affiché sur la feuille ne soit mélangé avec ud code de l'affichage des pallettes de couleurs sur le coté, il y a un probleme), d'autre part j'ai clairement trouvé une fonction nommée setCurrentColor
mais ni elle, ni le code qui ne l'appele ne semble faire de mise à jour de l'IHM.
Un truc important quand on navigue dans une base de code inconnue, c'est de savoir jusqu'où on pense devoir s'entêter. Là c'est probablement le moment de me dire que j'ai fais fausse route et de vérifier des choses. Le fichier que j'ai ouverts s'appèle KisScreenColorSampler.cpp
, est-ce qu'il y a d'autres candidats ? Je refais la recherche dans Github et trouve un kis_tool_colorsampler.cc
référencé dans CMakeList.txt
, puis un kis_tool_colorsampler.h
. En fait il y a un bouton "go to file" dans github plus pratique. Je tape ColorSampler
je trouve le bon fichier.
Je reprends le meme genre de scrutage que dans le fichier précédant, et je trouve une méthode KisToolColorSampler::activatePrimaryAction()
, qui elle même fait un m_helper.updateCursor(!m_config->sampleMerged, m_config->toForegroundColor);
J'essaie de trouver ce qu'est ce m_helper
qui semble être définit ou référencé en début de fichier :
KisToolColorSampler::KisToolColorSampler(KoCanvasBase *canvas)
: KisTool(canvas, KisCursor::samplerCursor()),
m_config(new KisToolUtils::ColorSamplerConfig),
m_helper(dynamic_cast<KisCanvas2*>(canvas))
Je ne comprends pas trop la notation. Pas trop grave, le but là est de trouver où est l'info, et de la comprendre après. Pas de comprendre tout le code. Je cherche ce m_helper
dans toute la base de code... avant de comprendre que bien sur, il est défini dans le fichier .h
correspondant à mon fichier .cpp
. Je trouve son type, et donc dans le fichier correspondant me dit que la methode updateCursor fait peut être un truc louche en utilisant le curseur pour afficher l'info. Je parcours un peu le code, arrive à une réfrence à la classe kis_cursor
qui a l'air de fournir les representations de curseurs, et à ce code :
QCursor KisCursor::samplerLayerForegroundCursor()
{
return load("color-sampler_layer_foreground.xpm", 8, 23);
}
qui fait référence au fichier .xpm
que je ne trouve pas dans github... Je clone le repository en local (270Mo!) et la trouve le fichier... qui ne contient qu'un bête curseur de souris sans rien pour afficher la couleur selectionnée au mauvais endroit... fausse route !
Allez, pas grave, on revient un peu plus haut, et dans le SamplerHelper
cette fois je trouve quelque chose qui a l'air beaucoup plus prometteur : colorPreviewDocRectImpl
.
Je n'ai toujours rien compris au code, qui semble lisible mais sans commentaire pour dire à qui servent les classes, je n'ai pas cherche de document de vue d'ensemble, j'essaie juste de trouver où est le code qui dit ou est affiché cette information mal placée, à partir de quoi j'essaierai de comprendre des choses. Je ne suis pas certain de comprendre la première ligne, mais la deuxième commence à carrément ressembler à ce que je cherche :
KisConfig cfg(true);
const QRectF colorPreviewViewRect = cfg.colorPreviewRect();
Il existe un fichier KisConfig.cpp
, je reprends ma lecture des signatures. J'apperçois au possage un m_cfg
, je suppose que c'est hérité ou importé ailleurs ? Ou alors c'est la variable privée et le KisConfig cfg(true)
plus haut déclare une variable cfg
en appelant le constructeur de KisConfig
? Bref, j'y connais rien en C++, faudra que je lise un peu de doc...
Et là, dans activateDelayedPreview
, les choses commencent à se préciser !
m_d->showPreview = true;
Bon, je commence à m'éparpiller et a essayer de comprendre des choses trop tôt. Revenons à la ligne la seconde ligne, celle qui semblait simple et évidente : const QRectF colorPreviewViewRect = cfg.colorPreviewRect();
Si je cherche ce colorPreviewRect()
dans KisConfig.cpp
, l'implémentation est courte :
QRect KisConfig::colorPreviewRect() const
{
return m_cfg.readEntry("colorPreviewRect", QVariant(QRect(32, 32, 48, 48))).toRect();
}
Il semble qu'il aille chercher une valeur nommée colorPreviewRect
dans un truc de configuration, et prenne une valeur par defaut s'il ne la trouve pas. Je pourrais aller vérifier cette hypothese en regardant le code de readEntry
, mais je pense commencer à être suffisemment proche de la solution pour commencer à avoir envie de comprendre un peu le contexte locale. Je fais une recherche sur internet pour voir où est-ce que Krita sauve sa configuration (j'ai pas trovué de fichier ~/.krita
), et découvre le fichier ~/.config/kritarc
(et kritadisaplyrc
).
J'essai à tout hasard d'ajouter dedans le nom de ma variable de configuration. Apparemment on peut mettre plusieurs valeurs séparées par des virgules, et je tente donc d'ajouter :
colorPreviewRect=100,200,100,200
Je relance Krita et... Victoire ! La preview de la couleur s'affiche n'importe où et avec une taille différente. J'essaie des valeurs négatives, j'arrive à positionner à un endroit ou Kholo arrivera à voir la couleur, je lui donne le truc sur Discord, et c'est deja une première contribution au logiciel libre : j'ai dépanné un utilisateur :)
Dans sa question sur Twitter, Kholo avait pingué l'équipe de Krita. Je donne ma réponse pour voir s'ils la valident ou s'ils ont mieux. J'efface le twitt et le re-redige de façon un peu mieux construite, au cas où ca puisse servir à quelqu'un d'autre :
Looking at the code, I found you can add :
colorPreviewRect=-100,-100,50,50
in ~/.config/kritarc
you probably want to add it as last line of the file, since the last section is [tool_color_sampler]
On discute un peu avec Kholo de l'utilité de ce changement de position, on vérifie qu'on ne trouve pas l'option dans l'IHM, de si c'est un endroit ou ce serait "rentable" pour l'équipe de dev de Krita de mettre des efforts (a mettre dans en balance avec tout le boulot qu'il y a à faire sur le logiciel, même s'il est deja très très utilisable). Comme Kholo m'a dit qu'il avait essayé de lire le code mais avait abandonné finalement très prêt de la solution, et comme j'essaie d'enjoindre les utilisateurs de logiciels libre à apprendre à contribuer en faisant des bugs reports et que j'ai moi meme trouvé l'exercice intéressant... je commence à me dire que je pourrais essayer d'ajouter l'option dans l'IHM.
Contact
- Twitter: @Twitter (DM ouverts mais le système de filtre de twitter n'est pas optimal)
- Mail: fabien at tregan point fr
Pour suggérer des corrections ou améliorations sur ce site, une possibilité est d'utiliser le système de pull request ou issues de gitlab. Mais si Twitter ou le Mail sont plus simple pour vous, n'hésitez pas.
Information sur les cookies
Il n'y en a pas.
Le local storage est utilisé pour sauvegarder le thème1 choisi. Cette information reste sur votre navigateur et n'est jamais envoyée au serveur.
Cliquer sur le pinceau en haut à gauche des articles.