[Nemo] [Cinnamon] « Actions » de Nemo et paramètres transmis à Python.

Postez ici vos scripts Bash, Python, C++, etc...
Répondre
Avatar du membre
smcj
Messages : 37
Enregistré le : mar. 13 janv. 2026 04:34
Localisation : Meymac (19250)
Contact :

[Nemo] [Cinnamon] « Actions » de Nemo et paramètres transmis à Python.

Message par smcj »

 

Bonjour à tous,

Même si, en programmation, je me juge « amateur éclairé » (du latin amator, « celui qui aime »), je suis épouvantablement néophyte en Python. Mais je sais une chose : pour apprendre à coder, il faut … coder. Comme on forge pour devenir forgeron ; ou comme on menuise pour devenir menuisier.

Et accepter de se planter ; au départ, de se trouver sous-doué, ne pas viser à apprendre à conduire avec une Lamborghini, parce que caler avec une 2CV, c’est mieux. Rouler beaucoup en 2CV, à petite vitesse. Ces voyages-là font plus que « former la jeunesse » ; ils font comprendre la dynamique de la bête, beaucoup plus en profondeur que ce que ne permettent que mal les mécaniques trop complexes ou trop puissantes. Dans cette optique, s’attacher à NE PAS faire appel, ne serait-ce qu’une seule fois à ce qui se prétend intelligent ou artificiel (alors les deux ensemble, on oublie), c’est s’accorder à soi-même une confiance, un temps d’apprentissage, dont on sait déjà bien à quel point ce sera, in fine, beaucoup plus efficace et satisfaisant que si l’on était simplement contenté d’un survol consumériste, de ceux dont se profile chaque jour davantage la nécessité de la diminution. J’ai bien conscience d’être beaucoup plus pétri de réflexes « procéduraux » qu’« objets » ; je fais avec, pour l’instant. Par contre, même si l’on va ― forcément ― piocher à droite et à gauche, il y a une obligation à comprendre toutes les lignes de code que l’on prétend produire ; sauf à se persuader que ce soit une bonne chose de ne pas, soi-même, valoir plus qu’une machine ou un tas de ferraille, celui-là-même dont on est persuadé d’avoir pris le contrôle.

Suite à ma participation à ce fil, aussi épique qu’hippique, je me suis dit, au pesé du peu d’exemples disponibles en ligne en général et sur ce forum en particulier (à l’exception notable du fil créé, il y a plus de dix ans, par @ledudulela), de reprendre l’exemple évoqué dans ce fil qui pique, pour faire, non pas un script Bash, mais un script Python, puisqu’aussi bien il me semble que la syntaxe du constricteur, dans son ensemble, est quand même nettement moins étouffante (surprenant non ?)…

Comprendre les subtilités du passage des variables, depuis Nemo, en fonction du langage qui les reçoit demande un certain temps et moult attentions, à fortiori pour quelqu’un qui, comme moi, n’a que peu d’expérience de ces choses… Les fichiers à l’extension .nemo_action sont d’une susceptibilité crasse au regard de la casse ; ne pas y prendre rigoureusement garde, c’est faire caler la 2CV, déjà. Le présent fil ne pose donc aucune question, mais n’est seulement écrit que comme un témoignage de quelqu’un qui s’attache à aimer.

Par exemple, au cours de l’élaboration de mon script Python, le Shebang que j’y avais posé était :

Code : Tout sélectionner

#!/usr/bin/env python
… simplement parce que j’ai lu quelque part que c’était plus-mieux. Mais, avec les Actions, ça ne fonctionne tout simplement pas (en tous les cas sur mon système, dans ma configuration). Il faut donc impérativement avoir un Shebang de type :

Code : Tout sélectionner

#!/usr/bin/python3
Il est également assez peu documenté / usité le fait que les fichiers « .nemo_action » peuvent avoir leur exécution faite dans un terminal, via la commande :

Code : Tout sélectionner

Terminal=true
Et, en cours d’écriture d’un script Python, c’est, pour le moins, un rien utile d’avoir un retour terminal

