Sam & Max » linux http://sametmax.com Du code, du cul Sat, 07 Nov 2015 10:56:13 +0000 en-US hourly 1 http://wordpress.org/?v=4.1 Désactiver un service dans Ubuntu 14.04 11 http://sametmax.com/desactiver-un-service-dans-ubuntu-14-04/ http://sametmax.com/desactiver-un-service-dans-ubuntu-14-04/#comments Tue, 25 Aug 2015 11:25:59 +0000 http://sametmax.com/?p=16801 Quand on installe un service, Ubuntu le lance automatiquement, et installe de quoi le faire démarer à chaque boot de la machine.

Si c’est votre machine de dev, vous ne voulez peut être pas qu’Apache, Nginx, Redis, MySQL, ElasticSearch, Solr, Supervisor, Postgres, Docker, et MongoDB soient up à chaque fois tous en même temps alors que vous voulez juste vous palucher sur youjizz.

La plupart des scripts de démarrage sont dans /etc/initd.d:

ls /etc/init.d
acpid           brltty             grub-common  lpd         pppd-dns      resolvconf   skeleton           unattended-upgrades
anacron         console-setup      halt...

Mais ces scripts sont lancés parce qu’ils sont symlinkés dans un des répertoires /etc/rcX:

ls /etc/rc*.d/*apache*
/etc/rc0.d/K80apache2  /etc/rc5.d/K80apache2  /etc/rc6.d/K80apache2

Ouai, j’ai encore des clients qui utilisent apache. Certains utilisent même Tomcat.

Pour désactiver le démarrage automatique, vous pouvez essayer bourinement la suppression des liens :

sudo rm $(ls /etc/rc*.d/*apache*)

Ça marche.

Solution plus pratique, utiliser la commande update-rc.d, qui n’est pas faite pour l’utilisation par les humains, mais fuck it :

sudo update-rc.d -f apache2 remove

Ça supprime aussi tous les liens. sudo update-rc.d -f apache2 disable devrait le faire moins bourinement mais ne marche pas sur ma machine.

Pour inverser la tendance :

sudo update-rc.d apache2 defaults

(ou enable si par miracle ça marche pour vous)

Enfin, si le cœur vous en dit:

sudo update-rc.d -f apache2 remove
sudo sysv-rc-conf

Vous donnera une joli interface curse pour le faire plus visuellement.

]]>
http://sametmax.com/desactiver-un-service-dans-ubuntu-14-04/feed/ 11
Le don du mois 8 http://sametmax.com/le-don-du-mois/ http://sametmax.com/le-don-du-mois/#comments Mon, 08 Jun 2015 18:01:27 +0000 http://sametmax.com/?p=16360 l'adoption de systemd par défaut, ils vont avoir besoin de ressources pour réaménager la caverne à trolls.]]> J’utilise Ubuntu au quotidien, et j’ai déjà donné au projet, mais les dernières décisions prises par l’équipe de la distrib ne me paraissent pas toujours judicieuses.

Aussi, afin de contribuer à Ubuntu, mais aussi à de nombreux autres projets, le don du mois sera pour le projet Debian, qui est à la racine de nombreuses distribs, dont Ubuntu. Les bénéfices se feront donc en cascade.

L’équipe de Debian a su garder un cap en terme de qualité et de stabilité au cours des longues années de son existence, et sur un projet libre de cette taille, c’est un exploit.

$50 partent donc pour eux. Avec l’adoption de systemd par défaut, ils vont avoir besoin de ressources pour réaménager la caverne à trolls.

]]>
http://sametmax.com/le-don-du-mois/feed/ 8
Écouter sur le port 80 sans être root 16 http://sametmax.com/ecouter-sur-le-port-80-sans-etre-root/ http://sametmax.com/ecouter-sur-le-port-80-sans-etre-root/#comments Wed, 28 Jan 2015 13:47:08 +0000 http://sametmax.com/?p=15817 Sous beaucoup d’OS, tous les ports d’un nombre inférieur à 1024 ne peuvent pas être utilisés par des processus sans avoir les privilèges administrateurs. Néanmoins, on a pas vraiment envie que son app bricolée un lendemain de cuite soit lancée en root, pour que la moindre faille de sécurité donne l’accès total à son système.

Beaucoup de logiciels se lancent en root, et relâchent leurs privilèges a posteriori. C’est ce que faisait Apache a une époque (peut être le fait-il toujours, j’ai pas cheché). Nginx lui, lance un processus racine en root, et spawn des enfants avec un utilisateur normal.

Mais nous, on a pas la foi de se faire chier à faire ça, donc généralement, on met nginx en front et il gère ça pour nous.

Sauf que, parfois, on a pas envie de mettre 40 couches devant notre app. Par exemple, si on utilise crossbar.io (ouai, j’ai encore réussi à le placer \o/), le logiciel est clairement capable d’être en front tout seul.

Bonne nouvelle, sur les Linux modernes, les exécutables peuvent avoir des permissions avancées, comme “pourvoir changer l’horloge système” ou “empêcher la mise en veille”. Ces permissions sont changeables avec l’outil setcap.

Sous Ubuntu, ça s’installe avec :

sudo apt-get install libcap2-bin

Puis on choisit l’exécutable à qui on veut donner nos nouvelles permissions. Dans mon cas, le Python du virtualenv de mon projet :

$ pew workon super_projet
$ which python
/home/sam/.local/share/virtualenvs/super_projet/bin/python

Je check si il a pas déjà des permissions (normalement non) :

$ sudo getcap `which python`

Nada. Good.

Un petit coup de man setcap nous liste les permissions utilisables, et on peut voire que CAP_NET_BIND_SERVICE est la permission qui permet d’autoriser un exécutable à binder n’importe quel port.

On rajoute les permissions :

sudo setcap cap_net_bind_service=+ep `which python`

On check que les permissions ont bien été ajoutées :

$ sudo getcap `which python`
/home/sam/.local/share/virtualenvs/super_projet/bin/python = cap_net_bind_service+eip

On a rajouté avec le + la permission cap_net_bind_service pour les cas e et p qui correspondent aux premières lettres de ces définitions :

Permitted (formerly known as forced):
    These capabilities are automatically permitted to the thread, regardless of the thread's inheritable capabilities. 

Inheritable (formerly known as allowed):
    This set is ANDed with the thread's inheritable set to determine which inheritable capabilities are enabled in the permitted set of the thread after the execve(2). 

Effective:
    This is not a set, but rather just a single bit. If this bit is set, then during an execve(2) all of the new permitted capabilities for the thread are also raised in the effective set. If this bit is not set, then after an execve(2), none of the new permitted capabilities is in the new effective set.

Et je n’ai absolument rien compris à celles-ci, je sais juste que ça marche.

Voilà, maintenant tout ce que vous lancez avec le Python de ce virtualenv peut se binder au port 80.

Si vous n’aimez pas l’idée de donner cette permission à tout un Python, il existe une alternative : rediriger tout ce qui rentre sur le port 80 vers un autre port.

Pour ça on peut utiliser un autre soft :

sudo apt-get install socat

Par exemple, balancer tout le trafic du port 80 vers le port 8080 :

socat tcp-listen:80,fork,reuseaddr tcp:localhost:8080

Et pouf, votre appli qui écoute sur le port 8080 reçoit le trafic du port 80.

C’est plus simple, mais il faut le faire pour chaque port, et s’assurer que la commande est bien lancée au démarrage du serveur.

]]>
http://sametmax.com/ecouter-sur-le-port-80-sans-etre-root/feed/ 16
Black awk down 8 http://sametmax.com/black-awk-down/ http://sametmax.com/black-awk-down/#comments Tue, 06 Jan 2015 10:26:20 +0000 http://sametmax.com/?p=10493

Ceci est un post invité de foX posté sous licence creative common 3.0 unported.

Introduction (sans douleur)

Qui n’a jamais rêvé d’avoir un shell Unix un peu plus pythonic ? Les oneliners en sed et Awk bien chiadés, c’est l’apanage des grands barbus en sandales et ça déchire, mais ça reste cryptique et la manipulation de liste et de chaînes de caractères est tout de même limitée. On peut dégainer perl en one line, comme ça c’est encore plus puissant mais moins lisible…

Et python dans tout ça ? En oneliner, ça craint, car le principe d’indentation ne facilite pas les choses et on finit avec une imbrication de bazar de parenthèses illisible.

La solution ? Pour se faire plaisir : une bonne pyp. Le bidule s’installe avec… pip (c’est une méta pipe).

pip install pyp

pyp (Python Power at the Prompt) va enchanter votre shell unix et vous envoyer au 7ème ciel. Petit tour d’horizon de la merveille.

Pyp et les strings

C’est la base quand on bricole des oneliners en shell, tripoter des strings.

ls *JPG | pyp "'mv', p, p.replace('JPG', 'jpg')"

Revoyons l’action au ralenti :

  • ls *JPG va nous lister tous les fichiers qui finissent par JPG dans le dossier courant.
  • Ensuite on envoie cela dans pyp avec un pipe Unix. Celui-ci va composer une ligne de commande qui est la concaténation d’une commande unix (mv pour renommer un fichier), ce qu’on aura reçu en entrée (qui s’appelle par convention “p”) et une version modifiée de p où l’on remplace JPG par jpg.

On voit ici que l’on utilise la méthode replace de la classe str python. On peut utiliser n’importe quelle méthode de str : lower, upper, title, strip(tease), count etc. Avec des petits bonus comme refindall(re) :-)

Ça donne quoi ma brave dame ? Et bien si on avait des fichiers appelés porn1.JPG et ass.JPG notre jolie commande renverrait :

mv porn1.JPG porn1.jpg
mv ass.JPG ass.jpg

Ok, bien gentil me direz-vous, mais qu’en fait-on ? Et bien ou l’on pipe ça dans le shell ( | sh) ou alors on passe l’argument -x (ou –execute) à pyp ou directement exécuter le résultat.

ls *JPG | pyp "'mv', p, p.replace('JPG', 'jpg')" | sh
# ou bien
ls *JPG | pyp -x "'mv', p, p.replace('JPG', 'jpg')"

Pourquoi on aime bien python ? Car il slice le bread comme un chef. Pyp aussi, oeuf corse, quelques exemples :

echo "sam-et-max" | pyp "p.split('-')[2]"
# max
 
# La même chose en plus court. Le m, comme minus, sous-entend qu'on split sur "-"
echo "sam-et-max" | pyp "m[2]"
# max
 
echo "sam-et-max" | pyp "m[0], m[2]"
# sam max

Comme vous avez vu, il y a un petit raccourci sympa avec m (comme minus) au lieu de p. C’est pas fini, vous en avez d’autres : d (dot) pour un point, w (whitespace) pour une espace, u pour underscore, s pour slash… Ils ont pensé à tout.

Des pyp à la chaîne

On tout de suite envie de remettre le couvert avec une autre pyp. C’est possible et même encouragé. Le symbole utilisé est le pipe unix | dans une commande pyp. Ce n’est donc pas un pipe du shell mais un pipe interne de pyp. Vous suivez ? Un petit exemple :

echo "sam et max" | pyp "w[0] | 'avant : ', o, 'après :', p"
# avant :  sam et max après : sam

Le p représente toujours le paramètre courant. Donc dans la seconde partie du pipe, c’est le premier élément de la liste issu du split de la chaîne “sam et max” sur l’espace (w comme whitespace). Vous suivez toujours ? Oui, bon. Et donc le o ? Et bien c’est le paramètre d’origine non modifié. Et il y a h (comme history ou comme hâlibi !) pour avoir le paramètre p à toutes les étapes de la chaîne de pipe… Beaucoup de puissance on vous dit !

On peut aussi rouler^w faire des join facilement avec les mêmes raccourcis. Par exemple on split sur les espaces puis join sur slash :

echo "sam et max aiment le lourd en fin de soirée" | pyp "w[:6]|s"
# sam/et/max/aiment/le/lourd

Une liste de pyp avec une belle pp

S’il y a un bien un truc qui envoie du bois avec python, c’est la gestion des listes. Pyp n’est pas en reste. C’est pp qui contient les listes. En avant :

ls | pyp "pp[3]"
# Affiche le troisième fichier

On peut utiliser toutes les méthodes standards de la classe list (sort, count etc.) plus quelques bonus :

# Équivalent de | sort | uniq

ls | pyp "pp.uniq()"
 
# Additionne tous les PID des process de machine.
# Ça ne sert à rien, mais c'est un exemple hein ?
ps -ef | pyp "w[1] | int(p) | sum(pp)"
 
# Voilà qui est plus utile. 
# Au passage, on note que la ligne d'en-tête est ignoré et ne fait pas planter pyp.
# Malin le lapin.
df -m | pyp "w[1] | int(p) | sum(pp)"

pyp au naturel

Pour compléter les fonctions python, pyp propose quelques fonctions maisons tout à fait sympathiques. Florilège :

echo "Sam Bill Max" | pyp "p.kill('Bill')"  # quand c'est pas bill, c'est kenny...
# Sam  Max
 
echo 124 | pyp "(int(p)/2+30)"
# 91
 
ls  | pyp "n, p"
# Liste des fichiers avec un numéro croissant devant
 
echo /home/fox/video/gangbang.avi  | pyp "'Dir='+p.dir, '\nFile='+p.file, '\nExt='+p.ext" # basename, dirname et ses amis
# Dir=/home/fox/video 
# File=gangbang.avi 
# Ext=avi
 
# On définit le séparateur de liste comme l'espace, 
# puis on regroupe la liste par deux élément
# et enfin on fait un join sur le caractère tiret (minus)
echo "1 2 3 4 5 6" | pyp "pp.delimit(' ') | pp.divide(2) | m"
# 1-2
# 3-4
# 5-6
 
ps -ef | pyp "w[1] | int(p) | max(pp)" 
# (le PID le plus grand)

pyp en intention ou intention de pyp ?

Il ne faut pas se gêner, on peut utiliser les excellentes listes en intention de Python. Voici tous les PID impaires :

ps -ef | pyp "w[1] | int(p) | [i for i in pp if i%2] | p "

L’industrie de la pyp c’est un truc de macro

Les bonnes choses, ça se garde, alors voilà :

ps -ef | pyp "w[1] | int(p) | [i for i in pp if i%2] | p " -s odd  # on enregistre la macro 'odd'
 
ps ef | pyp odd # c'est aussi simple que cela ;-)

pyp sur grand écran

Cette petite pépite est développée par Sony Pictures Imageworks pour ses besoins internes. Le développement n’est pas très collaboratif (un commit par release…) mais reste ouvert aux contributions externes. C’est sous une licence de type BSD.
Les loulous ont fait une jolie vidéo de tutorial très bling bling. C’est l’avantage de bosser dans le milieu :

Et bien plus encore…

Cet outil merveilleux possède encore des fonctionnalités qui vont vous le faire adorer comme son mode interactif avec une coloration syntaxique sympathique, la gestion de l’historique dans le pipe et beaucoup d’autres. Adieu sed, awk, uniq, sort et con sort, vous étiez mes premiers amours, mais pyp vous chasse dehors.

Ce n’est pas la seule ni la première tentative de faire la peau d’awk avec python. Sam avait déjà écrit un article sur une autre solution aussi appelée pyped.

C’est donc parfait ?

Oui, bien entendu. Aucun défaut, aucun bug. Ok… voici les limites :

      Perf de daube. Sur des petits volumes (quelques milliers de lignes) ça le fait. Au delà, ça rame. Quelques patch sont proposés pour diviser par trois le temps de traitement :-)
      Conso mémoire obcène. Idem, pour des petits volumes on s’en fout, mais sinon ça peut rapidement chiffrer. Pourquoi ? Le machin ne fonctionne pas en stream, tout est gardé en mémoire et à chaque étape de transformation (pour permettre d’accéder à l’historique).
      C’est installé sur votre pc, mais pas partout, contrairement à ce bon vieux awk.

Amusez vous bien, celui qui fait la plus jolie commande pyp dans les commentaires aura le droit à un bisous.

]]>
http://sametmax.com/black-awk-down/feed/ 8
Explication de code : python-mss 9 http://sametmax.com/explication-de-code-python-mss/ http://sametmax.com/explication-de-code-python-mss/#comments Sat, 22 Feb 2014 14:19:02 +0000 http://sametmax.com/?p=9219 envoyez nous les codes que vous ne pigez pas". Celle-ci est un peu particulière.]]> Ca faisait longtemps qu’on avait pas eu une petite explication de code dans le cadre de notre politique “envoyez nous les codes que vous ne pigez pas“.

Celle-ci est un peu particulière.

D’abord parce qu’elle traine dans la boîte mail depuis un siècle ou deux. Je pense que l’auteur de la demande n’en a plus besoin…

Ensuite parce que le code est assez complexe, notamment à cause de l’utilisateur d’API C. Donc si vous n’avez pas des notions de C, vous n’allez rien piger. En effet je ne vais pas expliquer les bases de C ou de Python, ce n’est pas un tuto, donc il me faut des prérequis. Je vous invite quand même à vous rafraichir la mémoire en utilisant notre introduction au module ctypes car il est massivement utilisé dans ce code.

Exceptionnellement, je ne vais pas pondre de version alternative car :

  • le code est très très long, et j’ai déjà passé mon samedi matin à écrire cet article.
  • je n’ai pas de mac pour tester cette partie.

Comme d’habitude, je vais faire des remarques parfois critiques sur le code, mais mon intention n’est bien entendu pas de faire du tord à l’auteur. C’est pédagogique pour les lecteurs. D’ailleurs ce bout de code est assez impressionnant et a du demander des heures et des heures de travail tant au niveau du code que de la recherche d’information. J’insiste donc sur mon respect pour l’auteur. On est pas sur bashfr.

Je tiens tout de même à prévenir que la lib ne fonctionne pas sur ma machine, plante sur certains appels, ou produits des screenshots illisibles. Je ne doute néanmoins pas de la compétence de l’auteur, ce qu’il essaye de faire est vraiment compliqué, et ne pense que Python n’est pas son premier langage.

On m’a signalé dans le twitcoutuer qu’il manque de la zik :

Normalement le but de la lib est de permettre de faire des screenshots en pure Python sous Windows, Linux et Mac. Exemple de code sous Linux :

>>> from mss import MSSLinux
>>> mss = MSSLinux()
>>> screnshots = mss.save(output='/tmp/screenshots', oneshot=True)
>>> list(screnshots)
[u'/tmp/screenshots-full.png']

Et voici le code commenté. C’est un gros morceau, et bien complexes, avec des notions parfois que je ne maitrise pas. J’ai pu faire des erreurs et dire des bêtises. Si l’auteur passe par là, il a bien entendu un droit de réponse.

#!/usr/bin/env python
# -*- coding: utf-8 -*-
 
''' A cross-platform multi-screen shot module in pure python using ctypes.
 
    This module is maintained by Mickaël Schoentgen <contact@tiger-222.fr>.
    If you find problems, please submit bug reports/patches via the
    GitHub issue tracker (https://github.com/BoboTiG/python-mss/issues).
 
    Note: please keep this module compatible to Python 2.6.
 
    Still needed:
    * support for additional systems
 
    Many thanks to all those who helped (in no particular order):
 
      Oros, Eownis
 
    History:
 
    <see Git checkin messages for history>
 
    0.0.1 - first release
    0.0.2 - add support for python 3 on Windows and GNU/Linux
    0.0.3 - MSSImage: remove PNG filters
          - MSSImage: remove 'ext' argument, using only PNG
          - MSSImage: do not overwrite existing image files
          - MSSImage: few optimizations into png()
          - MSSLinux: few optimizations into get_pixels()
    0.0.4 - MSSLinux: use of memoization => huge time/operations gains
    0.0.5 - MSSWindows: few optimizations into _arrange()
          - MSSImage: code simplified
 
    You can always get the latest version of this module at:
 
            https://raw.github.com/BoboTiG/python-mss/master/mss.py
 
    If that URL should fail, try contacting the author.
'''
 
from __future__ import (unicode_literals, absolute_import,
                        division, print_function)
 
__version__ = '0.0.5'
__author__ = "Mickaël 'Tiger-222' Schoentgen"
__copyright__ = '''
    Copyright (c) 2013, Mickaël 'Tiger-222' Schoentgen
 
    Permission to use, copy, modify, and distribute this software and its
    documentation for any purpose and without fee or royalty is hereby
    granted, provided that the above copyright notice appear in all copies
    and that both that copyright notice and this permission notice appear
    in supporting documentation or portions thereof, including
    modifications, that you make.
'''
 
# Bon je vais pas vous expliquer ce que les lignes du dessus font hein...hein
 
 
# __all__ liste les objets importables si on fait from mss import *
# ce qui limite la pollution du namespace avec tout un tas de choses inutiles
# comme pack, isfile, system, etc. qui sont quelques lignes plus bas.
__all__ = ['MSSImage', 'MSSLinux', 'MSSMac', 'MSSWindows']
 
# Permet de trouve une bibliothèqe via son nom sur le système en cherchant
# divers chemins standard à l'OS
from ctypes.util import find_library
 
# Permet une forme de sérialisation des types simples qui est compatible
# entre Python et C
from struct import pack
 
# Permet de vérifier si un fichier existe et n'est pas un dossier
from os.path import isfile
 
# Permet de récupérer le nom de l'OS
from platform import system
 
# Divers opérations sur le système
import sys
 
# Compression zip
import zlib
 
# On importe conditionnellement des packages selon le système sur lequel on
# est. La raison de cela est que certains packages n'existe pas (ou ne sont pas
# utiles) sur certains OS.
 
# Darwin, c'est l'OS open source qui sert de base au Mac. C'est
# un mélange de NeXTSTEP et de FreeBSD.
if system() == 'Darwin':
    # Quartz est la techno derrière l'affichage des Mac incluant notament
    # le compositeur graphique et moteur de rendu 2D. Il a un binding Python
    # intégré puisque les Mac utilisent Python un peu partout. A ce demander
    # pourquoi ces neuneus nous ont collé objectifs C pour le dev.
    from Quartz import *
    # On importe juste l'équivalent du mimetype sous Mac pour le PNG. C'est
    # une "constante"
    from LaunchServices import kUTTypePNG
 
 
elif system() == 'Linux':
 
    # Accès aux variables d'environnement
    from os import environ
    # Fonction pour transformer ~ dans les chemin d'accès en chemin vers
    # le dossier utilisateur
    from os.path import expanduser
    # Parseur de xml
    import xml.etree.ElementTree as ET
 
    # 'byref' permet d'obtenir un pointer sur une fonction c,
    # 'cast' est similaire à l'opérateur cast en c et permet le type casting
    # d'un objet c, 'cdll' permet de charger des shared lib C
    from ctypes import byref, cast, cdll
 
    # je ne sais pas pourquoi ça n'a pas été fait une seule ligne...
    # Tout ça représente les types c éponymes, mais en plus Structure est une
    # classe abstraite dont on peut hériter faire une classe qui peut être
    # passée en paramètre à une fonction C qui attend un struc.
    from ctypes import (
        c_char_p, c_int, c_int32, c_uint, c_uint32,
        c_ulong, c_void_p, POINTER, Structure
    )
 
    # On hérite de Structure ce qui nous fait pour le moment une structure
    # vide
    class Display(Structure):
        pass
 
    # Une structure représentant les attributs d'une fenêtre pour le serveur
    # d'affichage sous Linux.
    class XWindowAttributes(Structure):
        _fields_ = [
            ('x',                     c_int32),
            ('y',                     c_int32),
            ('width',                 c_int32),
            ('height',                c_int32),
            ('border_width',          c_int32),
            ('depth',                 c_int32),
            ('visual',                c_ulong),
            ('root',                  c_ulong),
            ('class',                 c_int32),
            ('bit_gravity',           c_int32),
            ('win_gravity',           c_int32),
            ('backing_store',         c_int32),
            ('backing_planes',        c_ulong),
            ('backing_pixel',         c_ulong),
            ('save_under',            c_int32),
            ('colourmap',             c_ulong),
            ('mapinstalled',          c_uint32),
            ('map_state',             c_uint32),
            ('all_event_masks',       c_ulong),
            ('your_event_mask',       c_ulong),
            ('do_not_propagate_mask', c_ulong),
            ('override_redirect',     c_int32),
            ('screen',                c_ulong)
        ]
 
    # structure définissant une image telle qu'elle existe dans la mémoire
    # d'un client du serveur d'affichage
    class XImage(Structure):
        _fields_ = [
            ('width'            , c_int),
            ('height'           , c_int),
            ('xoffset'          , c_int),
            ('format'           , c_int),
            ('data'             , c_char_p),
            ('byte_order'       , c_int),
            ('bitmap_unit'      , c_int),
            ('bitmap_bit_order' , c_int),
            ('bitmap_pad'       , c_int),
            ('depth'            , c_int),
            ('bytes_per_line'   , c_int),
            ('bits_per_pixel'   , c_int),
            ('red_mask'         , c_ulong),
            ('green_mask'       , c_ulong),
            ('blue_mask'        , c_ulong)
        ]
 
    # Apparement la fonction pack avec ce format va être appelée souvent
    # doc l'auteur se fait un raccourci. Le format en question est 'B', donc
    # du unsigned char, et '<', donc du little endian. Et là vous comprenez
    # le bonheur de travailler dans un langage de haut niveau comme Python.
    def b(x):
        return pack(b'<B', x)
 
elif system() == 'Windows':
 
 
    from ctypes import (
 
        # 'memset' fait pareil que memset en C : on rempli un bloc de mémoire
        # avec les bytes passés en param mais il n'est pas utilisé dans le code,
        # donc je suppose que c'est un oubli. 'pointer' va créer une instance de
        # la classe qui permet de manipuler un pointer sur un type c en Python.
        # 'sizeof', même chose que l'opérateur c, ça retourne la taille en bytes
        # d'un type
        # c. 'windll', comme 'cdll' plus haut, mais l'appel des fonctions suit
        # la convention stdcall typique de la win32api et pas la convention c
        # standard.
        byref, memset, pointer, sizeof, windll,
        # Représentation du type void en c, aliasé sous deux noms. Je n'ai pas
        # vraiment compris l'interêt de le faire, excepté pour explicitement
        # montrer quand on l'utilise dans un appel qui attend LPRECT (un pointer
        # sur une struc qui représente un rectangle ou un pointer sur void)
        c_void_p as LPRECT,
        c_void_p as LPVOID,
        # créer un array de char en c
        create_string_buffer,
        # ça on a vu
        Structure,
        POINTER,
        # Ceci est une fonction. Pourquoi c'est en majuscule, je n'en sais rien.
        # Une incohérence dans l'API. En tout cas ça permet de retourner
        # un prototype de fonction qui respecte la convention stdcall.
        WINFUNCTYPE,
    )
    # Des "constantes" qui représentent des types spécifiques à l'API widows
    # Notez que le PEP8 n'est pas respecté, mais bon, vu la taille du code,
    # il y a forcément des coquilles, c'est normal. Certains comme SHORT
    # et HANDLE ne sont pas utilisés.
    from ctypes.wintypes import (
        BOOL, DOUBLE, DWORD, HANDLE, HBITMAP, HDC, HGDIOBJ,
        HWND, INT, LPARAM, LONG,RECT,SHORT, UINT, WORD
    )
 
    # Structure contenant les information sur les dimentions et la couleur
    # d'un BMP (on appelle ça aussi un bitmap ou DIB, donc dans les docs
    # vous verrez l'une ou l'autre de ces appellations)
    class BITMAPINFOHEADER(Structure):
        _fields_ = [
            ('biSize',          DWORD),
            ('biWidth',         LONG),
            ('biHeight',        LONG),
            ('biPlanes',        WORD),
            ('biBitCount',      WORD),
            ('biCompression',   DWORD),
            ('biSizeImage',     DWORD),
            ('biXPelsPerMeter', LONG),
            ('biYPelsPerMeter', LONG),
            ('biClrUsed',       DWORD),
            ('biClrImportant',  DWORD)
        ]
 
    # Un wrapper sur la structure précédente, je suppose nécessaire pour
    # être compatible avec une des bizarreries de l'API windows.
    class BITMAPINFO(Structure):
        _fields_ = [
            ('bmiHeader', BITMAPINFOHEADER),
            ('bmiColors', DWORD * 3)
        ]
 
    # Là j’avoue je ne comprends pas trop le principe. Il tente de vérifier
    # si le système est du Python 2, si oui il ne caste pas la string en bytes
    # car le type de base des strings est déjà de type bytes (ce qui n'est
    # pas le cas en Python 3 : c'est un type unicode). Donc il fabrique la
    # fonction raccourcie selon la version. Sauf qu'il ne le fait pas
    # dans la condition pour linux. Du coup je pige pas trop comment il
    # s'en sort. Il est compatible que 2.7 sous Linux ?
    # Dans tous les cas ces fonctions pourraient être en dehors de la clause
    # if, tout comme certains imports pour des raisons de DRY.
    if sys.version < '3':
        def b(x):
            return x
    else:
        def b(x):
            return pack(b'<B', x)
 
 
# Moi aussi j'adore mettre des commentaires comme ça dans mon code.mon
# Que ceux qui n'aiment pas aillent se faire foutre.
 
 
# ----------------------------------------------------------------------
# --- [ C'est parti mon kiki ! ] ---------------------------------------
# ----------------------------------------------------------------------
 
# Comme précisé dans la docstring, c'est une classe de base. Ca implemente
# essentiellement le logging du mode debug et la logique d'enregistrement
# des fichiers.
class MSS(object):
    ''' This class will be overloaded by a system specific one.
        It checkes if there is a class available for the current system.
        Raise an exception if no one found.
    '''
 
 
    # De l'initialisation, pas super intéressant...
    DEBUG = False
 
    def __init__(self, debug=False):
        ''' Global vars and class overload. '''
 
        self.DEBUG = debug
        self.monitors = []
        self.oneshot = False
 
        # L'init présuppose quand même qu'une classe fille a une fonction
        #  init. Je vois pas trop l'interêt car ça peut se faire par héritage
        # ou alors quitte à faire ça, autant faire en utilisant un système
        # de call back sur des events genre "oninit", c'est plus propre, plus
        # découplé et ça tombe pas en marche si la fonction init() n'existe pas
        # dans l'enfant.
        self.init()
 
    # Une fonction print un peut élaborée qui permet d'afficher rapidement
    # le contenu qu'on lui passe en paramètre de manière formatté pour le debug.
    # Utiliser le module logging aurait été préférable, mais c'est tellement
    # chiant à setup que je comprends qu'il ait eu la flemme.
    def debug(self, method='', scalar=None, value=None):
        ''' Simple debug output. '''
 
        if self.DEBUG:
            if scalar is None:
                print(':: ' + method + '()')
            else:
                print(method + '()', scalar, type(value).__name__, value)
 
    # La fonction "kifétou". Elle demande la création des screenshots, les
    # sauvegarde dans des fichiers png dans un dossier. Il aurait été sympa
    # de découper cette fonction en sous fonctions : une prendre le screen
    # d'un moniteur, une pour recupérer le screenshot tout forme d'objet image,
    # etc. Là la seule option c'est "tu prends tout, tu sauves tout". C'est pas
    # très souple, et dur à tester.
    def save(self, output='mss', oneshot=False):
        ''' For each monitor, grab a screen shot and save it to a file.
 
            Parameters:
             - output - string - the output filename without extension
             - oneshot - boolean - grab only one screen shot of all monitors
 
            This is a generator which returns created files:
                'output-1.png',
                'output-2.png',
                ...,
                'output-NN.png'
                or
                'output-full.png'
        '''
 
        self.debug('save')
 
        # Là on voit un antipattern très courrant en POO : mettre des paramètres
        # pour un appel de méthode directement en attribut pour toute la classe.
        # Cela veut dire qu'un appel à cette fonction "lock" la classe dans
        # un état dépendant de cet appel. En plus de réduire à néant les possibilités
        # de multithreading et rendre les tests compliqués, ça veut dire aussi
        # que tout débuggage doit tenir compte de l'état de la classe. Et bien
        # entendu, ça veut dire qu'on ne peut pas avoir plusieurs états en
        # parallèles, à moins d'instancier plusieurs classes. Il vaudrait mieux
        # que tout ça soit passé en paramètres, quitte à faire un autre objet
        # nommé "config" qui contienne tout ça.
        #
        # En tout cas, ça stoque le param "oneshot" et la liste des moniteurs
        # retournés par le code fourni par la classe fille puisque
        # enum_display_monitors n'est pas défini dans cette classe.
        self.oneshot = oneshot
        self.monitors = self.enum_display_monitors() or []
 
        self.debug('save', 'oneshot', self.oneshot)
 
        # On lève une exception si il est impossible de trouver des écrans dont
        # on peut faire la capture, ce qui est une bonne chose. Mais une bonne
        # pratique serait de faire :
        # class ScreenshotError(Exception):
        #     pass
        # Afin d'avoir quelque chose de plus significatif que ValueError.
        # De plus, ce code est typique d'un programmeur d'un autre langage
        # qui apprend encore les idiomes Python car cette condition pourrait
        # se réduire à :
        # if not self.monitors: <- pas besoin de len
        #     raise ScreenshotError('MSS: no monitor found.'))
        if len(self.monitors) < 1:
            raise ValueError('MSS: no monitor found.')
 
        # Monitors screen shots!
        # On itère sur les écrans, et on fait un screen par écran. Même remarque
        # sur le fait que l'auteur cherche encore ses marques en Python (notez
        # bien que c'est naturel, personne n'a la science infuse et mon style
        # est horrible dans les autres langages), car ici le compteur i est
        # aventageusement remplacé par :
        # for i, monitor in enumerate(self.monitors)
        i = 1
        for monitor in self.monitors:
            self.debug('save', 'monitor', monitor)
 
            # Si on ne fait qu'un gros screenshot, on l'appelle name-full.png
            # sinon name-numero_du_moniteur.png
            if self.oneshot:
                filename = output + '-full'
            else:
                filename = output + '-' + str(i)
                i += 1
            filename += '.png'
 
            # Si le fichier n'existe pas, on l'écrit. Ceci est encore une
            # erreur car une API métier ne doit pas faire ce genre de
            # vérification. Il devrait écraser les fichiers. Il est en revanche
            # possible de mettre un système de callback pour réagir si le
            # fichier existe. Mais le comportement par défaut devrait être
            # d'écraser le fichiers, sinon une personne qui ne lit pas le code
            # source va croire que le code ne marche pas. De plus, il est plus
            # rare de ne pas vouloir écraser les fichiers d'origine que
            # l'inverse. Enfin, puisqu'on ne controle pas le nommage des fichiers
            # depuis l'extérieur de la classe, il n'y a pas de moyen de réagir
            # proprement pour dire "je veux garder les anciens fichiers et
            # avoir les nouveaux quand même" à part utilise un dossier différent
            # à chaque fois.
            if not isfile(filename):
 
                # On récupère l'array de pixels en utilisant le code de la
                # classe fille une fois de plus. C'est nécessaire puisque ça
                # dépend de l'OS.
                # Ce code ne fait pas du tout ce que vous croyez. get_pixels
                # va sauvegarder l'image sans self.image, et retourner 1.
                # C'est donc une fonction qui marche via ses effets de bords,
                # ce qui est une très mauvaise pratique.
                pixels = self.get_pixels(monitor)
 
                # Même remarque que sur l'autre exception. De plus l'exception
                # devrait être levée au niveau de get_pixels, pas au niveau
                # de save(). Il y a un mélange de base niveau et de haut
                # niveau dans cette classe qui rend l'API assez maladroite.
                # D'ailleurs get_pixels ne retourne jamais None...
                if pixels is None:
                    raise ValueError('MSS: no data to process.')
 
                # Bon, ça c'est super crade. En gros il délègue le save à une
                # méthode si elle existe, et sinon le fait à la main.
                # L'héritage est fait pour ça, et il aurait été préférable
                # de faire une méthode save(pixels, size, filename) générique
                # et ensuite l'écraser dans les enfants. Peut être que c'est un
                # dev C et qu'il n'est pas encore très à l'aise avec le POO.
                # Il est possible que l'auteur ait rencontré des problèmes du
                # fait d'une inconsistance d'api et n'ai pas su le gérer en
                # mettant un adapter par dessus. Quand je vous dis que les
                # design pattern c'est parfois utile :)
                if hasattr(self, 'save_'):
                    img_out = self.save_(output=filename)
                else:
                    img = MSSImage(pixels, monitor[b'width'], monitor[b'height'])
                    img_out = img.dump(filename)
                self.debug('save', 'img_out', img_out)
                if img_out:
                    # Etant donné que la génération du screenshot prend du
                    # temps et qu'il y en a plusieurs, l'auteur a intelligemment
                    # choisit de faire un générateur, ce qui permet de ne pas
                    # attendre que tous les screenshots soient terminé avant
                    # de d'avoir un premier résultat.
                    yield img_out
            else:
                yield filename + ' (already exists)'
 
    # Les fameuses méthodes délégues aux classes filles. Un petit
    # NotImplemented serait mieux que pass car c'est plus explicite, mais
    # rien de grave.
    def enum_display_monitors(self):
        ''' Get positions of all monitors.
 
            If self.oneshot is True, this function has to return a dict
            with dimensions of all monitors at the same time.
            If the monitor has rotation, you have to deal with inside this method.
 
            Must returns a dict with a minima:
            {
                'left':   the x-coordinate of the upper-left corner,
                'top':    the y-coordinate of the upper-left corner,
                'width':  the width,
                'height': the height
            }
        '''
        pass
 
    def get_pixels(self, monitor_infos):
        ''' Retrieve screen pixels for a given monitor.
 
            monitor_infos should contain at least:
            {
                'left':   the x-coordinate of the upper-left corner,
                'top':    the y-coordinate of the upper-left corner,
                'width':  the width,
                'heigth': the height
            }
 
            Returns a dict with pixels.
        '''
        pass
 
# Maintenant on par dans les détails de l'implémentation de l'algo qui récupère
# les screenshots qui est spécifique à chaque OS.
class MSSMac(MSS):
    ''' Mutli-screen shot implementation for Mac OSX.
        It uses intensively the Quartz.
    '''
 
    # Bon, du coup plutôt que de faire une fonction init, il aurait mieux
    # valut faire une fonction __init__ qui appelle super().
    def init(self):
        ''' Mac OSX initialisations '''
        self.debug('init')
        pass
 
    # Là c'est le gros du boulot, et c'est là que je vous dis que l'auteur
    # c'est vraiment super cassé le cul pour trouver toutes ces infos sur
    # les APIS propres à chaque système, et en plus en C. Kudos.
    def enum_display_monitors(self):
        ''' Get positions of one or more monitors.
            Returns a dict with minimal requirements (see MSS class).
        '''
 
        self.debug('enum_display_monitors')
 
        results = []
        # Si il y a un seul screen à prend, on se fait pas chier et on prend
        # un rectangle de taille infinie pour les limites du screen. Je
        # suppose que l'OS est capable de se démerder avec ça et borner
        # à la taille du matos automatiquement. C'est un truc de faignasse, et
        # c'est plutôt malin.
        if self.oneshot:
            rect = CGRectInfinite
            results.append({
                b'left'  : int(rect.origin.x),
                b'top'   : int(rect.origin.y),
                b'width' : int(rect.size.width),
                b'height': int(rect.size.height)
            })
        else:
            # Par contre là il faut se taper le calcul des coordonnées de
            # rectangle à la main
 
            # Cette variable est requise par CGGetActiveDisplayList mais
            # comme le suggère le commentaire de l'auteur ci-dessous : LOL.
            max_displays = 32  # Peut-être augmenté, si besoin...
 
            # Apparemment il est possible que l'affichage ait subit une
            # rotation. Je suppose que MacOS anticipait déjà l’existence des
            # tablettes à l'époque.
            rotations = {0.0: 'normal', 90.0: 'right', -90.0: 'left'}
 
            # On demande les affichages qui sont effectivement en court
            # d'utilisation, et on récupère un identifiant unique pour chacun
            # d'entre deux.
            res, ids, count = CGGetActiveDisplayList(max_displays, None, None)
            for display in ids:
                # On interroge l'API Mac pour avoir ses coordonnées, et on
                # les extrait.
                rect = CGRectStandardize(CGDisplayBounds(display))
                left, top = rect.origin.x, rect.origin.y
                width, height = rect.size.width, rect.size.height
                # Correction des coordonnées en cas de rotation.
                rot = CGDisplayRotation(display)
                rotation = rotations[rot]
                if rotation in ['left', 'right']:
                    width, height = height, width
 
                # Et on insère le résultat dans la liste des monitors. On
                # aurait pu yielder aussi comme dans save(), mais bon, c'est
                # parce que je yield partout que je dis ça.
                results.append({
                    b'left'    : int(left),
                    b'top'     : int(top),
                    b'width'   : int(width),
                    b'height'  : int(height),
                    b'rotation': rotation
                })
        return results
 
    def get_pixels(self, monitor):
        ''' Retreive all pixels from a monitor. Pixels have to be RGB.
        '''
 
        self.debug('get_pixels')
 
        # On récupère les coordonnées retournées par enum_display_monitors
        # et on en fait un CGRect qui est nécessaire pour CGWindowListCreateImage.
        width, height = monitor[b'width'], monitor[b'height']
        left, top = monitor[b'left'], monitor[b'top']
        rect = CGRect((left, top), (width, height))
        # CGWindowListCreateImage retourne l'image à ces coordonnées.
        # Les 3 derniers paramètres sont là grace à "from Quartz import *"
        # (c'est pour ça que je vous dis de ne pas le faire, car c'est pas
        # facile à retracer). kCGWindowListOptionOnScreenOnly force l'inclusion
        # uniquement des fenêtre affichées et non celles hors écran.
        # kCGNullWindowID est l'équivalent de None, dans ce contexte. C'est
        # la fenêtre de référence, et on en veut aucune.
        # kCGWindowImageDefault demande de capturer le fenêtre ET sa décoration
        # (comme les ombres portées).
        # Ces deux derniers params sont les valeurs par défaut donc je ne sais
        # pas pourquoi il les passent.
        self.image = CGWindowListCreateImage(
                    rect, kCGWindowListOptionOnScreenOnly,
                    kCGNullWindowID, kCGWindowImageDefault)
 
        # Comme je le disais dans save(), la fonction ne retourne PAS l'objet
        # image mais 1. L'image est stockée dans self.image et ce sont les
        # autres méthodes qui tapent dans cet attribut. C'est mal.
        return 1
 
    # la fameurse méthode save_ utilisée par save()...
    def save_(self, output):
        ''' Special method to not use MSSImage class. '''
 
        self.debug('save_')
 
        # résolution du png
        dpi = 72
        # chemin du png. Tout est URL dans Mac OS. Un chemin de fichier
        # est une URL file://
        url = NSURL.fileURLWithPath_(output)
        # On fabrique l'objet fichier
        dest = CGImageDestinationCreateWithURL(url, kUTTypePNG, 1, None)
        properties = {
            kCGImagePropertyDPIWidth: dpi,
            kCGImagePropertyDPIHeight: dpi,
        }
        # On met notre image dans l'objet fichier. Là il faut se souvenir
        # que save() appelle get_pixel() qui remplit self.image puis appelle
        # save_() derrière. Je vous l'avais dis que les effets de bord, ça
        # craint).
        CGImageDestinationAddImage(dest, self.image, properties)
        # On écrit notre objet fichier sur le disk.
        if not CGImageDestinationFinalize(dest):
            output = None
 
        # L'auteur vide ouput si CGImageDestinationFinalize à échoué. Il
        # vaudrait mieux lever une exception.
        return output
 
# On passe à l'implémentation Linux.
class MSSLinux(MSS):
    ''' Mutli-screen shot implementation for GNU/Linux.
        It uses intensively the Xlib.
    '''
 
    # Fait un travail de nettoyage quand l'objet est garbage collecté, en
    # l'occurence ferme la connection avec le serveur x. Je rappelle que
    # l'appel à __del__ n'est pas garanti donc mieux faut faire une méthode
    # close() officielle et un context manager avec un finally.
    def __del__(self):
        ''' Disconnect from X server '''
 
        self.debug('__del__')
 
        if self.display:
            self.XCloseDisplay(self.display)
 
 
    def init(self):
        ''' GNU/Linux initialisations '''
 
        self.debug('init')
 
        # on charge la shared lib du serveur x
        x11 = find_library('X11')
        if x11 is None:
            raise OSError('MSSLinux: no X11 library found.')
        else:
            xlib = cdll.LoadLibrary(x11)
 
        self.debug('init', 'xlib', xlib)
 
        # Aliasing des méthode de xlib. Pourquoi ? Aucune idée.
        # passer l'objet xlib le parait plus logique. Peut être qu'il y a
        # une raison technique liée au mapping ctypes, mais je ne la connais
        # pas.
        self.XOpenDisplay = xlib.XOpenDisplay
        self.XDefaultScreen = xlib.XDefaultScreen
        self.XDefaultRootWindow = xlib.XDefaultRootWindow
        self.XGetWindowAttributes = xlib.XGetWindowAttributes
        self.XAllPlanes = xlib.XAllPlanes
        self.XGetImage = xlib.XGetImage
        self.XGetPixel = xlib.XGetPixel
        self.XFree = xlib.XFree
        self.XCloseDisplay = xlib.XCloseDisplay
 
        # ctypes permet de spécifier pour chaque fonction des types attendus
        # en paramètre et en valeur de retour. Cela permet l'autocasting
        # de types compatibles et une exception explicite en cas de passage
        # de mauvais paramètres plutôt qu'un core dump.
        # L'auteur ici à mis ce code un peu lourd dans des méthodes dédiées et
        # préfixées de _ puisqu'à usage interne.
        self._set_argtypes()
        self._set_restypes()
 
        # On récupère l'affichage en cours en tapant dans les variables
        # d'environnement. Le jobby-jobba et du au fait qu'il veut garder
        # la compatibilité Python 3 et 2.7 et qu'il faut accomoder
        # les types str/unicode/bytes.
        # C'est une vérification pour éviter un segfault si il n'y pas de
        # serveur x qui tourne.
        display = None
        self.display = None
        try:
            if sys.version > '3':
                display = bytes(environ['DISPLAY'], 'utf-8')
            else:
                display = environ['DISPLAY']
        except KeyError:
            err = 'MSSLinux: $DISPLAY not set. Stopping to prevent segfault.'
            raise ValueError(err)
        self.debug('init', '$DISPLAY', display)
 
        # On récupère l'écran par défaut et la fenêtre racine sur l'affichage
        # en cours.
 
        # At this point, if there is no running server, it could end on
        # a segmentation fault. And we cannot catch it.
        self.display = self.XOpenDisplay(display)
        self.debug('init', 'display', self.display)
        self.screen = self.XDefaultScreen(self.display)
        self.debug('init', 'screen', self.screen)
        self.root = self.XDefaultRootWindow(self.display, self.screen)
        self.debug('init', 'root', self.root)
 
    # les deux méthodes qui servent à manuellement définir les types
    # des autres méthodes dont j'ai parlé plus haut.
 
    def _set_argtypes(self):
        ''' Functions arguments '''
 
        self.debug('_set_argtypes')
 
        self.XOpenDisplay.argtypes = [c_char_p]
        self.XDefaultScreen.argtypes = [POINTER(Display)]
        self.XDefaultRootWindow.argtypes = [POINTER(Display), c_int]
        self.XGetWindowAttributes.argtypes = [POINTER(Display),
            POINTER(XWindowAttributes), POINTER(XWindowAttributes)]
        self.XAllPlanes.argtypes = []
        self.XGetImage.argtypes = [POINTER(Display), POINTER(Display),
            c_int, c_int, c_uint, c_uint, c_ulong, c_int]
        self.XGetPixel.argtypes = [POINTER(XImage), c_int, c_int]
        self.XFree.argtypes = [POINTER(XImage)]
        self.XCloseDisplay.argtypes = [POINTER(Display)]
 
    def _set_restypes(self):
        ''' Functions return type '''
 
        self.debug('_set_restypes')
 
        self.XOpenDisplay.restype = POINTER(Display)
        self.XDefaultScreen.restype = c_int
        self.XDefaultRootWindow.restype = POINTER(XWindowAttributes)
        self.XGetWindowAttributes.restype = c_int
        self.XAllPlanes.restype = c_ulong
        self.XGetImage.restype = POINTER(XImage)
        self.XGetPixel.restype = c_ulong
        self.XFree.restype = c_void_p
        self.XCloseDisplay.restype = c_void_p
 
 
    def enum_display_monitors(self):
        ''' Get positions of one or more monitors.
            Returns a dict with minimal requirements (see MSS class).
        '''
 
        self.debug('enum_display_monitors')
 
        results = []
        if self.oneshot:
            # Dans le cas d'un seul screenshot pour tout, on récupère juste
            # les coordonnées de la fenêtre racine.
            gwa = XWindowAttributes()
            self.XGetWindowAttributes(self.display, self.root, byref(gwa))
            results.append({
                b'left'  : int(gwa.x),
                b'top'   : int(gwa.y),
                b'width' : int(gwa.width),
                b'height': int(gwa.height)
            })
        else:
 
            # Sinon on parse le fichier XML de config pour essayer de
            # trouver les coordonnées. Je ne suis pas certain que ce soit
            # une bonne stratégie puisque le fichier monitors.xml contient
            # tous les moniteurs jamais branché sur la machine, y compris ceux
            # qu'on a pas branché depuis 1000 ans...
 
            # It is a little more complicated, we have to guess all stuff
            # from ~/.config/monitors.xml, if present.
            monitors = expanduser('~/.config/monitors.xml')
            if not isfile(monitors):
                # Ici, lever une exception serait pas mal.
                # A la place, l'auteur choisit de mettre oneshot et de relancer
                # la capture. Donc au lieu d'avoir ce qu'on demande ou une
                # erreur, on a ce qu'on demande ou ce qu'on ne demande pas.
                # Encore une fois, j'en profite pour souligner qu'il faut
                # éviter ce genre de config à base de site effects. Devoir
                # setter un attribut pour avoir un résultat différent à cette
                # méthode n'est pas très propre.
                self.debug('ERROR', 'MSSLinux: enum_display_monitors() failed (no monitors.xml).')
                self.oneshot = True
                return self.enum_display_monitors()
 
            # Le XML est une collection de noeuds 'configuration' qui représentent
            # chacun un moniteur. On récupère ici le premier noeud 'configurations'
            tree = ET.parse(monitors)
            root = tree.getroot()
            config = root.findall('configuration')[-1]
            conf = []
            # chaque noeud "configurations" à une série de noeuds ouput qui
            # représentent chaque format de sortie (VGA, HDMI, etc). On
            # boucle dessus.
            for output in config.findall('output'):
                name = output.get('name')
                if name != 'default':
                    # On récupère les coordonnées, la rotation, on extrait,
                    # on corrige, on ajoute à la liste... Bref, même topo
                    # qu'avec MacOsX.
                    x = output.find('x')
                    y = output.find('y')
                    width = output.find('width')
                    height = output.find('height')
                    rotation = output.find('rotation')
                    if None not in [x, y, width, height] and name not in conf:
                        conf.append(name)
                        if rotation.text in ['left', 'right']:
                            width, height = height, width
                        results.append({
                            b'left'    : int(x.text),
                            b'top'     : int(y.text),
                            b'width'   : int(width.text),
                            b'height'  : int(height.text),
                            b'rotation': rotation.text
                        })
        return results
 
    def get_pixels(self, monitor):
        ''' Retreive all pixels from a monitor. Pixels have to be RGB.
        '''
 
        self.debug('get_pixels')
 
        # On récupère les coordonnées des monitors revoyées par enum_display_monitors
        width, height = monitor[b'width'], monitor[b'height']
        left, top = monitor[b'left'], monitor[b'top']
        ZPixmap = 2
 
        # On récupère un masque de pixels pour l'ensemble de l'affichage
        allplanes = self.XAllPlanes()
        self.debug('get_pixels', 'allplanes', allplanes)
 
        # Visiblement un fix. C'est ce qu'on appelle un commentaire utile.
        # Fix for XGetImage: expected LP_Display instance instead of LP_XWindowAttributes
        root = cast(self.root, POINTER(Display))
 
        # On récupère un dump des pixels pour les coordonnées en cours, de
        # l'affichage en cours, on lui applique le masque de pixel mais
        # je ne sais pas pourquoi on doit le faire. x11 est une bestiole
        # très tarabiscotée, et mes recherches n'ont rien donné. L'auteur
        # a du bien s'amuser à trouver comment faire ce genre de chose.
        image = self.XGetImage(self.display, root, left, top, width,
            height, allplanes, ZPixmap)
        if image is None:
            raise ValueError('MSSLinux: XGetImage() failed.')
 
        # Les pixels doivent être récupérés en RGB. L'auteur fait donc une
        # fonction de conversion (c'est une fonction inline, donc jetable)
        # puis l'applique à la liste des pixels qu'il récupère dans l'image.
        # Une simple boucle for aurait fait l'affaire mais l'auteur a mis
        # en place une stratégie de mémoisation (mise en cache) dans la fonction.
        # Vu que la fonction est inline, un simple dico aurait aussi fait
        # l'affaire. Mais une il est très possible qu'on lui ait donné
        # le truc et comme il n'est pas habitué à Python il a juste copié/collé.
        # Je le fais souvent en Java / C donc je vais pas lui jeter la pierre.
        def pix(pixel, _resultats={}):
            ''' Apply shifts to a pixel to get the RGB values.
                This method uses of memoization.
            '''
            # La mise en cache se fait à ce niveau.
            if not pixel in _resultats:
                # Là c'est du byte shifting, mais quelle logique exacte est
                # implémentée, aucune idée. Il faudrait regarder les algos
                # de conversion pixels vers RGB et trouver celui qui est
                # appliqué. C'est le genre de truc que je copie/colle car
                # je suis trop feignant et tester que ça marche est plus rapide
                # que comprendre.
                _resultats[pixel] = b((pixel & 16711680) >> 16) + b((pixel & 65280) >> 8) + b(pixel & 255)
            return _resultats[pixel]
 
        # Aliasing de la fonction pour gagner en vitesse en évitant un lookup
        # d'attribut dans une boucle.
        get_pix = self.XGetPixel
        # Boucle de conversion via une liste en intention.
        pixels = [pix(get_pix(image, x, y)) for y in range(height) for x in range(width)]
 
        # Ici get_pixels retourne l'objet image plutôt que de la sauver
        # dans un attribut self.image. Ouch.
        self.XFree(image)
        return b''.join(pixels)
 
# On passe maintenant à l'implémentation pour Windows
 
class MSSWindows(MSS):
    ''' Mutli-screen shot implementation for Microsoft Windows. '''
 
    # Même topo que pour la version Linux. Même principe que son init.
    def init(self):
        ''' Windows initialisations '''
 
        self.debug('init')
 
        self.GetSystemMetrics = windll.user32.GetSystemMetrics
        self.EnumDisplayMonitors = windll.user32.EnumDisplayMonitors
        self.GetWindowDC = windll.user32.GetWindowDC
        self.CreateCompatibleDC = windll.gdi32.CreateCompatibleDC
        self.CreateCompatibleBitmap = windll.gdi32.CreateCompatibleBitmap
        self.SelectObject = windll.gdi32.SelectObject
        self.BitBlt = windll.gdi32.BitBlt
        self.GetDIBits = windll.gdi32.GetDIBits
        self.DeleteObject = windll.gdi32.DeleteObject
 
        self._set_argtypes()
        self._set_restypes()
 
    def _set_argtypes(self):
        ''' Functions arguments '''
 
        self.debug('_set_argtypes')
 
        self.MONITORENUMPROC = WINFUNCTYPE(INT, DWORD, DWORD,
            POINTER(RECT), DOUBLE)
        self.GetSystemMetrics.argtypes = [INT]
        self.EnumDisplayMonitors.argtypes = [HDC, LPRECT,
            self.MONITORENUMPROC, LPARAM]
        self.GetWindowDC.argtypes = [HWND]
        self.CreateCompatibleDC.argtypes = [HDC]
        self.CreateCompatibleBitmap.argtypes = [HDC, INT, INT]
        self.SelectObject.argtypes = [HDC, HGDIOBJ]
        self.BitBlt.argtypes = [HDC, INT, INT, INT, INT, HDC, INT, INT, DWORD]
        self.DeleteObject.argtypes = [HGDIOBJ]
        self.GetDIBits.argtypes = [HDC, HBITMAP, UINT, UINT, LPVOID,
            POINTER(BITMAPINFO), UINT]
 
    def _set_restypes(self):
        ''' Functions return type '''
 
        self.debug('_set_restypes')
 
        self.GetSystemMetrics.restypes = INT
        self.EnumDisplayMonitors.restypes = BOOL
        self.GetWindowDC.restypes = HDC
        self.CreateCompatibleDC.restypes = HDC
        self.CreateCompatibleBitmap.restypes = HBITMAP
        self.SelectObject.restypes = HGDIOBJ
        self.BitBlt.restypes =  BOOL
        self.GetDIBits.restypes = INT
        self.DeleteObject.restypes = BOOL
 
    def enum_display_monitors(self):
        ''' Get positions of one or more monitors.
            Returns a dict with minimal requirements (see MSS class).
        '''
 
        self.debug('enum_display_monitors')
 
        # le code qui permet de récupérer les moniteurs est visiblement
        # asynchrone, donc on fabrique un callback qui va remplir le tableau
        # des résultats.
 
        def _callback(monitor, dc, rect, data):
            rct = rect.contents
            results.append({
                b'left'  : int(rct.left),
                b'top'   : int(rct.top),
                b'width' : int(rct.right - rct.left),
                b'height': int(rct.bottom -rct.top)
            })
            return 1
 
        results = []
        # si c'est juste un seul screenshot, c'est un appel synchrone
        if self.oneshot:
            # ce sont des constantes qui déterminent quelle info ont veut que
            # GetSystemMetrics renvoit. Ici on demande les coordonnées de tout
            # l'écran, une par une.
            SM_XVIRTUALSCREEN = 76
            SM_YVIRTUALSCREEN = 77
            SM_CXVIRTUALSCREEN = 78
            SM_CYVIRTUALSCREEN = 79
            left = self.GetSystemMetrics(SM_XVIRTUALSCREEN)
            right = self.GetSystemMetrics(SM_CXVIRTUALSCREEN)
            top = self.GetSystemMetrics(SM_YVIRTUALSCREEN)
            bottom = self.GetSystemMetrics(SM_CYVIRTUALSCREEN)
            results.append({
                b'left'  : int(left),
                b'top'   : int(top),
                b'width' : int(right - left),
                b'height': int(bottom - top)
            })
        else:
            # On enrobe le callback Python dans un proxy qui le rend
            # utilisable par le code C
            callback = self.MONITORENUMPROC(_callback)
            # On demande à windows de nous lister les moniteurs, et comme
            # cet appel est asynchrone, on lui passe un callback pour qu'il
            # replisse la liste au fur et à mesure
            self.EnumDisplayMonitors(0, 0, callback, 0)
 
        # On retourne la liste. A ce stade là, on ne sait pas si la liste
        # est vide, à moitié remplie ou complètement remplie. Utiliser du
        # code asynchrone au milieu d'une lib synchrone, c'est assez dangereux
        # donc je me demande comment il retombe sur ses pieds.
        return results
 
    # A ce stade là il est 13h, j'ai commencé à 9h et j'ai la dalle. J'en ai
    # marre de commenter ce code. 'envoyez-vous les codes que vous pigez pas'
    # est une idée à la con. Je vous hais tous. Mon coloc va me ramener des
    # frites.
 
    def get_pixels(self, monitor):
        ''' Retreive all pixels from a monitor. Pixels have to be RGB. '''
 
        self.debug('get_pixels')
 
        # Encore une fois on récupère les coordonnées des moniteurs filées
        # par enum_display_monitors
        width, height = monitor[b'width'], monitor[b'height']
        left, top = monitor[b'left'], monitor[b'top']
 
        # Reajustement de la taille de la largeur. Je suppose que c'est
        # empirique, mais je peux me tromper.
        good_width = (width * 3 + 3) & -4
        # Valeur de paramètre qui dit de copier directement dans le rectangle
        # des destination.
        SRCCOPY = 0xCC0020
        DIB_RGB_COLORS = 0
 
        # Récupère le Device Context (title bar, menus, and scroll bars, etc)
        srcdc = self.GetWindowDC(0)
        # On fabrique une copie en mémoire.
        memdc = self.CreateCompatibleDC(srcdc)
        # On fabrique un bitmap basé sur ce DC
        bmp = self.CreateCompatibleBitmap(srcdc, width, height)
        # On injecte un bitmap dans le DC en mémoire.
        self.SelectObject(memdc, bmp)
        # On transfert les bits d'un DC à l'autre.
        self.BitBlt(memdc, 0, 0, width, height, srcdc, left, top, SRCCOPY)
        # On fabrique le header du BMP
        bmi = BITMAPINFO()
        bmi.bmiHeader.biSize = sizeof(BITMAPINFOHEADER)
        bmi.bmiHeader.biWidth = width
        bmi.bmiHeader.biHeight = height
        bmi.bmiHeader.biBitCount = 24
        bmi.bmiHeader.biPlanes = 1
        buffer_len = height * good_width
        # On fabrique un array de char
        pixels = create_string_buffer(buffer_len)
        # On récupère les bits du bitmap issu du DC
        bits = self.GetDIBits(memdc, bmp, 0, height, byref(pixels),
            pointer(bmi), DIB_RGB_COLORS)
 
        self.debug('get_pixels', 'srcdc', srcdc)
        self.debug('get_pixels', 'memdc', memdc)
        self.debug('get_pixels', 'bmp', bmp)
        self.debug('get_pixels', 'buffer_len', buffer_len)
        self.debug('get_pixels', 'bits', bits)
        self.debug('get_pixels', 'len(pixels.raw)', len(pixels.raw))
 
        # Nettoyage des objets.
        # Clean up
        self.DeleteObject(srcdc)
        self.DeleteObject(memdc)
        self.DeleteObject(bmp)
 
        # Apparemment la récupération peut échouer et on peut vérifier
        # cet échec en checkant la longueur. Après la cause de l'échec est
        # un mystère
        if bits != height or len(pixels.raw) != buffer_len:
            raise ValueError('MSSWindows: GetDIBits() failed.')
 
        # Réorganise les bits dans le bonne ordre et converti le format
        # de couleur de BGR vers RGB
 
        # Note that the origin of the returned image is in the
        # bottom-left corner, 32-bit aligned. And it is BGR.
        # Need to "arrange" that.
        return self._arrange(pixels.raw, good_width, height)
 
 
    def _arrange(self, data, width, height):
        ''' Reorganises data when the origin of the image is in the
            bottom-left corner and converts BGR triple to RGB. '''
 
        self.debug('_arrange')
 
        # On crée une nouvelle liste pleine de zéro, et on la rempli
        # avec la nouvelle position des pixels.
        total = width * height
        scanlines = [b'0'] * total
        for y in range(height):
            off = width * (y + 1)
            offset = total - off
            for x in range(0, width - 2, 3):
                # On inverse aussi la position du bleu et du rouge
                scanlines[off+x:off+x+3] = b(data[offset+x+2]), b(data[offset+x+1]), b(data[offset+x])
        return b''.join(scanlines)
 
# Un wrapper pour dumper un array de pixels dans un fichier
# Typiquement un truc qui aurait pu tenir dans une fonction au lieu d'une classe.
 
class MSSImage(object):
    ''' This is a class to save data (raw pixels) to a picture file.
    '''
 
    def __init__(self, data=None, width=1, height=1):
        self.data = data
        self.width = int(width)
        self.height = int(height)
 
        if self.data is None:
            raise ValueError('MSSImage: no data to process.')
        elif self.width < 1 or self.height < 1:
            raise ValueError('MSSImage: width or height must be positive.')
 
 
    # Tout le boulot se passe ici.
 
    def dump(self, output):
        ''' Dump data to the image file.
            Pure python PNG implementation.
            Image represented as RGB tuples, no interlacing.
            http://inaps.org/journal/comment-fonctionne-le-png
        '''
 
        # On ouvre le fichier en mode écriture binaire.
 
        with open(output, 'wb') as fileh:
            # Pour cette partie il faut connaitre les subtilités du format PNG
            # pour comprendre, ce qui n'est pas mon cas. Donc je vais faire
            # de la déduction au gros doigt mouillé.
 
            # Ca prend les données en pixel, ça en fait des morceaux de la bonne taille,
            # organisés en rangés de pixels puisqu'il y a range(height) rangés.
            to_take = (self.width * 3 + 3) & -4
            padding = 0 if to_take % 8 == 0 else (to_take % 8) // 2
            height, data = self.height, self.data
            scanlines = [b''.join([b'0', data[to_take*y:to_take*y+to_take-padding]]) for y in range(height)]
 
            # les "magic bytes", le marqueur du début de fichier qui indique
            # que c'est un fichier png
            magic = pack(b'>8B', 137, 80, 78, 71, 13, 10, 26, 10)
 
            # Les metadata du fichiers : taille de l'image, une somme de
            # controle, la profondeur de couleur, méthode de compression,
            # le mode d'entrelacement, etc.
            # Header: size, marker, data, CRC32
            ihdr = [b'', b'IHDR', b'', b'']
            ihdr[2] = pack(b'>2I5B', self.width, self.height, 8, 2, 0, 0, 0)
            ihdr[3] = pack(b'>I', zlib.crc32(b''.join(ihdr[1:3])) & 0xffffffff)
            ihdr[0] = pack(b'>I', len(ihdr[2]))
 
            # l'image en elle même avec un marker de départ, les pixels
            # et une somme de controle
            # Data: size, marker, data, CRC32
            idat = [b'', b'IDAT', b'', b'']
            idat[2] = zlib.compress(b''.join(scanlines), 9)
            idat[3] = pack(b'>I', zlib.crc32(b''.join(idat[1:3])) & 0xffffffff)
            idat[0] = pack(b'>I', len(idat[2]))
 
            # Les metadata à la fin du fichier qui sont vides.
            # Footer: size, marker, None, CRC32
            iend = [b'', b'IEND', b'', b'']
            iend[3] = pack(b'>I', zlib.crc32(iend[1]) & 0xffffffff)
            iend[0] = pack(b'>I', len(iend[2]))
 
            # On écrit le fichier, et on retourne son nom
            fileh.write(magic + b''.join(ihdr) + b''.join(idat) + b''.join(iend))
            return output
        return None
 
# Un exemple d'usage avec l'habituel if __name__ pour éviter de
# le lancer à l'import
if __name__ == '__main__':
 
    systems = {
        'Darwin' : MSSMac,
        'Linux'  : MSSLinux,
        'Windows': MSSWindows
    }
    try:
        MSS = systems[system()]
    except KeyError:
        err = 'System "{0}" not implemented.'.format(system())
        raise NotImplementedError(err)
 
    try:
        mss = MSS(debug=False)
 
        # One screen shot per monitor
        for filename in mss.save():
            print('File "{0}" created.'.format(filename))
 
        # A shot to grab them all :)
        for filename in mss.save(oneshot=True):
            print('File "{0}" created.'.format(filename))
    except Exception as ex:
        print(ex)
        raise
]]>
http://sametmax.com/explication-de-code-python-mss/feed/ 9
Ouvrir un fichier avec le bon programme en Python 16 http://sametmax.com/ouvrir-un-fichier-avec-le-bon-programme-en-python/ http://sametmax.com/ouvrir-un-fichier-avec-le-bon-programme-en-python/#comments Thu, 17 Oct 2013 10:09:59 +0000 http://sametmax.com/?p=7469 Votre logiciel doit permettre d’ouvrir un fichier avec un programme externe. Oui mais lequel ?

Les OS ont des réglages par défaut pour chaque type de fichier, et on peut demander “ouvrir le prog pour ce type de fichier par défaut”. Par exemple, moi, si je demande d’ouvrir un fichier vidéo, je m’attend à ce que VLC soit lancé.

Voilà comment faire ça en Python :

import subprocess
import sys
import os
 
def run_file(path):
 
    # Pas de EAFP cette fois puisqu'on est dans un process externe,
    # on ne peut pas gérer l'exception aussi facilement, donc on fait
    # des checks essentiels avant.
 
    # Vérifier que le fichier existe
    if not os.path.exists(path):
        raise IOError('No such file: %s' % path)
 
    # On a accès en lecture ?
    if hasattr(os, 'access') and not os.access(path, os.R_OK):
        raise IOError('Cannot access file: %s' % path)
 
    # Lancer le bon programme pour le bon OS :
 
    if hasattr(os, 'startfile'): # Windows
        # Startfile est très limité sous Windows, on ne pourra pas savoir
        # si il y a eu une erreu
        proc = os.startfile(path)
 
    elif sys.platform.startswith('linux'): # Linux:
        proc = subprocess.Popen(['xdg-open', path], 
                                 # on capture stdin et out pour rendre le 
                                 # tout non bloquant
                                 stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
    elif sys.platform == 'darwin': # Mac:
        proc = subprocess.Popen(['open', '--', path], 
                                stdout=subprocess.PIPE, stderr=subprocess.PIPE)
 
    else:
        raise NotImplementedError(
            "Your `%s` isn't a supported operatin system`." % sys.platform)
 
    # Proc sera toujours None sous Windows. Sous les autres OS, il permet de
    # récupérer le status code du programme, and lire / ecrire sur stdin et out
    return proc

C’était le petit snippet sympas du jour !

P.S : si quelqu’un utilise BDSM BSD ou Solaris, je veux bien qu’il complète le snippet.

]]>
http://sametmax.com/ouvrir-un-fichier-avec-le-bon-programme-en-python/feed/ 16
Tiens, je suis toujours sur OSX 81 http://sametmax.com/tiens-je-suis-toujours-sur-osx/ http://sametmax.com/tiens-je-suis-toujours-sur-osx/#comments Wed, 19 Jun 2013 11:23:42 +0000 http://sametmax.com/?p=6419 Ceci est un post invité de coyote posté sous licence creative common 3.0 unported.

Attention: ce qui suit est un billet d’humeur personnel. Prenez le comme tel…

Je suis un linuxien. Pas un activiste, mais tout de même un fervent défenseur du logiciel libre et de Linux (oui pour moi écrire GNU/Linux partout c’est pédant et inutile – et ça te fait passer pour un gros nazi.). J’ai même créé une association de défense du libre, un LUG et fait switcher de très nombreuses personnes.

Mais alors, bordel, que fais-je sur1 OSX ?

1 Si quelqu’un a une règle pour décrire rapidement que l’on utilise un OS, en lieu et place de “sur Ubuntu”, “sous Windows’”, etc ; qu’il la balance car c’est juste insupportable.

Tout à commencé sans doute quand j’ai découvert Sublime Text (merci Sam!). J’ai alors rompu mon workflow entièrement libre pour y intégrer un outil proprio, mais fantastique et dont je me suis précipité pour payer la licence afin de remercier le développeur pour cette bouffée d’oxygène.

J’utilise régulièrement des Mac comme machine depuis plusieurs années (2003?) et ce pour plusieurs raisons:

  • C’est propre. Pas de sticker dégueulasse, de look plastique ou autre.
  • C’est une grande marque avec peu de modèles: l’assurance d’avoir un support pour Linux relativement rapidement.
  • C’est de bonne qualité (oui il y a toujours des exceptions), ça dure longtemps et ça se revend pas trop mal.
  • Le connecteur d’alim aimanté ; tous ceux qui ont déjà cassé un laptop à cause de ça comprendront…
  • Je fais un peu de dev multiplateforme (libre!) et il faut toujours tester/ajuster sur OSX.

Me voilà donc avec une machine Mac dont je suis satisfait, faisant tourner un Ubuntu que j’adore avec un dual boot que j’utilise dans les aéroports ou autre car la conso batterie est incomparable entre Ubuntu et OSX.

Linux a toujours eu des petits problèmes, la batterie dont je parlais, le WiFi, le suspend/hibernation, etc. Rien d’insurmontable pour quelqu’un de convaincu.

L’aigle noir

Soudain, je sais plus exactement vers quelle version, je crois que c’était la 10.10, Ubuntu a commencé à devenir de plus en plus pénible et les nouvelles versions n’ont fait qu’accroitre la frustration.

Le passage à Unity a été difficile, et le fait de tester d’autres alternatives (XFCE, Xmonad) permet de se rendre compte à quel point Ubuntu est intégré: beaucoup de composants ne sont plus vraiment interchangeable ou alors au prix de gros efforts de configurations voir de hacks. En bref, Ubuntu, c’est bien si tu y touches pas trop.

Au fil des versions, des nouveaux problèmes, des fonctionnalités perdues, toute tentative montrant à quel point Ubuntu s’écarte du Linux modulaire, j’ai décidé de franchir le Rubicon et de tester d’autres distros.

Pour faire (très) court :

  • Toutes les non-debian ne m’ont pas plu: la gestion des paquets est contre-productive pour moi qui ait des années d’apt dans les dents. Surtout, c’est lent comme la mort. Incroyablement lent en 2013 ; WTF?
  • Les Debian-Ubuntu-based: la plupart sont des customisations d’Ubuntu avec des paquets différents, des thèmes et des outils. J’ai pas vu d’avantages vraiment et toutes ont des petits désagréments supplémentaires, sans compter que les problèmes spécifiques sont dur à résoudre car ils ont peu d’utilisateurs.
  • Debian ; j’ai beaucoup aimé car c’est très simple, la modularité est là, la vitesse aussi ; c’est bien intégré. Malheureusement, la gestion non souple des paquets (soit tout est vieux, soit tout est jeune) couplé avec l’absence de PPA (les PPA, c’est génial!) font que j’ai renoncé.

Archlinux

Je suis donc resté sur Archlinux. J’avais utilisé un peu par le passé et ça correspondait bien à ce que je voulais actuellement: quelque chose de propre, documenté où je savais qu’en cas de problème, je pourrais l’identifier facilement.

Archlinux m’a pris du temps pour le configurer. C’est sans doute ce qui m’a le plus dérangé car j’utilise mon laptop pour travailler, pas pour geeker. Une journée de perdue à configurer la machine, c’est une journée de travail en moins avec des conséquences en sousous.

Mais j’étais satisfait d’Archlinux, c’était effectivement beaucoup plus simple et clair qu’Ubuntu. J’ai pu faire fonctionner des petites choses qui marchaient pas avec Ubuntu ; j’avais un Gnome à jour, etc.

Mais, car il y a toujours un mais ; Archlinux a aussi ses problèmes: il veut que vous soyez à jour, et vous avez pas intérêt à le contrarier ; mais en même temps, il faut vérifier ce qu’il fait donc re-perte de temps.

Au fil du temps, les problèmes des logiciels récents se faisaient plus frustrants ; une version qui marche ; une qui marche pas, etc.

Quand je me suis rendu compte que je devais redémarrer ma machine plus d’une fois par semaine, et que je perdais vraiment du temps avec des bêtises, je me suis dit “oh et puis merde, je vais mettre OSX!” (oui je reste relativement poli dans ma tête).

Le trou noir

J’ai formatté ma machine sur un coup de tête, un week-end, pour voir si vraiment Linux devenait de la fiente d’âne ou si c’était partout pareil. Je me suis dis, ce sera l’occasion de voir à quoi ressemble la concurrence, de voir comment se comporte cette machine dans son environnement naturel (genre voir ce que ça fait d’utiliser un SSD qui a couté la peau du Q). Je voulais tester ça une semaine, deux tout au plus.

C’était il y a des mois déjà. Je sais plus trop quand. Janvier ? Février ? Je ne m’en souviens pas car je ne pense plus à ma machine. Je l’oublie complètement.

Bien sûr, au début, je raillais ce système et ses perversions (installer GCC nécessite de passer par un Store à la con), et puis, passé les deux premiers jours de setup, je l’ai oublié. Le matin, j’arrive au boulot, la machine est là, allumée, prête, rapide, disponible. Je travaille, et je lui cloue le bec en partant, chose qui ne marchait pas avant.

Je peux me promener avec sans craindre pour la batterie, je peux utiliser le Wifi à tout moment (pas besoin de patcher le kernel dans un hôtel), je peux faire de la visio-conf sans jongler avec 2 outils PulseAudio, le trackpad marche à merveille, le tout démarre en moins de 10s, j’ai mon Sublime Text, mon terminal, mon ipython et toute sa clique, bref, c’est le bonheur.

Je ne sais pas quand je vais retourner sous Linux ; la simple idée de me retaper le setup à l’envers alors que je suis si productif maintenant me donne la nausée. Je n’ai même pas été tenté à la sortie du 13.04. Tout juste ais-je pris le temps de lancer le Live-CD dans VirtualBox.

Bien sûr, tout n’est pas rose, je connais bien les arcanes de Linux, mais pas celles d’OSX ; dès qu’il y a une dépendances bizarre dans un projet (genre opencv), je prends peur car tout n’est pas aussi bien packagé, ou aussi facilement compilable, etc mais finalement, ça n’arrive pas très souvent.

Vous m’avez lu jusqu’ici, bravo, je n’ai pas écrit tout ça pour vous convaincre de quoi que ce soit ou pour me justifier. Je suis toujours autant dérangé par Apple, je ne me sens pas à l’aise en utilisant OSX, mais je peux enfin utiliser ma machine comme l’outil de dev qu’il devrait être et rien d’autre ; c’est un soulagement très grand.

J’espère retrouver dans les commentaires de ce billet vos frustrations concernant Linux et pourquoi pas ce que vous faites pour y remédier (Punching Ball?).

]]>
http://sametmax.com/tiens-je-suis-toujours-sur-osx/feed/ 81
Synchroniser son serveur avec ntp sous Linux 4 http://sametmax.com/synchroniser-son-serveur-avec-ntp-sous-linux/ http://sametmax.com/synchroniser-son-serveur-avec-ntp-sous-linux/#comments Wed, 15 May 2013 07:32:53 +0000 http://sametmax.com/?p=1086 NTP (Network Time Protocol) est un protocole utilisé pour synchroniser l’heure de votre système en utilisant un serveur en ligne. Cet article explique comment l’installer et le configurer sur un système d’exploitation Linux.

1. Installation

Pour installer le service ntp, ouvrez un terminal en root.

Sous Ubuntu:

sudo apt-get install ntp


2. Utilisation

La configuration de ntp se trouve généralement dans le fichier /etc/ntp.conf. On peut y ajouter de nouveaux serveurs de temps, en ajoutant une ligne similaire à celle-ci par exemple :

server 0.fedora.pool.ntp.org dynamic

Démarrez ensuite le service avec cette commande afin de synchroniser la date et l’heure (Attention, il faut plusieurs minutes avant que l’heure soit synchronisée) :

/etc/init.d/ntpd start

On pourra ensuite vérifier que l’heure est correcte avec la commande “date”:

[sm@web1 ~]# date
Wed May 15 07:21:15 UTC 2013
[sm@web1 ~]#

Note : en cas de problème, on pourra vérifier le bon fonctionnement avec la commande ntpstat qui donnera des informations sur le statut du service ntp :

[sm@web1 ~]# ntpstat
synchronised to NTP server (213.161.194.93) at stratum 4
time correct to within 562 ms
polling server every 64 s

A quoi ça sert ?

Dans notre cas nous avons des sites qui hébergent du contenu avec des urls qui expirent au bout d’un certainstemps.
La création de ces urls à durée de vie comptée est faite depuis le serveur web, le contenu se trouvant sur d’autres serveurs.
ntp dans ce cas nous assure que tous les serveurs seront calés sur la même heure, ce qui évite de générer des urls déjà expirées si un des serveurs de contenu n’était pas à l’heure.

]]>
http://sametmax.com/synchroniser-son-serveur-avec-ntp-sous-linux/feed/ 4
Les usages légaux de BitTorrent 11 http://sametmax.com/les-usages-legaux-de-bittorrent/ http://sametmax.com/les-usages-legaux-de-bittorrent/#comments Sat, 15 Sep 2012 15:08:18 +0000 http://sametmax.com/?p=2094 BitTorrent est diabolisé, mais c’est comme la télévision: ce n’est pas parce que la majorité des gens en font un mauvais usage qu’il faut considérer que c’est une technologie satanique.

Déploiement de code

BitTorrent à cela de merveilleux que plus il y a de personnes qui téléchargent, plus le téléchargement est rapide. Pour cette raison, les grandes entreprises l’utilisent pour déployer leur code en productions sur leurs centaines de serveur, avec des gains de vitesse allant jusqu’à 7500 %.

Twitter et Facebook sont connus pour utiliser le protocole p2p pour économiser exactement ça.

Distribution de patch et vente de jeux

On pense toujours que le peer to peer ce n’est pas pour le kikoo lol moyen, que c’est compliqué. Mais en fait énormément de Kevin de base utilisent BitTorrent sans le savoir.

La raison ?

Capture d'écran du logiciel de mise à jour des jeux Blizzard

Vous jouez à Wow, SC2 ou Diablo 3 ? Vous utilisez BitTorrent !

Blizzard utilise en effet BitTorrent dans son fameux logiciel “Blizzard downloader” pour distribuer les installeurs des jeux vendus sur Battle.net, mais également toutes les mises à jour des dit jeux.

Quand on a des millions de joueurs qui sont du coup autant de serveurs, on économise un MAX de pognon. On parle de millions d’euros là.

Distribution d’iso

Quelqu’un qui surf sur Internet (60% des serveurs Web dans le monde), utilise un téléphone Android ou regarde une vidéo sur sa Freebox utilise Linux. Or l’acquisition d’une ISO d’installation Linux se fait massivement à travers BitTorrent, pour justement éviter de surcharger les éditeurs de distro qui ont un budget limité. Votre serviteur tape d’ailleurs cet article sur une Ubuntu installé depuis une ISO téléchargée via BitTorrent.

D’une manière générale, si on a un gros fichier à partager sur un blog, le mettre à disposition via BitTorrent est ce qu’il y a de plus pratique, ça évite de tuer son hébergement. Pour cette raison, Amazon S3 le supporte par défaut.

Potentiel

Parce que des gens stressés par la mutation des mœurs ne voient pas plus loin que le bout de leur narines, et parce que “BitTorrent, c’est surtout utilisé pour pirater, il faut pas déconner”, cette technologie est largement sous exploitée. On pourrait faire la mise à jour de tous les logiciels avec, même la mise à jour des OS, et carrément l’intégrer dans les gestionnaires de paquets. On pourrait faire du streaming avec: chaque visionneur repartageant des paquets, et allégeant la charge des fournisseurs de VOD.

Si l’usage de BitTorrent se généralise, ce sont des économies à l’échelle du monde qui seront réalisées, mais aussi l’opportunité pour plein de “petits” de mettre à disposition du gros contenu pour le grand public. Ce serait fabuleux que tous les navigateurs intègrent le téléchargement BitTorrent par défaut, comme le fait Opéra (qui est d’ailleurs aussi téléchargeable en torrent). Parce que pour le moment, allez proposer à mamie de télécharger un truc avec BitTorrent… J’ai expliqué à mon père il y a quelques semaines, lui qui pourtant sait ce que c’est qu’un cable SCSI et qui a été le premier à utiliser CloneCD en son temps, il a eu du mal.

Pour le moment, les ports sont souvent bloqués, le protocole est souvent bridé, les applications censurées sur les appstores, et tout ce potentiel est perdu.

Chers éditeurs de contenu, même dans le cas improbable ou vous gagniez la bataille de la maîtrise du Net, empêchant les méchants pirates de vous voler de quoi vous acheter votre prochain coupé sport, j’ai toujours un disque dur externe et des amis. Qui en ont aussi.

Donc puisque vous ne pouvez pas arrêter le piratage sans transformer le pays en dictature stalinienne, ayez au moins la décence de laisser tranquille des technologies brillantes et extraordinairement utiles pour tous. C’est peut être un détail pour vous, mais pour nous ça veut dire beaucoup.

 

]]>
http://sametmax.com/les-usages-legaux-de-bittorrent/feed/ 11
Liste des partitions, de leur type, de la place restante sous Linux – df / du 14 http://sametmax.com/liste-des-partitions-de-leur-type-de-la-place-restante-sous-linux-df-du/ http://sametmax.com/liste-des-partitions-de-leur-type-de-la-place-restante-sous-linux-df-du/#comments Mon, 10 Sep 2012 01:36:48 +0000 http://sametmax.com/?p=2049 Quelques commandes sympa sous Linux pour connaître les ressources disque de son serveur.

la commande df:
Retourne l’espace disque utilisé par chaque partition montée sur votre machine.

-h Affiche les résultats sous forme humainement lisible :)
-T indique le type de système de fichiers utilisé

df -hT
Sys. fich.    Type  Taille  Uti. Disp. Uti% Monté sur
/dev/sda2      xfs     20G  2,9G   17G  15% /
tmpfs        tmpfs    3,9G     0  3,9G   0% /dev/shm
/dev/sda1      xfs    192M   28M  164M  15% /boot
/dev/sda4      xfs    1,8T  220G  1,6T  12% /data

la commande du:
Retourne l’espace disque utilisé par un répertoire

-h Affiche les résultats sous forme humainement lisible :)
-s affiche la somme des fichiers du répertoire

du -hs /boot/
18M	/boot/

Et si vous voulez trouver ce qui prend toute cette place, c’est par ici.

Edit:
Pour le côté prog on peu utiliser psutil en python qui permet tout un tas d’acceès aux infos de la machine.
Merci François

]]>
http://sametmax.com/liste-des-partitions-de-leur-type-de-la-place-restante-sous-linux-df-du/feed/ 14