Parce que c’est, pour moi, plus un script de démonstration et d’apprentissage, évidemment très souvent écrit à partir de choses glanées sur le wouaibe, le script est abondamment commenté. J’ai porté une attention particulière à nommer les variables avec une consonance française, parce que c’est un des rares points où l’on peut se servir de cette spécificité pour faire la différence entre ce qui relève de la stricte nativité du langage (ou des bibliothèques), de ce qui est établi par le scripteur. Il est tout à fait acquis que le fait de faire précéder les noms de variables par des déterminants possessifs (mon, ma, mes) est aussi lourdingue que verbeux. Toutefois, dans un contexte d’apprentissage du code, pour moi, ce n’est juste pas une option. Ainsi, lire

Code : Tout sélectionner

maFenetre = Tk()
monURL = Entry(width=60)
monEtiquette_01 = Label(text="Fichier : ")
plutôt que

Code : Tout sélectionner

tk=Tk()
entry=Entry(width=60)
label=Label(text="Fichier : ")
me semble simplifier la lecture, non au moment de la déclaration de la variable (objet), mais bien, beaucoup plus tard, lors de son utilisation effective.

Évidemment, il ne faut pas attendre de la bibliothèque graphique Tkinter, livrée avec Python, plus que ce qu’elle est capable de faire : à savoir faire des choses basiques. On est (très) loin de ce que peut offrir GTK, voire (surtout) Qt. C’est, par contre, un très bon tremplin pour défricher les bases et il se trouve déjà là un nombre conséquent de contrôles qui, même si leur look frise l’antique, restent parfaitement fonctionnels. Il se trouve même un certain plaisir à faire vivre, encore, ces monuments historiques qui n’ont pas besoin de paraître plus que l’efficience décroissante qu’ils servent…

Même s’il m’est arrivé d’exprimer ailleurs ce principe, je reste toujours aussi émerveillé de voir à quel point il est facile de faire fruit de ce que propose l’UTF-8, tout de même à pas cher en matière de ressources. Les touches fléchées de la boite de dialogue sont ici données à titre d’exemple : je n’utilise pas de fichiers graphiques, mais évidemment bien de simples caractères. Cela présente évidemment le risque que cela s’affiche un peu différemment selon les configurations ; mais dans l’ensemble, c’est, là encore, bougrement efficace.

Le but de ce code est, aussi, de voir à quel point il est facile de récupérer la liste des fichiers sélectionnés pour, par exemple, en donner la possibilité de l’ordonnancement dans une liste déroulante. Ce truc-là est récupérable dans un nombre faramineux de contextes, et en Python, s’avère quand même très facile à mettre en place !

Le simple fait de laisser à l’utilisateur la possibilité de saisir l’URL du fichier de sortie en toutes lettres, autant manuellement, qu’avec la boite de dialogue, complexifie notablement la gestion Python. Mais, ici comme dans le cas de la gestion de la liste déroulante, l’exercice en vaut la peine parce qu’il est évidemment récupérable ailleurs, dans d’autres situations et pour d’autres usages.

Cette expérience m’aura aussi appris que j’ai encore beaucoup à apprendre sur la simple mise en place et disposition des contrôles d’une boite de dialogue TKinter. Parce que ça ne doit pas être insurmontable de faire mieux… Monter une boite de dialogue utilisée par une macro de LibreOffice est l’enfance de l’art ; tout se fait à la souris de manière graphique dans l’éditeur dédié. Là, c’est autre chose : on pourrait croire que c’est aussi simple que de monter un tableau HTML, mais en fait pas tant que ça…

L’installation.
Deux fichiers ; deux destinations :
  • Créer le fichier
    ~/.local/share/nemo/actions/assembleImages.nemo_action
    et le remplir du code suivant :

    Code : Tout sélectionner

    [Nemo Action]
    Active=true
    Terminal=false
    Icon-Name=face-smile
    
    Name=Assembler les images sélectionnées
    Comment=Assembler, en un seul fichier graphique, les images actuellement sélectionnées.
    
    # ATTENTION : l'ordre de l'énoncé « %P %F » est ici IMPORTANT pour la récupération des données par Python. Ne pas mettre « %F %P ».
    Exec=<assembleImgSel/assembleImgSel.py %P %F>
    
    Selection=m
    Extensions=jpg;jpeg;png;bmp;gif;webp;
    
    Dependencies=python3;convert
  • Créer le répertoire
    ~/.local/share/nemo/actions/assembleImgSel
  • Créer le fichier
    ~/.local/share/nemo/actions/assembleImgSel/assembleImgSel.py
    Le rendre exécutable :

    Code : Tout sélectionner

    chmod +x ~/.local/share/nemo/actions/assembleImgSel/assembleImgSel.py
    et le remplir du code suivant :

    Code : Tout sélectionner

    #!/usr/bin/python3
    # -*- coding: utf-8 -*-
    
    import os, sys
    
    from tkinter import *
    from tkinter import filedialog
    from pathlib import Path
    
    monNomFichierGenerique = "assemblage.png"
    monFichierIcone = Path("/usr/share/icons/gnome/48x48/emblems/emblem-photos.png")
    
    
    # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗
    # ║ Fonction de centrage de la fenêtre sur l'écran principal.                                 ║█
    # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█
    #  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
    def centrerAffichageFenetre(ecran : Tk, largeur : int, hauteur: int):
    	largeurEcran = ecran.winfo_screenwidth()    ; hauteurEcran = ecran.winfo_screenheight()
    	x = int((largeurEcran / 2) - (largeur / 2)) ; y = int((hauteurEcran / 2) - (hauteur / 1.5))
    
    	return f"{largeur}x{hauteur}+{x}+{y}"
    
    
    # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗
    # ║ Appel de la boite de dialogue de saisie du fichier à écrire.                              ║█
    # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█
    #  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
    def saisieMonFichierFinal() :
    	# Récupérer le texte saisi dans le champ « monURL ».
    	monCheminSaisi = monURL.get() ; monChemin = ""
    
    	if Path(monCheminSaisi).is_file() or Path(monCheminSaisi).parent.is_dir() : monChemin = Path(monCheminSaisi).parent
    	if Path(monCheminSaisi).is_dir()                                          : monChemin = monCheminSaisi
    	if monChemin == ""                                                        : monChemin = Path.home()
    
        # Attribution du nom du fichier initial.
    	if Path(monCheminSaisi).is_dir() :
    		monFichierInitial = monNomFichierGenerique
    	else :
    		monFichierInitial = Path(monCheminSaisi).name
    
    	monFichierFinal = filedialog.asksaveasfilename(
    	title       = "Enregistrer sous…",
    	filetypes   = (("PNG", "*.png"), ("JpeG", "*.jpg"), ("BMP", "*.bmp"), ("TIFF", "*.tif"), ("Tous les fichiers", "*.*")),
    	initialdir  = monChemin,
    	initialfile = monFichierInitial )
    
    	# Validation effective si la saisie est correcte.
    	if monFichierFinal :
    		monURL.delete(0, "end") ; monURL.insert(0, monFichierFinal)
    
    
    # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗
    # ║ Monter et descendre un choix sélectionner dans la liste déroulante.                       ║█
    # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█
    #  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
    
    def maGestionBoutonsMonterDescendre(monEvenement) :
    	maSelection = maListeFichiers.curselection()
    	if maSelection :
    		monIndex = maSelection[0] + 1                                  # Base de l'index : 1.
    		monTotal = maListeFichiers.index("end")
    
    		monBoutonMonter    ["state"] = "normal" if monIndex > 1        else "disabled"
    		monBoutonDescendre ["state"] = "normal" if monIndex < monTotal else "disabled"
    	else :
    		monBoutonMonter["state"] = "disabled" ; monBoutonDescendre["state"] = "disabled"
    
    def monterDescendre(monSens) :
    	maSelection = maListeFichiers.curselection()
    	if maSelection :
    		monIndex = maSelection[0]                                      # Base de l'index : 0.
    		monTotal = maListeFichiers.index("end") - 1
    		monChoixActif = maListeFichiers.get(monIndex)
    		maListeFichiers.delete(monIndex)
    
    		# Monter [▲] et Descendre [▼].
    		maListeFichiers.insert(monIndex + monSens , monChoixActif)
    		maListeFichiers.selection_set(first = monIndex + monSens) ; maListeFichiers.activate(monIndex + monSens)
    		monIndex += monSens                                            # Car, qui peut le plus, peut le moins.
    
    		# Gestion de la disponibilité des boutons « Monter [▲] » et « Descendre [▼] ».
    		monBoutonMonter    ["state"] = "normal" if monIndex > 0        else "disabled"
    		monBoutonDescendre ["state"] = "normal" if monIndex < monTotal else "disabled"
    
    
    # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗
    # ║ Assemblage effectif des image avec le logiciel externe « ImageMagick ».                   ║█
    # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█
    #  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
    def assemblerImages() :
    	# La forme de la commande sera « convert +append "*.jpg" "sortie.jpg" », avec des chemins absolus et sans caractères génériques.
    	maCommande = f"convert {monOrientation.get()}append "
    
    	# Avec « join », assemble en une seule chaine et, pour chaque fichier, rajoute « monDossierActif » (plus un slash) en encadrant le chemin total de guillemets.
    	mesFichiersEnOrdre = f"\"{monDossierActif}/" + f"\" \"{monDossierActif}/".join(maListeFichiers.get(0, "end")) + "\""
    
        # Rajout à « maCommande », non seulement « mesFichiersEnOrdre », mais aussi l'URL de la destination.
    	maCommande += f"{mesFichiersEnOrdre} \"{monFichierFinalSaisi.get()}\""
    
    	# Appel du Shell.
    	os.system(maCommande)
    
    
    # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗
    # ║ Récupération des données passées en arguments via le clic droit.                          ║█
    # ║ Il est IMPÉRATIF que le fichier appelant ait bien respecté l'ordre de l'énoncé « %P %F ». ║█
    # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█
    #  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
    
    mesArguments = []
    monAppel = sys.argv[0] # Le nom-même du présent fichier ; inutile sans le cas présent.
    
    for monArgument in sys.argv :
        if monArgument == monAppel :
            continue
        else:
            mesArguments.append(monArgument)
    
    # Supprime mesArguments[0] car il est passé dans la variable « monDossierActif » et devenait gênant lors du remplissage de la Listbox « maListeFichiers ».
    monDossierActif = mesArguments[0] ; mesArguments.pop(0)
    monFichierFinal = Path(monDossierActif, monNomFichierGenerique).as_posix()
    
    
    # ╔═══════════════════════════════════════════════════════════════════════════════════════════╗
    # ║ Montage de la boite de dialogue avec « TKinter ».                                         ║█
    # ╚═══════════════════════════════════════════════════════════════════════════════════════════╝█
    #  ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
     
    #        ╔════════════════════════════════════════════════════════════════════════════════════╗
    #        ║                                Assemblage d'images.                                ║█
    #        ╠════════════════════════════════════════════════════════════════════════════════════╣█
    #        ║  ┌──────────────────────────────────────────────────────────────── monCadre_01 ─┐  ║█
    #        ║  │ Fichier de sortie :                                                          │  ║█
    #        ║  ├─────────────────────────────────────────────────────────┬────────────────────┤  ║█
    #        ║  │  ┌───────────────────────────────────────────────────┐  │  ┌──────────────┐  │  ║█
    #        ║  │  │ Saisie de l'URL.                                  │  │  │ Choisir...   │  │  ║█
    #        ║  │  └───────────────────────────────────────────────────┘  │  └──────────────┘  │  ║█
    #        ║  └─────────────────────────────────────────────────────────┴────────────────────┘  ║█
    #        ║  ┌──────────────────────────────────────────────────────────────── monCadre_02 ─┐  ║█
    #        ║  │ Dossier actif et fichiers sélectionnés :                                     │  ║█
    #        ║  ├──────────────────────────────────────────────────────────────────────────────┤  ║█
    #        ║  │ URL des fichiers sélectionnés.                                               │  ║█
    #        ║  ├──────────────────────────┬─────────┬─────────────────────────────────────────┤  ║█
    #        ║  │                          │  ┌───┐  │  [0] Vertical                           │  ║█
    #        ║  │ Liste déroulante         │  │ ▲ │  ├─────────────────────────────────────────┤  ║█
    #        ║  │ des fichiers             │  └───┘  │  [O] Horizontal                         │  ║█
    #        ║  │ des fichiers             ├─────────┼────────────────────┬────────────────────┤  ║█
    #        ║  │ graphiques               │  ┌───┐  │  ┌──────────────┐  │  ┌──────────────┐  │  ║█
    #        ║  │ sélectionnés.            │  │ ▼ │  │  │  Assembler   │  │  │   Quitter    │  │  ║█
    #        ║  │                          │  └───┘  │  └──────────────┘  │  └──────────────┘  │  ║█
    #        ║  └──────────────────────────┴─────────┴────────────────────┴────────────────────┘  ║█
    #        ╚════════════════════════════════════════════════════════════════════════════════════╝█
    #         ▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀▀
    
    # Création de la boite de dialogue principale.
    maFenetre = Tk()
    maFenetre.title("Assemblage d'images")
    maFenetre.geometry(centrerAffichageFenetre(maFenetre, 850, 410)) 
    
    # Icône de la boite de dialogue SI le fichier est présent.
    if monFichierIcone.exists() : maFenetre.iconphoto(True, PhotoImage(file = monFichierIcone))
    
    # Polices de caractères personnalisées.
    maPoliceDeCaracteresFixe       = ("Mono"     , 12, "bold"  )
    maPoliceDeCaracteresStandard   = ("Helvetica", 14, "normal")
    maPoliceDeCaracteresIconesUTF8 = ("Helvetica", 20, "normal")
    
    # Premier cadre (monCadre_01).
    monCadre_01 = Frame(maFenetre, padx=5, pady=5)
    monCadre_01.pack()
    
    # Label du fichier de sortie.
    monEtiquette_01 = Label(monCadre_01, text="Fichier de sortie : ", font=maPoliceDeCaracteresStandard)
    monEtiquette_01.grid(row=0, column=0, columnspan=2, sticky="w")
    
    # Champ de saisie (monURL) où l'utilisateur peut saisir une URL de fichier.
    monFichierFinalSaisi = StringVar()
    monURL = Entry(monCadre_01, width=60, bg="#fff7e7", fg="green", font=maPoliceDeCaracteresFixe, textvar=monFichierFinalSaisi)
    monURL.insert(0, monFichierFinal)
    monURL.focus()
    monURL.grid(row=1, column=0, padx=5, pady=5, sticky="w")
    
    # Bouton qui lance la boite de dialogue « Enregistrer sous… ».
    monBoutonEnregistrerSous = Button(monCadre_01, text="Choisir…", command=saisieMonFichierFinal, font=maPoliceDeCaracteresStandard)
    monBoutonEnregistrerSous.grid(row=1, column=1, padx=20, pady=5, sticky = "e")
    
    # Second cadre (monCadre_02).
    monCadre_02 = Frame(maFenetre, padx=10, pady=5)
    monCadre_02.pack()
    
    # Label du dossier actif.
    monEtiquette_02 = Label(monCadre_02, text="\nDossier actif et fichiers sélectionnés :", font=(maPoliceDeCaracteresStandard))
    monEtiquette_02.grid(row=0, column=0, columnspan=4, sticky="w")
    
    monEtiquette_03 = Label(monCadre_02, text=monDossierActif + "\n", font=(maPoliceDeCaracteresFixe))
    monEtiquette_03.grid(row=1, column=0, columnspan=3, sticky="w")
    
    # Liste déroulante des fichiers graphiques sélectionnés.
    maListeFichiers = Listbox(monCadre_02, height=10, width=50, bg="#fff7e7", fg="darkblue", font=maPoliceDeCaracteresFixe)
    for monElement in mesArguments : maListeFichiers.insert("end", Path(monElement).name)
    maListeFichiers.bind('<<ListboxSelect>>', maGestionBoutonsMonterDescendre)
    maListeFichiers.grid(row=2, column=0, rowspan=3, pady=10, sticky = "w")
    
    # Bouton « Monter [▲] » (un item de choix de la liste déroulante).
    monBoutonMonter = Button(monCadre_02, text = "⮬", command=lambda : monterDescendre(-1), fg="darkblue", font=maPoliceDeCaracteresIconesUTF8)
    monBoutonMonter["state"] = "disabled"
    monBoutonMonter.grid(row=2, column=1, padx=5, rowspan=2, sticky = "ws")
    
    # Bouton « Descendre [▼] » (un item de choix de la liste déroulante).
    monBoutonDescendre = Button(monCadre_02, text = "⮯", command=lambda : monterDescendre(1), fg="darkblue", font=maPoliceDeCaracteresIconesUTF8)
    monBoutonDescendre["state"] = "disabled"
    monBoutonDescendre.grid(row=4, column=1, padx=5, pady=40, sticky = "wn")
    
    # Boutons radio de sélection « Horizontal » ou « Vertical ».
    monOrientation = StringVar() ; monOrientation.set("+")
    monRBOrientation_01 = Radiobutton(monCadre_02, text="Horizontal", variable=monOrientation, value="+", font=maPoliceDeCaracteresStandard)
    monRBOrientation_01.grid(row=2, column=2, columnspan=2, padx=20, pady=5, sticky = "ws")
    
    monRBOrientation_02 = Radiobutton(monCadre_02, text="Vertical", variable=monOrientation, value="-", font=maPoliceDeCaracteresStandard)
    monRBOrientation_02.grid(row=3, column=2, columnspan=2, padx=20, pady=5, sticky = "wn")
    
    # Bouton « Assembler ».
    monBoutonAssembler = Button(monCadre_02, text = "Assembler", command=assemblerImages, font=maPoliceDeCaracteresStandard)
    monBoutonAssembler.grid(row=4, column=2, padx=10, pady=5, sticky = "ws")
    
    # Bouton « Quitter ».
    monBoutonQuitter = Button(monCadre_02, text = "Quitter", command=maFenetre.destroy, font=maPoliceDeCaracteresStandard)
    monBoutonQuitter.grid(row=4, column=3, padx=10, pady=5, sticky = "es")
    
    # Lancer la boucle principale de l'application TKinter.
    maFenetre.mainloop()
Si on veut faire plus simple, dans un terminal, il est aussi possible de taper la commande

Code : Tout sélectionner

monF="assembleImgSel.tar.gz";wget "https://roubo.art/documents/GNU-Linux-Mint/$monF" ; tar -xf ./$monF -C ~/.local/share/nemo/actions/ ; rm ./$monF
qui rapatrie, depuis un espace qui n’a rien à voir avec le présent forum, un fichier « assembleImgSel.tar.gz » (3 953 octets) contenant lui-même :
  • Le fichier « assembleImages.nemo_action », non déclaré exécutable.
  • Le dossier « assembleImgSel ».
  • Dans ce dossier « assembleImgSel », le script Python « assembleImgSel.py » qui, lui, est déclaré exécutable.
et qui fait le boulot de mettre tout ce petit monde en place dans
~/.local/share/nemo/actions/

Dans tous les cas et pour que cela fonctionne pleinement, il faut évidemment qu’ImageMagick soit installé et que la commande « convert » soit disponible. Ce dernier point est une condition sine qua non, déjà imposée par le fichier « assembleImages.nemo_action » (Dependencies).

Pour faire marcher le bouzin, depuis le navigateur de fichier Némo, on sélectionne un certain nombre de fichiers graphiques, souvent deux, bien qu’il soit théoriquement possible d’en sélectionner beaucoup plus. Dans tous les cas, la sélection doit être multiple. Un clic droit amène alors à l’option « Assembler les images sélectionnées » dans le menu contextuel.

Image

Apparaît alors la boite de dialogue « TKinter ». L’ordonnancement des fichiers se comprend de manière intuitive avec les boutons qui jouxtent la liste. On fait monter ou descendre le fichier au nom sélectionné. De même pour les choix « Horizontal » ou « Vertical » dont ImageMagick fait finalement fruit. On termine le boulot par le bouton « Assembler » dont je n’ai, volontairement, pas fait fermer automatiquement la boite de dialogue tant il peut être, aussi et dans ce cas-là, utile de pouvoir faire plusieurs assemblages (dans un autre ordre, dans un autre format, à un emplacement différent, &c.).

Image

On imagine aisément la déclinaison de ce principe, quittant ImageMagick pour, par exemple (et pour ne citer que lui), PFKtk, non pour recréer un logiciel, mais pour s’adapter à une ou deux opérations que l’on fait couramment. Il existe d’ailleurs, déjà écrites, un nombre non négligeable d’Actions relatives au PDF dont l’« interface graphique » est généralement assurée par Zenity. C’est d’ailleurs en écrivant ces lignes et via sa page Wikipedia anglophone, que j’ai découvert qu’il se trouve même une bibliothèque Python PyZenity qui lui est dédié (documentation ici). On n’arrête pas le progrès…

Dans tous les cas, et sous Linux Mint-Cinnamon, on gagne énormément à explorer et comprendre trois « places » :
  1. Le dossier ~/.local/share/nemo/actions/ et ses fichiers à l’extension .nemo_action
  2. « Paramètres du système » / « Préférences » / « Actions ». Les trois onglets « Gérer », « Télécharger » et « Disposition » sont un régal de découverte. On se rend compte ici qu’il est même possible d’organiser les Actions en sous-menus, avec séparateurs éventuels, dans le menu contextuel.
    • Petit bug noté : si on attribue un raccourci clavier à une Action, il n’est alors plus pris en compte les conditions de la sélection exprimées dans le fichier à l’extension .nemo_action
    • Petite notation supplémentaire : l’organisation de ce menu contextuel est écrite dans le fichier : ~/.config/nemo/actions-tree.json, à la structure très explicite, ce qui laisse évidemment imaginer une extrême simplicité de programmation dans le cadre de l’installation d’un logiciel quelconque, dédié à ce système d’exploitation.
  3. Dans Nemo, menu « Édition » / « Préférences » / « Greffons » est à connaître de manière impérative, car il peut être fait des téléchargements d’Actions qui ne s’activent pas de manière automatique ; et c’est à cet emplacement que cela se coche.

En résumé et pour conclure, le concept d’« Actions », provoquées par un clic droit sur des fichiers sélectionnés, devient incroyablement productif puisque cela répond, précisément, à une demande extrêmement ciblée de l’utilisateur. Si je prends plaisir à « vendre » Linux en général et Mint en particulier, c’est d’abord pour ce genre de choses : le fait que jamais je n’ai vu Micro$lop atteindre cette écoute de l’utilisateur. Là, c’est un produit (Nemo) en apparence très simple, voire banale, qui montre une supériorité productique sans pareille, à très bas prix. Il faut, certes, avoir des notions de programmation, mais on peut, encore, se satisfaire de ne rester qu’un amateur.

Enjoy !
:-)
| HP Pavilion Gaming Desktop 690-00xx | Linux Mint 22.3 Zena | Cinnamon : 6.6.7 | X11 | Kernel 6.8.0-111-generic arch: x86_64 | Intel Core i5-9400F bits : 64 type : MCP | NVIDIA GP107 [GeForce GTX 1050] | Ram : 8 GiB |
Avatar du membre
michLapprenti
Messages : 1105
Enregistré le : sam. 20 août 2016 20:11

Re: [Nemo] [Cinnamon] « Actions » de Nemo et paramètres transmis à Python.

Message par michLapprenti »

Bonjour smcj
smcj a écrit : mar. 28 juil. 2026 19:10 Suite à ma participation à ce fil, aussi épique qu’hippique
Ravi d'être un peu à l'origine de votre production de concepts époustouflants à mes yeux et cervelle hagards, mais certainement évidents à qui sait ou saura s'en servir - merci pour Elles et Eux :P
Pour accompagner en musique cet exposé :arrow: https://www.youtube.com/watch?v=P2kK1ctxVaA
Distrib : Linux Mint 22.3 Desktop : X-Cinnamon Lenovo T480S - SSD 500go
Kernel : 6.17.0-35-generic CPU : Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz
RAM : 15Gi CG : Intel Corporation UHD Graphics 620 (rev 07)
Avatar du membre
Jennatux
Messages : 3290
Enregistré le : mer. 1 nov. 2017 01:59

Re: [Nemo] [Cinnamon] « Actions » de Nemo et paramètres transmis à Python.

Message par Jennatux »

J'ai ajouté une action a nautilus qui fait ce collage
Fujitsu : Ubuntu 26.04 - Xubuntu 26.04 - Ubuntu Cosmic 24.04
Clevo : Ubuntu 26.04 - Xubuntu et Lubuntu 26.04 - Linux Mint 22.3 Cinnamon et xfce

Avec partition data ..................... Partition data en Multiboot EFI

Une fraction de seconde..................................Animations
Avatar du membre
smcj
Messages : 37
Enregistré le : mar. 13 janv. 2026 04:34
Localisation : Meymac (19250)
Contact :

Re: [Nemo] [Cinnamon] « Actions » de Nemo et paramètres transmis à Python.

Message par smcj »

Bonjour à tous,
michLapprenti a écrit : mar. 28 juil. 2026 22:11Ravi d'être un peu à l'origine de votre production
Pas « un peu » Michel ; entièrement ! C’est aussi pour cela que, même s’il est entendu que vous puissiez avoir quelques difficultés à naviguer dans des contrées qui nous sont plus familières, il est ici fait la démonstration qu’aucune requête n’est à rejeter, tant elle peut, aussi, inspirer beaucoup plus que leur simple résolution.
michLapprenti a écrit : mar. 28 juil. 2026 22:11[...] certainement évidents à qui sait ou saura s'en servir
Mais vous-même Michel ! Je vous ai déjà lu, ailleurs, parfaitement maîtriser le terminal pour y copier-coller des commandes que l’on vous suggérait. La preuve a été faite que vous avez parfaitement installé ImageMagick. Donc à-peu-près tout ce qui se trouve entre « Si on veut faire plus simple, dans un terminal […] » jusqu’à « […] à un emplacement différent, &c.). » de mon premier post du présent fil est parfaitement compréhensible par vous.

Notez que je ne renie absolument pas la solution que vous a donnée @tyrry, et qui vous convenait, puisque mes propres lignes la reprennent presque intégralement. Simplement, avec une boite de dialogue, vous pouvez faire, comme décrit, plus simplement. Si, dans Nemo, vous ne savez pas sélectionner des fichiers, par exemple non-contigus, allez sur le moteur de recherche le plus connu (G…) et tapez les mots « Nemo sélectionner fichiers »  ; la réponse IA n’est (pour une fois…) pas trop mauvaise. Après, c’est juste une affaire de clic droit sur l’un des fichiers sélectionnés qui, s’ils sont TOUS des fichiers images, donne accès à l’option « Assembler les images sélectionnées » dans le menu contextuel (illustration plus haut).
 
Je ne connaissais pas ce Tom & Jerry (accompagné de Droopy) ; merci du partage !
:-)
| HP Pavilion Gaming Desktop 690-00xx | Linux Mint 22.3 Zena | Cinnamon : 6.6.7 | X11 | Kernel 6.8.0-111-generic arch: x86_64 | Intel Core i5-9400F bits : 64 type : MCP | NVIDIA GP107 [GeForce GTX 1050] | Ram : 8 GiB |
Avatar du membre
michLapprenti
Messages : 1105
Enregistré le : sam. 20 août 2016 20:11

Re: [Nemo] [Cinnamon] « Actions » de Nemo et paramètres transmis à Python.

Message par michLapprenti »

Bonjour smcj

J'ai lu plusieurs fois votre 1er post sans que rien ne soit vraiment capté par mon cerveau
Par contre votre dernier post m'a intéressé:
smcj a écrit : mer. 29 juil. 2026 16:07 allez sur le moteur de recherche le plus connu (G…) et tapez les mots « Nemo sélectionner fichiers 
Personnellement j'utilise le moteur de recherche DuckDuckGo et suis opposé à TOUT usage de l'IA

Donc avec DuckDuckGo j'ai tapé "Nemo sélectionner fichiers" et me suis intéressé à 2 liens,
(d'autres liens consultés présentent les mêmes caractéristiques - semble-t-il):

(article daté 11-2022) https://www.numetopia.fr/comment-person ... eur-parti/

(article non daté) https://buso.ovh/le-monde-linux/les-ges ... r-cinnamon

1) Qu'en pensez-vous ? Est-ce que c'est quelque peu ce que vous proposez ?

2) ces liens sont nettement plus clairs que votre exposé - vous pourriez peut-être l'améliorer ;)

La lecture de ces liens, sans doute un peu anciens, a suscité ma question
posée là viewtopic.php?t=26000
afin de ne pas déroger aux règles du forum (pour faciliter les recherches): une question = un sujet
Distrib : Linux Mint 22.3 Desktop : X-Cinnamon Lenovo T480S - SSD 500go
Kernel : 6.17.0-35-generic CPU : Intel(R) Core(TM) i5-8350U CPU @ 1.70GHz
RAM : 15Gi CG : Intel Corporation UHD Graphics 620 (rev 07)
Répondre