#1 Le 08/07/2014, à 13:14
- hackoeur
[Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Bonjour le Monde !
Comme l'évoque le titre, je cherche à paramétrer la taille d'une fenêtre indépendamment l'une de l'autre, ainsi qu'à la fixer de façon à ce que ce soit celle (la taille) qui s'ouvre par défaut.
Cela est-il possible ?
Si oui da, quelle en est la procédure ?
Idéalement, je souhaiterais le faire via du css, mais a-t-on accès à la source .css de Xfce ?
Si affirmatif, où
Merci à celles et ceux qui pourront m'aider.
Dernière modification par ljere (Le 26/07/2014, à 11:28)
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#2 Le 17/07/2014, à 10:22
- metalux
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Bonjour hackoeur,
Pour le css je n'en sais rien mais devilspie devrait te permettre de faire ce que tu souhaites.
Hors ligne
#3 Le 17/07/2014, à 11:53
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Hey metalux,
Ai rapidement jeté un oeil sur le lien et en effet, cela semble correspondre. Mais étant sur mon ardoise, hors de chez moi, je verrai tout ça dès mon retour et je posterai le retour du bins.
(il en va de même pour le script du cenvertisseur mp3-mp4 sur le tuto de nam1962)
Merci beaucoup.
Dernière modification par hackoeur (Le 17/07/2014, à 11:55)
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#4 Le 23/07/2014, à 17:13
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Bonjour metalux,
Voici moi de retour de ma virée en mer...
Alors :
J'ai tout essayé afin d'installer devilspie (même devilspie2), mais rien à faire, jusqu'à ce que je lise le premier tuto en anglais qui parle de l'environnement gnome que je ne charge pas étant sous Xfce.
Ai-je bien compris, ou y a-t-il une subtilité dans l'installation qui m'échappe ?
Merci de ton aide.
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#5 Le 23/07/2014, à 19:04
- metalux
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Je n'utilise pas devilspie mais normalement c'est possible avec Xfce.
As-tu bien suivi la doc?
Hors ligne
#6 Le 23/07/2014, à 19:21
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Il me semble, oui.
C'est dans ce tuto de Wolki, cité par la doc-Ubuntu où gnome y est mentionné :
http://ubuntuforums.org/showthread.php?t=75749
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#7 Le 24/07/2014, à 02:16
- ljere
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
bonjour hackoeur,
il n'y a pas d’inconvénient à utiliser xfce pour devilspie, je le fais moi même et cela fonctionne parfaitement.
pourrais tu nous donner la config que tu utilises
ancien PC Toshiba satellite_c670d-11 / Linux Mint 21 Vanessa
Nouveau PC ASUS TUF GAMING A17 GPU RTX 4070 CPU AMD Ryzen 9 7940HS w/ Radeon 780M Graphics / Linux Mint 21.2 Victoria / Kernel: 6.4.8-1-liquorix / Desktop: Cinnamon
Hors ligne
#8 Le 24/07/2014, à 08:17
- F50
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Salut,
Je m'abonne aussi au sujet, ça fait un moment que je cherche à faire la même chose avec les fenêtres des paramètres qui sont trop petites, y'a pas moyen au travers des fichiers conf au lieu de devilspie ? Merci.
#9 Le 24/07/2014, à 16:45
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Bonjour ljere et merci de te pencher sur mon souci.
il n'y a pas d’inconvénient à utiliser xfce pour devilspie, je le fais moi même et cela fonctionne parfaitement.
C'est clair que c'est moi qui débilise quelque part...
J'ai installé par le terminal :
install devilspie
[sudo] password for no:
Les NOUVEAUX paquets suivants vont être installés :
devilspie
0 paquets mis à jour, 1 nouvellement installés, 0 à enlever et 1 non mis à jour.
Il est nécessaire de télécharger 43.5 ko d'archives. Après dépaquetage, 330 ko seront utilisés.
Prendre : 1 http://ch.archive.ubuntu.com/ubuntu/ trusty/universe devilspie i386 0.23-2 [43.5 kB]
43.5 ko téléchargés en 0s (321 ko/s)
Sélection du paquet devilspie précédemment désélectionné.
(Lecture de la base de données... 202322 fichiers et répertoires déjà installés.)
Préparation du décompactage de .../devilspie_0.23-2_i386.deb ...
Décompactage de devilspie (0.23-2) ...
Traitement déclenché pour man-db (2.6.7.1-1) ...
Paramétrage de devilspie (0.23-2) ...
no@no-A8SR:~$
Ensuite pour l'interface graphique j'ai suivi la doc-Ubuntu :
3. Installation
Il est possible d'installer Devil's pie à partir du gestionnaire de paquet Synaptic, après avoir activé le dépôt "Universe".
Ai aussi essayé hier, mais pas là.
Alternativement, une installation en ligne de commande est possible en rentrant dans un terminal la commande suivante :sudo apt-get install devilspie
Fait.
Pour installer l'interface graphique dont il est fait mention plus haut, il faut télécharger le fichier tar.bz2 disponible sur cette page: http://code.google.com/p/gdevilspie/downloads/list.
Fait.
Une fois le fichier sur votre ordinateur, il suffit d'en extraire le contenu et d' exécuter le fichier "gdevilspie" qui se trouve dans le dossier "gdevilspie-0.5".
Fait :
Il est possible d'installer l'interface grâce à un script d'installation. Dans un terminal, lancez les commandes suivantes :cd /répertoire_ou_se_trouve_gdevilspie-0.5 sudo python setup.py install
Fait auss et résultat :
no@no-A8SR:~$ cd /répertoire_ou_se_trouve_gdevilspie-0.5 bash: cd: /répertoire_ou_se_trouve_gdevilspie-0.5: Aucun fichier ou dossier de ce type no@no-A8SR:~$ sudo python setup.py install python: can't open file 'setup.py': [Errno 2] No such file or directory no@no-A8SR:~$
Il vous est désormais possible de lancer l'interface graphique en tapant, dans un terminal :
gdevilspie
Fait et résultat :
no@no-A8SR:~$ gdevilspie Le programme « gdevilspie » n'est pas encore installé. Vous pouvez l'installer en tapant : sudo apt-get install gdevilspie no@no-A8SR:~$
??? Jusque là mékéjeu fait ?
pourrais tu nous donner la config que tu utilises
Est-ce bien ce que tu me demandais ?
EDIT : @fcn50 Bienvenue, plus y a de questions plus y a de réponses et donc... focus sur la "fenêtre" ! XD
Dernière modification par hackoeur (Le 24/07/2014, à 16:56)
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#10 Le 25/07/2014, à 02:30
- ljere
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
je n'utilise pas gdevilspie
il suffit aller dans le fichier caché
/home/user/.devilspie
de créer un fichier config par exemple pour thunar on peut faire pour mon cas (c'est à dire avec l'user jerem) la config thunar.ds
(if
(and (contains (application_name) "Thunar") (contains(window_name) "jerem - Gestionnaire de fichiers"))
(begin
(below)
(undecorate)
(skip_pager)
(opacity 60)
(pin)
(skip_tasklist)
(wintype "utility")
(geometry "800x315+0+554")
)
)
puis on lance la commande
devilspie -d &
ancien PC Toshiba satellite_c670d-11 / Linux Mint 21 Vanessa
Nouveau PC ASUS TUF GAMING A17 GPU RTX 4070 CPU AMD Ryzen 9 7940HS w/ Radeon 780M Graphics / Linux Mint 21.2 Victoria / Kernel: 6.4.8-1-liquorix / Desktop: Cinnamon
Hors ligne
#11 Le 25/07/2014, à 10:35
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Bonjour ljere,
je n'utilise pas gdevilspie
Idem pour moi, et dans /home/usr/bin/ c'est bien devilspie qui s'y trouve.
il suffit aller dans le fichier caché /home/user/bin/.devilspie
de créer un fichier config par exemple pour thunar on peut faire pour mon cas (c'est à dire avec l'user jerem) la config thunar.ds
Pourquoi fut-il créer ce fichier ?
Et ce qui en gras, c'est quoi et qu'est-ce que ça fait au juste ?
Désolée de solliciter ta patience ainsi, mais parle-moi comme à une enfant de 7 ans pour que je comprenne, après ça va tout seul, craché-promis.
(if (and (contains (application_name) "Thunar") (contains(window_name) "jerem - Gestionnaire de fichiers")) (begin (below) (undecorate) (skip_pager) (opacity 60) (pin) (skip_tasklist) (wintype "utility") (geometry "800x315+0+554") ) )
Donc ce script je le met dans un fichier créé avec gedit, c'est bien ça ?
Nommé "devilspie -d &" ?
Est-ce pour Xfce ?
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#12 Le 25/07/2014, à 11:39
- ljere
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
en effet tu n'as pas tout compris
donc ce fichier est un fichier de config, donc il sera lu par devilspie quand tu le lanceras.
Le nom du fichier pour l'exemple de Thunar sera thunar.ds pour Firefox on l'appellera firefox.ds,
comme tout fichier de config, il a une certaine structure que j'ai déjà expliqué dans la doc:
Pour l'emplacement c'est /home/user/.devilspie/ ce qui dans mon cas donne /home/jerem/devilspie/ je ne vois pas d'où tu as sorti ce dossier /bin.
Pour conclure cette commande
devilspie -d &
sert à lancer devilspie donc
soit tu le lances via un terminal ou tu le mets dans "session et démarrage" ou mieux tu l'ajoutes dans ton lanceur car devilspie agit sur les fenêtres déjà lancées
exemple:
exo-open --launch FileManager %u && devilspie -d &
ancien PC Toshiba satellite_c670d-11 / Linux Mint 21 Vanessa
Nouveau PC ASUS TUF GAMING A17 GPU RTX 4070 CPU AMD Ryzen 9 7940HS w/ Radeon 780M Graphics / Linux Mint 21.2 Victoria / Kernel: 6.4.8-1-liquorix / Desktop: Cinnamon
Hors ligne
#13 Le 25/07/2014, à 12:07
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Merci pour ta patience ljere. En effet, c'est tout moi : Le chemin est long jusqu'à mon interrupteur.^^
Comme tu peux le constater ici, pas de .devilspie :
En revanche, ici on voit le dossier devilspie2 (selection) IL EST VIDE :
Voici où j'ai trouvé l'exécutable :
- Faudrait-il que je le déplace dans le /home/no/config ?
- Faudrait-il que je déplace le tout dans le répertoire que tu mentionnes ?
- Faut-il supprimer et réinstaller, tout en sachant que je n'ai pas déplacer ces fichiers ?
Merci encore.
M'en vais lire la doc.
Dernière modification par hackoeur (Le 25/07/2014, à 12:08)
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#14 Le 25/07/2014, à 12:28
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
EDIT : Voici ce que j'ai dans le système de fichiers :
http://www.zimagez.com/zimage/screensho … 133046.php
Ce qui est antérieur à hier n'est pas de mon fait.
EDIT2 : Bien vu ljere :
- ai tout supprimé, purgé et réinstallé à neuf via synaptic.
- dossier localisé /home/no/.devilspie
- création du fichier thunar.ds dans le dossier ./.devilspie =
1 thunar.ds et 1 thunar.ds~ (<-- sauvegarde, si ?)
http://www.zimagez.com/zimage/screensho … 153113.php
- dans le terminal, stacké là :
no@no-A8SR:~$ devilspie -d &
[2] 12003
no@no-A8SR:~$ Devil's Pie 0.23 starting...
Loading /etc/devilspie
/etc/devilspie doesn't exist
Loading /home/no/.devilspie
Loading /home/no/.devilspie/thunar.ds
1 s-expressions loaded.
APRẼS UN [ENTER]
no@no-A8SR:~$
Y a du progrès quand même, hein dis oui ?
Dernière modification par hackoeur (Le 25/07/2014, à 14:32)
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#15 Le 25/07/2014, à 16:23
- ljere
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
oui tout me semble correct cette fois ci, par contre montre moi le contenu que tu as mis dans thunar.ds
pour thunar.ds~ c'est gedit qui crée automatiquement une sauvegarde, donc rien d'anormal
une astuce que j'ai donné aussi sur la doc
Pour obtenir le nom exacte d'une application (à utiliser avec application_name) il vous suffit de créer un fichier ~/.devilspie/debug.ds et d'y ajouter : (debug)
Ensuite, lancez "devilspie -d" dans une console et observez les lignes apparaissant à l'écran lorsque vous lancez une application. Exemple pour Firefox : Window Title: 'Mozilla Firefox Beta 1'; Application Name: 'Firefox'; Class: 'Firefox-bin'; Geometry: 1014x692+5+46
La partie à retenir est : "Application Name: 'Firefox'".
ancien PC Toshiba satellite_c670d-11 / Linux Mint 21 Vanessa
Nouveau PC ASUS TUF GAMING A17 GPU RTX 4070 CPU AMD Ryzen 9 7940HS w/ Radeon 780M Graphics / Linux Mint 21.2 Victoria / Kernel: 6.4.8-1-liquorix / Desktop: Cinnamon
Hors ligne
#16 Le 25/07/2014, à 18:05
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
montre moi le contenu que tu as mis dans thunar.ds
Je n'y mis que ton script en modifiant seulement le paramètre "opacity" de 60 à 73.
Vu que je dois encore apprendre beaucoup (plutôt : TOUT) sur les règles, etc, je me contente donc d'observer le bins tout en lisant les tutos (par exemple je n'ai pas encore compris les valeurs du paramètre "geometry")...
(if
(and (contains (application_name) "Thunar") (contains(window_name) "jerem - Gestionnaire de fichiers"))
(begin
(below)
(undecorate)
(skip_pager)
(opacity 73)
(pin)
(skip_tasklist)
(wintype "utility")
(geometry "800x315+0+554")
)
)
en "texte brut" <-- ne devrait-il pas être en js ou quelque chose de genre ?
Que vois-tu ?
Pour obtenir le nom exacte d'une application (à utiliser avec application_name) il vous suffit de créer un fichier ~/.devilspie/debug.ds et d'y ajouter : (debug)
Aïe, c'est technique, mais je vais chercher à imprimer le bins.
Je reviens vers toi dès qu'il se passe quelque chose, parce là, nada :
no@no-A8SR:~$ devilspie -d
Devil's Pie 0.23 starting...
Loading /etc/devilspie
/etc/devilspie doesn't exist
Loading /home/no/.devilspie
Loading /home/no/.devilspie/thunar.ds
1 s-expressions loaded.
^C
no@no-A8SR:~$
Merci pour ton aide.
EDIT : J'avais zappé la dernière commande de ton précédent pots :
no@no-A8SR:~$ exo-open --launch FileManager %u && devilspie -d &
[1] 3012
no@no-A8SR:~$ Devil's Pie 0.23 starting...
Loading /etc/devilspie
/etc/devilspie doesn't exist
Loading /home/no/.devilspie
Loading /home/no/.devilspie/thunar.ds
1 s-expressions loaded.
Thunar: Échec à l'ouverture de « %u » : Erreur lors de l'obtention des informations du fichier « /home/no/%u » : Aucun fichier ou dossier de ce type
Thunar: Échec à l'ouverture de « %u » : Erreur lors de l'obtention des informations du fichier « /home/no/%u » : Aucun fichier ou dossier de ce type
Dernière modification par hackoeur (Le 25/07/2014, à 18:27)
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#17 Le 25/07/2014, à 19:55
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Rhooo, là je reposte direct parce que j'ai réussi à suivre la partie (debug) de ton tuto, merci, des personnes telles que toi (metalux et bien d'utres) arrivent à activer une lueur d'intelligence chez moi, ce qui est plus ardu que le plus sophistiqué des tutos, lol.
Donc après avoir créé le fichier ~/.devilspie/debug.ds (juste mis (debug) dedans) et lancé la commande, voici le résultat :
no@no-A8SR:~$ devilspie -d
Devil's Pie 0.23 starting...
Loading /etc/devilspie
/etc/devilspie doesn't exist
Loading /home/no/.devilspie
Loading /home/no/.devilspie/debug.ds
Loading /home/no/.devilspie/thunar.ds
2 s-expressions loaded.
Window Title: 'Terminal - no@no-A8SR: ~'; Application Name: 'Terminal Xfce'; Class: 'Xfce4-terminal'; Geometry: 511x399+0+0
Window Title: 'Param?trer et fixer la taille par d?faut des fen?tres Xfce (Page 1) / XFCE / Forum Ubuntu-fr.org - Chromium'; Application Name: 'Param?trer et fixer la taille par d?faut des fen?tres Xfce (Page 1) / XFCE / Forum Ubuntu-fr.org - Chromium'; Class: 'Chromium-browser'; Geometry: 1280x780+0+0
Window Title: 'Bureau'; Application Name: 'xfdesktop'; Class: 'Xfdesktop'; Geometry: 1280x800+0+0
Window Title: 'xfce4-panel'; Application Name: 'xfce4-panel'; Class: 'Xfce4-panel'; Geometry: 1280x20+0+780
Window Title: 'Opera'; Application Name: 'Opera'; Class: 'Opera'; Geometry: 769x780+511+0
Je ne sais pas encore exactement comment agir sur ces infos du terminal...
EDIT : Par contre, j'ai essayé de lancer la version graphique gdevilspie, qui a été installé avec devilspie (ou l'inverse), dont le fichier-py se trouve dans /usr/bin
#!/usr/bin/python
#
# gdevilspie.py
# Copyright (C) Islam Amer 2008 <iamer@open-craft.com>
#
# gdevilspie.py is free software: you can redistribute it and/or modify it
# under the terms of the GNU General Public License as published by the
# Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# gdevilspie.py is distributed in the hope that it will be useful, but
# WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
# See the GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License along
# with this program. If not, see <http://www.gnu.org/licenses/>.
# Import needed modules
import os
import sys
import commands
import signal
import subprocess
import string
import gDevilspie.reader as reader
import tempfile
try:
import gobject
import pygtk
pygtk.require('2.0')
import gtk
import gtk.glade
except:
print "pyGTK is not correctly installed, exiting."
sys.exit(1)
DISABLE_FILLER=""
try:
import gDevilspie.filler as filler
except:
error = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, "Make sure that Python-Wnck is correctly installed for 'filler' module to run properly.")
response = error.run()
error.destroy()
DISABLE_FILLER="True"
DISABLE_XDG=""
try:
import xdg.DesktopEntry
except:
error = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, "Make sure that Python-xdg is correctly installed if you need autostart handling.")
response = error.run()
error.destroy()
DISABLE_XDG="True"
import gettext
_ = gettext.gettext
PACKAGE = "gdevilspie"
# FIXME
# LOCALEDIR = @localedir@
LOCALEDIR = "locale"
gettext.bindtextdomain(PACKAGE, LOCALEDIR)
gettext.textdomain(PACKAGE)
gtk.glade.bindtextdomain(PACKAGE, LOCALEDIR)
gtk.glade.textdomain(PACKAGE)
# List of possible match criteria
match_criteria={
"window_name" : {
"description" : _("<b>Any window whose name</b>"), "widget" : None,
"entry_is" : None, "entry_contains" : None, "entry_matches" : None,
"is_not" : False, "contains_not" : False, "matches_not" : False,
"is_not_checkbox" : None, "contains_not_checkbox" : None, "matches_not_checkbox" : None },
"window_role" : { "description" : _("<b>Any window whose role</b>"), "widget" : None,
"entry_is" : None, "entry_contains" : None, "entry_matches" : None ,
"is_not" : False, "contains_not" : False, "matches_not" : False,
"is_not_checkbox" : None, "contains_not_checkbox" : None, "matches_not_checkbox" : None },
"window_class" : { "description" : _("<b>Any window whose class</b>"), "widget" : None,
"entry_is" : None, "entry_contains" : None, "entry_matches" : None ,
"is_not" : False, "contains_not" : False, "matches_not" : False,
"is_not_checkbox" : None, "contains_not_checkbox" : None, "matches_not_checkbox" : None },
"window_xid" : { "description" : _("<b>Any window whose xid</b>"), "widget" : None,
"entry_is" : None, "entry_contains" : None, "entry_matches" : None ,
"is_not" : False, "contains_not" : False, "matches_not" : False,
"is_not_checkbox" : None, "contains_not_checkbox" : None, "matches_not_checkbox" : None },
"application_name" : { "description" : _("<b>Any window whose application name</b>"), "widget" : None,
"entry_is" : None, "entry_contains" : None, "entry_matches" : None ,
"is_not" : False, "contains_not" : False, "matches_not" : False,
"is_not_checkbox" : None, "contains_not_checkbox" : None, "matches_not_checkbox" : None },
"window_property" : { "description" : _("<b>Any window whose property</b>"), "widget" : None,
"entry_is" : None, "entry_contains" : None, "entry_matches" : None ,
"is_not" : False, "contains_not" : False, "matches_not" : False,
"is_not_checkbox" : None, "contains_not_checkbox" : None, "matches_not_checkbox" : None },
"window_workspace" : { "description" : _("<b>Any window whose workspace</b>"), "widget" : None,
"entry_is" : None, "entry_contains" : None, "entry_matches" : None ,
"is_not" : False, "contains_not" : False, "matches_not" : False,
"is_not_checkbox" : None, "contains_not_checkbox" : None, "matches_not_checkbox" : None }
}
def toggle_this(widget, str, match_criteria_name):
match_criteria[match_criteria_name][str+"_not"]= not match_criteria[match_criteria_name][str+"_not"]
return
def create_match_parameters_page(match_criteria_name):
vbox = gtk.VBox()
str = match_criteria[match_criteria_name]["description"]
description_text = gtk.Label(str)
description_text.set_use_markup(True)
description_text.set_line_wrap(True)
vbox.pack_start(description_text, False, False)
# three hboxes
hbox_is, hbox_contains, hbox_matches = gtk.HBox(), gtk.HBox(), gtk.HBox()
# Three Check boxes for negation
match_criteria[match_criteria_name]["is_not_checkbox"] = gtk.CheckButton("does not")
match_criteria[match_criteria_name]["contains_not_checkbox"] = gtk.CheckButton("does not")
match_criteria[match_criteria_name]["matches_not_checkbox"] = gtk.CheckButton("does not")
is_not_checkbox = match_criteria[match_criteria_name]["is_not_checkbox"]
contains_not_checkbox = match_criteria[match_criteria_name]["contains_not_checkbox"]
matches_not_checkbox = match_criteria[match_criteria_name]["matches_not_checkbox"]
# We have to reflect the check box state somewhere
is_not_checkbox.connect("toggled", toggle_this, "is", match_criteria_name)
contains_not_checkbox.connect("toggled", toggle_this, "contains", match_criteria_name)
matches_not_checkbox.connect("toggled", toggle_this, "matches", match_criteria_name)
# Three text entries
match_criteria[match_criteria_name]["entry_is"] = gtk.Entry()
match_criteria[match_criteria_name]["entry_contains"] = gtk.Entry()
match_criteria[match_criteria_name]["entry_matches"] = gtk.Entry()
entry_is = match_criteria[match_criteria_name]["entry_is"]
entry_contains = match_criteria[match_criteria_name]["entry_contains"]
entry_matches = match_criteria[match_criteria_name]["entry_matches"]
# Three labels
MatchMethod_text_is=gtk.Label(_("equal(s)"))
MatchMethod_text_contains=gtk.Label(_("contain(s)"))
MatchMethod_text_matches=gtk.Label(_("match(es)"))
# Pack the triads
hbox_is.pack_start(is_not_checkbox, False, False)
hbox_contains.pack_start(contains_not_checkbox, False, False)
hbox_matches.pack_start(matches_not_checkbox, False, False)
hbox_is.pack_end(entry_is, False, False)
hbox_contains.pack_end(entry_contains, False, False)
hbox_matches.pack_end(entry_matches, False, False)
hbox_is.pack_end(MatchMethod_text_is, True, False)
hbox_contains.pack_end(MatchMethod_text_contains, True, False)
hbox_matches.pack_end(MatchMethod_text_matches, True, False)
# pack the rows
vbox.pack_start(hbox_is, True, False)
vbox.pack_start(hbox_contains, True, False)
vbox.pack_start(hbox_matches, True, False)
# return the vbox we built so it gets packed into the property page
return vbox
# Dictionary of the actions for each of which we store a dictionary of help text and widgets
actions_dict={
"geometry" : {"description" : _("<b>Set position and size of window</b>"), "widget" : None, "type" : "Text" , "input" : { "xposition" : None, "yposition" : None, "width" : None, "height" : None } },
"fullscreen" : {"description" : _("<b>Make the window fullscreen</b>"), "widget" : None},
"focus": {"description" : _("<b>Focus the window</b>"), "widget" : None},
"center": {"description" : _("<b>Center the position of the window</b>"), "widget" : None},
"maximize": {"description" : _("<b>Maximize the window</b>"), "widget" : None},
"maximize_vertically": {"description" : _("<b>Maximize the window vertically only</b>"), "widget" : None},
"maximize_horizontally": {"description" : _("<b>Maximize the window horizontally only</b>"), "widget" : None},
"unmaximize": {"description" : _("<b>Unmaximize the window</b>"), "widget" : None},
"minimize": {"description" : _("<b>Minimize the window</b>"), "widget" : None},
"unminimize": {"description" : _("<b>Unminimize the window</b>"), "widget" : None},
"shade": {"description" : _("<b>Roll up the window</b>"), "widget" : None},
"unshade": {"description" : _("<b>Roll down the window</b>"), "widget" : None},
"close": {"description" : _("<b>Close the window</b>"), "widget" : None},
"pin": {"description" : _("<b>Pin the window to all workspaces</b>"), "widget" : None},
"unpin": {"description" : _("<b>Unpin the window from all workspaces</b>"), "widget" : None},
"stick": {"description" : _("<b>Stick the window to all viewports</b>"), "widget" : None},
"unstick": {"description" : _("<b>Unstick the window from all viewports</b>"), "widget" : None},
"set_workspace": {"description" : _("<b>Move the window to a specific workspace number</b>"), "widget" : None, "type" : "Spin" , "input" : { "workspace" : None } },
"set_viewport": {"description" : _("<b>Move the window to a specific viewport number</b>"), "widget" : None, "type" : "Spin" , "input" : { "viewport" : None } },
"skip_pager": {"description" : _("<b>Remove the window from the pager</b>"), "widget" : None},
"skip_tasklist": {"description" : _("<b>Remove the window from the window list</b>"), "widget" : None},
"above": {"description" : _("<b>Set the current window to be above all normal windows</b>"), "widget" : None},
"below": {"description" : _("<b>Set the current window to be below all normal windows</b>"), "widget" : None},
"decorate": {"description" : _("<b>Add the window manager decorations to the window</b>"), "widget" : None},
"undecorate": {"description" : _("<b>Remove the window manager decorations from the window</b>"), "widget" : None},
"wintype": {"description" : _("<b>Set the window type of the window</b>"), "widget" : None, "type" : "Combo" , "Choices" : ["normal", "dialog", "menu", "toolbar", "splash-screen", "utility", "dock", "desktop"] , "input" : { "wintype" : None } },
"opacity": {"description" : _("<b>Change the opacity level of the widnow</b>"), "widget" : None, "type" : "Spin" , "input" : { "opacity" : None } },
"spawn_async": {"description" : _("<b>Execute a command in the background</b>"), "widget" : None, "type" : "Text" , "input" : { "command" : None } },
"spawn_sync": {"description" : _("<b>Execute a command in the foreground</b>"), "widget" : None, "type" : "Text" , "input" : { "command" : None } }
}
def create_action_parameters_page(action_name):
vbox = gtk.VBox()
str = actions_dict[action_name]["description"]
description_text = gtk.Label(str)
description_text.set_use_markup(True)
description_text.set_line_wrap(True)
vbox.pack_start(description_text, False, False, 10)
if ( actions_dict[action_name].has_key("input") ):
InputType = actions_dict[action_name]["type"]
for key in actions_dict[action_name]["input"]:
if ( InputType == "Text" ):
entry = gtk.Entry()
elif ( InputType == "Spin" ):
entry = gtk.SpinButton(gtk.Adjustment(0, 0, 100, 1, 0, 0) , 1, 0)
elif (InputType == "Combo" ):
entry = gtk.combo_box_new_text()
for choice in actions_dict[action_name]["Choices"]:
entry.append_text(choice)
entry.set_active(0)
actions_dict[action_name]["input"][key] = entry
hbox = gtk.HBox()
label = gtk.Label(key)
hbox.pack_start(label, True, True, 10)
hbox.pack_start(entry, True, False, 10)
vbox.pack_start(hbox, True, False, 10)
return vbox
# generated rule storage
def generate_rule(generated_rule):
part0 = "( if \n"
part1 = "( begin \n"
part2 = generate_match_criteria()
part3 = ") \n" + "( begin \n"
part4 = generate_actions()
part5 = "( println \"match\" )\n"
part6 = ")\n)\n"
generated_rule = part0 + part1 + part2 + part3 + part4 + part5 + part6
return generated_rule
def generate_match_criteria():
strous=""
for row in MainWindow.RuleEdit.match_list_store:
match_method = row[1]
if ( row[0] == True ):
for prop in ["is", "contains", "matches"]:
entry = "entry_" + prop
negation = prop + "_not"
entry_text = match_criteria[match_method][entry].get_text()
negation_state = match_criteria[match_method][negation]
if ( entry_text != "" ):
storing=""
if ( negation_state == True ):
storing = "( " + "not "+ "( " + prop
else:
storing = "( " + prop
storing = storing + " ( " + match_method + " ) "
storing = storing + '"' + entry_text + '"' + " )"
if ( negation_state == True ):
storing = storing + " )"
storing = storing + "\n"
strous += storing
return strous
def generate_actions():
strous = ""
for row in MainWindow.RuleEdit.actions_list_store:
action_name = row[1]
if ( row[0] == True ):
storing = action_name
if actions_dict[action_name].has_key("input"):
if ( action_name == "geometry" ):
xposition = actions_dict[action_name]["input"]["xposition"].get_text()
yposition = actions_dict[action_name]["input"]["yposition"].get_text()
height = actions_dict[action_name]["input"]["height"].get_text()
width = actions_dict[action_name]["input"]["width"].get_text()
if ( width == "" ) or ( height == "" ):
size = ""
else:
size = width + "x" + height
geomstring = size + "+" + xposition + "+" + yposition
storing = storing + " \"" + geomstring + "\""
elif ( action_name == "set_viewport" ) or ( action_name == "set_workspace" ) or ( action_name == "opacity" ):
for key in actions_dict[action_name]["input"]:
storing = storing + " " + str(actions_dict[action_name]["input"][key].get_value_as_int())
elif ( action_name == "wintype" ):
for key in actions_dict[action_name]["input"]:
storing = storing + " \"" + actions_dict[action_name]["input"][key].get_active_text() + "\""
else:
for key in actions_dict[action_name]["input"]:
storing = storing + " \"" + actions_dict[action_name]["input"][key].get_text() + "\""
strous += "( " + storing + " )\n"
return strous
# Glade file used in all classes
if os.path.isfile(os.path.dirname(sys.argv[0])+'/gdevilspie.glade'):
gladefile = os.path.dirname(sys.argv[0])+'/gdevilspie.glade'
elif os.path.isfile(os.path.dirname(sys.argv[0]) + '/../share/gdevilspie/gdevilspie.glade'):
gladefile = os.path.dirname(sys.argv[0])+'/../share/gdevilspie/gdevilspie.glade'
else:
gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, "Glade file not found, exiting.").run()
quit()
# icon used
if os.path.isfile(os.path.dirname(sys.argv[0])+'/gdevilspie.png'):
gdevilspie_icon = os.path.dirname(sys.argv[0])+'/gdevilspie.png'
elif os.path.isfile(os.path.dirname(sys.argv[0]) + '/../share/gdevilspie/gdevilspie.png'):
gdevilspie_icon = os.path.dirname(sys.argv[0])+'/../share/gdevilspie/gdevilspie.png'
else:
gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, "Icon file not found.").run()
# Directory where we store .ds files
dir = os.path.expanduser("~/.devilspie")
# The main class which creates the main window where we list the rules
class RulesListWindow:
# Initialization of the class
def __init__(self):
try:
# try to get our widgets from the gladefile
wTreeList = gtk.glade.XML (gladefile, "RulesList")
wTreeStatusPopupMenu = gtk.glade.XML (gladefile, "StatusPopupMenu")
wTreeAbout = gtk.glade.XML (gladefile, "AboutgDevilepie")
except:
#inform the user there was an error and exit
gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, _("Glade file not found, exiting.")).run()
quit()
self.AboutgDevilepie = wTreeAbout.get_widget("AboutgDevilepie")
# Get the widgets that we will work with RulesList is the window, and RulesTree is the tree list of rules
self.RulesList = wTreeList.get_widget("RulesList")
self.RulesTree = wTreeList.get_widget("RulesTree")
self.DaemonStatus = wTreeList.get_widget("DaemonStatus")
self.ToggleDaemon = wTreeList.get_widget("ToggleDaemon")
self.DaemonAutostart = wTreeList.get_widget("DaemonAutostart")
self.ToggleDaemonLabel = wTreeList.get_widget("ToggleDaemonLabel")
self.StatusPopupMenu = wTreeStatusPopupMenu.get_widget("StatusPopupMenu")
# connect the signals to callbacks
wTreeList.signal_autoconnect(self)
wTreeStatusPopupMenu.signal_autoconnect(self)
# create a liststore model which takes one string, the rule name
self.rules_list_store = gtk.ListStore(str)
self.RulesTree.connect("row-activated", self.on_RulesTree_row_activated)
# connect the model to the tree view
self.RulesTree.set_model(self.rules_list_store)
# pack a single column that has a text cell into the tree view
self.RulesFilesNames=gtk.TreeViewColumn('Rule Name')
self.RulesTree.append_column(self.RulesFilesNames)
self.RuleFileName=gtk.CellRendererText()
self.RulesFilesNames.pack_start(self.RuleFileName,expand=True)
self.RulesFilesNames.add_attribute(self.RuleFileName, 'text', 0)
# if we have a config dir list the files inside otherwise try to create it
if (os.path.exists(dir)):
if (os.path.isdir(dir)):
self.fill_rules_list()
else:
print _("~/.devilspie is a file, please remove it")
else:
os.makedirs(dir)
self.RulesTree.set_cursor(0, None, False)
self.UpdateDaemonStatus()
self.UpdateAutostartStatus()
# Tray Icon (Not working properly yet)
self.tray = gtk.StatusIcon()
self.tray.set_from_file(gdevilspie_icon)
self.tray.set_tooltip(_("gDevilspie"))
self.tray.set_blinking(False)
self.tray.connect("activate", self.on_tray_activate)
self.tray.connect("popup-menu", self.on_tray_popup)
# display the main window
self.RulesList.show_all()
def on_tray_activate(self, widget):
# toggle view of main window
if ( self.RulesList.get_property("visible") == True):
self.RulesList.hide_all()
elif ( self.RulesList.get_property("visible") == False):
self.RulesList.show_all()
def on_tray_popup(self, widget, button, activate_time):
self.StatusPopupMenu.popup(None, None, None, button, activate_time)
def on_About_activate(self, widget):
self.AboutgDevilepie.show_all
response = self.AboutgDevilepie.run()
self.AboutgDevilepie.hide()
def on_Quit_activate(self, widget):
gtk.main_quit()
# handle exiting the program
def on_RulesList_destroy(self,widget):
gtk.main_quit()
def on_Quit_clicked(self, widget):
gtk.main_quit()
# make a rule creator instance
def on_AddRule_clicked(self, widget):
self.RuleEdit = RuleEditorWindow()
def on_RulesTree_row_activated(self, widget, path, view_column):
self.on_EditRule_clicked(widget)
def on_EditRule_clicked(self, widget):
self.RuleEdit = RuleEditorWindow()
SelectedRow = self.RulesTree.get_selection()
(model, iter) = SelectedRow.get_selected()
if (iter != None):
SelectedRule = self.rules_list_store.get(iter, 0)
RuleFile = os.path.expanduser("~/.devilspie/") + SelectedRule[0] + '.ds'
if (os.path.exists(RuleFile)):
self.RuleEdit.RuleName_entry.set_text(SelectedRule[0])
matdict , actiondict = reader.read_file(RuleFile)
self.Parse_EditRule(matdict, actiondict)
def Parse_EditRule(self, matdict, actiondict):
for key in matdict:
for match_row in self.RuleEdit.match_list_store:
if ( match_row[1] == key ):
match_row[0] = True
if 'is' in matdict[key]:
if 'not' in matdict[key]:
matdict[key].remove('not')
match_criteria[key]["is_not_checkbox"].set_active(True)
match_criteria[key]["is_not"] = True
matdict[key].remove('is')
match_criteria[key]["entry_is"].set_text(matdict[key][0])
if 'contains' in matdict[key]:
if 'not' in matdict[key]:
matdict[key].remove('not')
match_criteria[key]["contains_not_checkbox"].set_active(True)
match_criteria[key]["contains_not"] = True
matdict[key].remove('contains')
match_criteria[key]["entry_contains"].set_text(matdict[key][0])
if 'matches' in matdict[key]:
if 'not' in matdict[key]:
matdict[key].remove('not')
match_criteria[key]["matches_not_checkbox"].set_active(True)
match_criteria[key]["matches_not"] = True
matdict[key].remove('matches')
match_criteria[key]["entry_matches"].set_text(matdict[key][0])
for key in actiondict:
for action_row in self.RuleEdit.actions_list_store:
if ( action_row[1] == key ):
if ( actiondict[key] != "False" ):
action_row[0] = True
if ( actions_dict[key].has_key("input") ):
input_type = actions_dict[key]["type"]
for input_field in actions_dict[key]["input"]:
if ( input_type == "Text" ):
if ( key == "geometry" ):
geom_parts = self.parse_geom(input_field, actiondict[key])
actions_dict[key]["input"][input_field].set_text(geom_parts[input_field])
else:
actions_dict[key]["input"][input_field].set_text(actiondict[key])
elif ( input_type == "Spin" ):
actions_dict[key]["input"][input_field].set_value(int(actiondict[key]))
elif ( input_type == "Combo" ):
index = actions_dict[key]["Choices"].index(actiondict[key])
actions_dict[key]["input"][input_field].set_active(index)
def parse_geom(self, input_field, geom_string):
geom_string = geom_string.strip('"')
if ( geom_string[0] == '+' ):
geom_string = geom_string[1:]
split1 = geom_string.split('+')
if ( len(split1) == 1 ):
split2 = split1.split('x')
parsed_geom = { "width" : split2[0] , "height" : split2[1] , "xposition" : "" , "ypostion" : "" }
return parsed_geom
elif (len(split1) == 2 ):
parsed_geom = { "width" : "" , "height" : "" , "xposition" : split1[0] , "yposition" : split1[1] }
return parsed_geom
elif (len(split1) == 3 ):
split2 = split1[0].split('x')
parsed_geom = { "width" : split2[0] , "height" : split2[1] , "xposition" : split1[1] , "yposition" : split1[2] }
return parsed_geom
def UpdateDaemonStatus(self):
prog = commands.getoutput("pgrep -x devilspie -u $USER")
if ( prog == "" ):
# daemon not running
self.DaemonStatus.set_markup(_("<b>The devilspie daemon is <span foreground=\"red\">not</span> running.</b>"))
self.ToggleDaemonLabel.set_markup(_("<b><span foreground=\"dark green\">Start</span></b>"))
return 1
else:
self.DaemonStatus.set_markup(_("<b>The devilspie daemon is <span foreground=\"dark green\">running.</span></b>"))
self.ToggleDaemonLabel.set_markup(_("<b><span foreground=\"red\">Stop</span></b>"))
return prog
def toggle_daemon(self, request):
status = self.UpdateDaemonStatus()
if ( status == 1 ):
try:
self.devilspie_process = subprocess.Popen(['devilspie','-a'] , stdin=None, stdout=None, stderr=None, env=os.environ)
except OSError:
error = gtk.MessageDialog(self.RulesList, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_OK, _("There was an error starting the devilspie daemon. Please check that it is installed somewhere included in the PATH and try again."))
response = error.run()
error.destroy()
else: # kill it
if ( self.__dict__.has_key("devilspie_process") ):
try:
os.kill(int(status),signal.SIGKILL)
except OSError:
pass
self.devilspie_process.wait()
del self.__dict__["devilspie_process"]
else:
#error = gtk.MessageDialog(self.RulesList, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_INFO, gtk.BUTTONS_OK, "The devilspie daemon was started somewhere else. Trying to handle it here.")
#response = error.run()
#error.destroy()
try:
status = status.split()
for instance in status :
os.kill(int(instance),signal.SIGKILL)
except OSError:
pass
if ( request == "Start" ):
self.toggle_daemon(None)
status = self.UpdateDaemonStatus()
return
def on_ToggleDaemon_clicked(self,widget):
self.toggle_daemon(self.ToggleDaemonLabel.get_text())
def UpdateAutostartStatus(self):
if ( DISABLE_XDG == "True" ):
self.DaemonAutostart.set_sensitive(False)
else:
if (os.path.exists(xdg.DesktopEntry.xdg_config_home + "/autostart/devilspie.desktop")):
df = xdg.DesktopEntry.DesktopEntry(xdg.DesktopEntry.xdg_config_home + "/autostart/devilspie.desktop")
if (df.get('X-GNOME-Autostart-enabled') == 'true'):
self.DaemonAutostart.set_active(True)
else:
df = xdg.DesktopEntry.DesktopEntry(xdg.DesktopEntry.xdg_config_home + "/autostart/devilspie.desktop")
df.set('Name','Devilspie')
df.set('Exec','devilspie')
df.set('X-GNOME-Autostart-enabled','false')
df.write()
def on_DaemonAutostart_toggled(self,widget):
df = xdg.DesktopEntry.DesktopEntry(xdg.DesktopEntry.xdg_config_home + "/autostart/devilspie.desktop")
if (widget.get_active()):
df.set('X-GNOME-Autostart-enabled','true')
df.write()
else:
df.set('X-GNOME-Autostart-enabled','false')
df.write()
# used to delete a rule
def on_DeleteRule_clicked(self,widget):
SelectedRow = self.RulesTree.get_selection()
(model, iter) = SelectedRow.get_selected()
if (iter != None):
SelectedRule = self.rules_list_store.get(iter, 0)
RuleFile = os.path.expanduser("~/.devilspie/") + SelectedRule[0] + '.ds'
if (os.path.exists(RuleFile)):
try:
error_dialog = gtk.MessageDialog(self.RulesList, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("Are you sure you want to remove the rule ") + SelectedRule[0] + "?")
response = error_dialog.run()
error_dialog.destroy()
if ( response == gtk.RESPONSE_YES ):
os.remove(RuleFile)
self.rules_list_store.remove(iter)
else:
return
except:
error_dialog = gtk.MessageDialog(self.RulesList, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_CANCEL, _("Could not remove the rule, please check file permissions and try again."))
response = error_dialog.run()
error_dialog.destroy()
# used to update the list after a delete or add
def update_rules_list(self):
self.rules_list_store.clear()
self.fill_rules_list()
# fill up the rules list with the names of the files that end with .ds
def fill_rules_list(self):
rulefileslist = os.listdir(dir)
for rulefile in rulefileslist:
if (rulefile.endswith(".ds")):
rulefile=gobject.filename_display_name(rulefile)
rulefile=rulefile.replace(".ds","")
self.rules_list_store.append([rulefile])
# This is the rule creator window
class RuleEditorWindow:
def __init__(self):
# try to get our widgets from the gladefile
try:
wTreeEdit = gtk.glade.XML (gladefile, "RuleEdit")
except:
gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, _("Glade file not found, exiting.")).run()
quit()
# get the widgets that we use
# the window
self.RuleEdit = wTreeEdit.get_widget("RuleEdit")
# Match tree
self.MatchTree = wTreeEdit.get_widget("MatchTree")
# Actions tree
self.ActionsTree = wTreeEdit.get_widget("ActionsTreeList")
# Match parameters notebook
self.MatchPropertyParameters_notebook = wTreeEdit.get_widget("MatchPropertyParameters_notebook")
# Action parameters notebook
self.ActionsParameters_notebook = wTreeEdit.get_widget("ActionsParameters_notebook")
# rule name text box
self.RuleName_entry = wTreeEdit.get_widget("RuleName_entry")
# rule notebook
self.RuleNotebook = wTreeEdit.get_widget("RuleNotebook")
# Raw rule text view
self.RawRule = wTreeEdit.get_widget("RawRule")
# the text buffer to hold the raw rule.
self.buffer = gtk.TextBuffer(None)
# The save button
self.Save_button = wTreeEdit.get_widget("Save")
# Connect to our signals
wTreeEdit.signal_autoconnect (self)
# create list stores and connect the models to the tree views
self.match_list_store = gtk.ListStore(bool, str)
self.actions_list_store = gtk.ListStore(bool,str)
self.MatchTree.set_model(self.match_list_store)
self.ActionsTree.set_model(self.actions_list_store)
# Action tree has two columns with two cells. One cell is a checkbox and the other is text
self.ActionsNames_column=gtk.TreeViewColumn('Action')
self.ActionsEnable_column=gtk.TreeViewColumn('')
self.ActionsTree.append_column(self.ActionsEnable_column)
self.ActionsTree.append_column(self.ActionsNames_column)
self.ActionsNames_cell=gtk.CellRendererText()
self.ActionsEnable_cell=gtk.CellRendererToggle()
self.ActionsEnable_cell.set_property("activatable", 1)
self.ActionsEnable_column.pack_start(self.ActionsEnable_cell, expand=True)
self.ActionsNames_column.pack_start(self.ActionsNames_cell, expand=True)
self.ActionsNames_column.add_attribute(self.ActionsNames_cell, 'text', 1)
self.ActionsEnable_column.add_attribute(self.ActionsEnable_cell, 'active', False)
# Fill up the actions list store from the dictionary and create notebook pages for their parameters
for Action in actions_dict:
self.actions_list_store.append([0, Action])
#actions_dict[Action] = gtk.Label(Action)
actions_dict[Action]["widget"] = create_action_parameters_page(Action)
self.ActionsParameters_notebook.insert_page(actions_dict[Action]["widget"], None)
# Reflect the checkbox state in the model
self.ActionsEnable_cell.connect("toggled", self.ActionsEnable_toggle)
# Flip the notebook pages when the selection changes
self.ActionsTree.connect("cursor-changed", self.Actions_selected)
# Select first action for consistent look
self.ActionsTree.set_cursor(0, None, False)
# Match tree has two columns with two cells. One cell is a checkbox and the other is text
self.MatchPropertyNames_column=gtk.TreeViewColumn('Property')
self.MatchPropertyEnable_column=gtk.TreeViewColumn('')
self.MatchTree.append_column(self.MatchPropertyEnable_column)
self.MatchTree.append_column(self.MatchPropertyNames_column)
self.MatchPropertyName_cell=gtk.CellRendererText()
self.MatchPropertyEnable_cell=gtk.CellRendererToggle()
self.MatchPropertyEnable_cell.set_property("activatable", 1)
self.MatchPropertyEnable_column.pack_start(self.MatchPropertyEnable_cell, expand=True)
self.MatchPropertyNames_column.pack_start(self.MatchPropertyName_cell, expand=True)
self.MatchPropertyEnable_column.add_attribute(self.MatchPropertyEnable_cell, 'active', False)
self.MatchPropertyNames_column.add_attribute(self.MatchPropertyName_cell, 'text', 1)
# Fill up the actions list store from the dictionary and create notebook pages for their parameters
for MatchProperty in match_criteria:
self.match_list_store.append([0, MatchProperty])
match_criteria[MatchProperty]["widget"] = create_match_parameters_page(MatchProperty)
self.MatchPropertyParameters_notebook.insert_page(match_criteria[MatchProperty]["widget"], None)
self.MatchPropertyEnable_cell.connect("toggled", self.MatchPropertyEnable_toggle)
self.MatchTree.connect("cursor-changed", self.MatchPropertyRow_selected)
#select first row
self.MatchTree.set_cursor(0, None, False)
self.RuleEdit.show_all()
def on_RuleNotebook_change_current_page(self, widget, page, pagenum):
if ( self.RuleNotebook.get_current_page() == 2 ):
NewRule = self.buffer.get_text(self.buffer.get_start_iter(),self.buffer.get_end_iter(),False)
TempFile , TempFileName = tempfile.mkstemp(dir='/tmp')
TempFile = open(TempFileName, 'w' )
TempFile.write(NewRule)
TempFile.close()
matdict , actiondict = reader.read_file(TempFileName)
MainWindow.Parse_EditRule(matdict, actiondict)
os.remove(TempFileName)
elif ( pagenum == 2 ):
generated_rule = ""
generated_rule = generate_rule(generated_rule)
self.buffer.set_text(generated_rule)
self.RawRule.set_buffer(self.buffer)
def Actions_selected(self, widget):
selected_row = self.ActionsTree.get_selection()
(model, iter) = selected_row.get_selected()
if (iter != None):
path = model.get_string_from_iter(iter)
self.ActionsParameters_notebook.set_current_page(int(path))
self.ActionsParameters_notebook.show()
def ActionsEnable_toggle(self, widget, path):
iter = self.actions_list_store.get_iter_from_string(path)
CurrentState = self.actions_list_store.get_value(iter, 0)
self.actions_list_store.set_value(iter, 0 , not CurrentState)
def MatchPropertyRow_selected(self, widget):
selected_row = self.MatchTree.get_selection()
(model, iter) = selected_row.get_selected()
if (iter != None):
path = model.get_string_from_iter(iter)
self.MatchPropertyParameters_notebook.set_current_page(int(path))
def MatchPropertyEnable_toggle(self, widget, path):
iter = self.match_list_store.get_iter_from_string(path)
CurrentState = self.match_list_store.get_value(iter, 0)
self.match_list_store.set_value(iter, 0 , not CurrentState )
def on_RuleEdit_destroy(self,widget):
self.RuleEdit.destroy()
def on_Fill_clicked(self,widget):
if (DISABLE_FILLER == "True"):
error = gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, _("The filler is disabled."))
response = error.run()
error.destroy()
else:
self.filler_window = FillerWindow()
def on_Cancel_clicked(self,widget):
self.RuleEdit.destroy()
def on_Save_clicked(self,widget):
str = self.RuleName_entry.get_text()
self.Save_Rule(str)
def on_RuleName_entry_activate(self, widget, event):
self.RuleName_entry.set_text("")
self.RuleName_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("black"))
self.RuleName_entry.disconnect(self.handler_id)
self.Save_button.set_sensitive(True)
def Save_Rule(self, str):
if ( str == "" ):
self.Save_button.set_sensitive(False)
self.RuleName_entry.modify_text(gtk.STATE_NORMAL, gtk.gdk.color_parse("red"))
self.RuleName_entry.set_text(_("Please enter a name for the rule."))
self.handler_id = self.RuleName_entry.connect("focus-in-event", self.on_RuleName_entry_activate)
return
path = os.path.expanduser("~/.devilspie/")
new_Rule_file_name = str + ".ds"
rulefileslist = os.listdir(dir)
response = gtk.RESPONSE_YES
if ( new_Rule_file_name in rulefileslist ):
error_dialog = gtk.MessageDialog(self.RuleEdit, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_QUESTION, gtk.BUTTONS_YES_NO, _("The rule name you entered is already in use, do you want to overwrite it?"))
response = error_dialog.run()
error_dialog.destroy()
if ( response == gtk.RESPONSE_YES ):
try:
new_Rule_file_name = path + new_Rule_file_name
f = open( new_Rule_file_name, 'w' )
f.write( "; generated_rule " + str + "\n")
generated_rule = ""
generated_rule = generate_rule(generated_rule)
f.write( generated_rule )
f.close()
MainWindow.update_rules_list()
self.RuleEdit.destroy()
except:
error_dialog = gtk.MessageDialog(self.RuleEdit, gtk.DIALOG_DESTROY_WITH_PARENT, gtk.MESSAGE_ERROR, gtk.BUTTONS_CANCEL, _("Could not save the rule, please check file permissions and try again."))
response = error_dialog.run()
error_dialog.destroy()
else:
pass
class FillerWindow:
def __init__(self):
try:
# try to get our widgets from the gladefile
wFillerList = gtk.glade.XML (gladefile, "FillerDialog")
except:
#inform the user there was an error and exit
gtk.MessageDialog(None, 0, gtk.MESSAGE_ERROR, gtk.BUTTONS_CLOSE, _("Glade file not found, exiting.")).run()
quit()
self.FillerDialog = wFillerList.get_widget("FillerDialog")
self.cancel_button = wFillerList.get_widget("Filler_Cancel")
self.fill_button = wFillerList.get_widget("Filler_Apply")
self.window_tree = wFillerList.get_widget("FillerTree")
self.cancel_button.connect("clicked", self.on_Filler_Cancel_clicked)
self.fill_button.connect("clicked", self.on_Filler_Apply_clicked)
self.FillerDialog.connect("destroy", self.on_FillerDialog_destroy)
self.window_liststore = gtk.ListStore(gobject.TYPE_STRING)
self.window_name_cell = gtk.CellRendererText()
self.window_names_column = gtk.TreeViewColumn("Available Windows")
self.window_names_column.pack_start(self.window_name_cell, expand=True)
self.window_names_column.add_attribute(self.window_name_cell, 'text', 0)
self.window_tree.append_column(self.window_names_column)
self.window_tree.set_model(self.window_liststore)
self.windowlist, self.namelist = filler.Get_Windowname_List()
for name in self.namelist:
self.window_liststore.append([name])
self.window_tree.connect("row-activated", self.on_window_tree_row_activated)
self.FillerDialog.show_all()
def on_window_tree_row_activated(self, widget, path, view_column):
self.on_Filler_Apply_clicked(widget)
def on_Filler_Apply_clicked(self, widget):
selected_window = self.window_tree.get_selection()
( model , iter ) = selected_window.get_selected()
path = model.get_path(iter)
window_object = self.windowlist[path[0]]
window_match_dict = filler.Matchdict_Window(window_object)
window_action_dict = filler.Actiondict_Window(window_object)
for key in window_match_dict:
Entry = match_criteria[key]["entry_is"]
Entry.set_text(str(window_match_dict[key]))
Entry = actions_dict["geometry"]["input"]["xposition"]
Entry.set_text(str(window_action_dict["xposition"]))
Entry = actions_dict["geometry"]["input"]["yposition"]
Entry.set_text(str(window_action_dict["yposition"]))
Entry = actions_dict["geometry"]["input"]["width"]
Entry.set_text(str(window_action_dict["width"]))
Entry = actions_dict["geometry"]["input"]["height"]
Entry.set_text(str(window_action_dict["height"]))
Entry = actions_dict["set_workspace"]["input"]["workspace"]
Entry.set_text(str(window_action_dict["set_workspace"]))
for row in MainWindow.RuleEdit.actions_list_store:
if ( row[1] == "fullscreen"):
if (window_action_dict["fullscreen"] == True):
row[0] = True
elif ( row[1] == "maximize"):
if (window_action_dict["maximize"] == True):
row[0] = True
elif ( row[1] == "maximize_horizontally"):
if (window_action_dict["maximize_horizontally"] == True):
row[0] = True
elif ( row[1] == "maximize_vertically"):
if (window_action_dict["maximize_vertically"] == True):
row[0] = True
elif ( row[1] == "minimize"):
if (window_action_dict["minimize"] == True):
row[0] = True
elif ( row[1] == "shade"):
if (window_action_dict["shade"] == True):
row[0] = True
elif ( row[1] == "pin"):
if (window_action_dict["pin"] == True):
row[0] = True
elif ( row[1] == "stick"):
if (window_action_dict["stick"] == True):
row[0] = True
else:
pass
self.FillerDialog.destroy()
def on_FillerDialog_destroy(self, widget):
self.FillerDialog.destroy()
def on_Filler_Cancel_clicked(self, widget):
self.FillerDialog.destroy()
gtk.gdk.threads_init()
MainWindow = RulesListWindow()
gtk.main()
Et voici ce que me renvoie le terminal :
no@no-A8SR:~$ gdevilspie
Traceback (most recent call last):
File "/usr/bin/gdevilspie", line 893, in <module>
MainWindow = RulesListWindow()
File "/usr/bin/gdevilspie", line 374, in __init__
self.UpdateAutostartStatus()
File "/usr/bin/gdevilspie", line 545, in UpdateAutostartStatus
if (os.path.exists(xdg.DesktopEntry.xdg_config_home + "/autostart/devilspie.desktop")):
AttributeError: 'module' object has no attribute 'xdg_config_home'
AUTRE MÉTHODE :
no@no-A8SR:~$ /usr/bin/gdevilspie
Traceback (most recent call last):
File "/usr/bin/gdevilspie", line 893, in <module>
MainWindow = RulesListWindow()
File "/usr/bin/gdevilspie", line 374, in __init__
self.UpdateAutostartStatus()
File "/usr/bin/gdevilspie", line 545, in UpdateAutostartStatus
if (os.path.exists(xdg.DesktopEntry.xdg_config_home + "/autostart/devilspie.desktop")):
AttributeError: 'module' object has no attribute 'xdg_config_home'
no@no-A8SR:~$
Apparemment quelque chose n'est pas ou mal paramétré... ?
Dernière modification par hackoeur (Le 25/07/2014, à 21:07)
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#18 Le 26/07/2014, à 08:23
- ljere
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
c'est ce que je pensai tu as laissé mon identifiant au lieu du tien sur cette ligne
(and (contains (application_name) "Thunar") (contains(window_name) "jerem - Gestionnaire de fichiers"))
doit devenir
(and (contains (application_name) "Thunar") (contains(window_name) "no - Gestionnaire de fichiers"))
et pour cette commande
exo-open --launch FileManager %u && devilspie -d &
remplace par
exo-open --launch FileManager && devilspie -d &
ancien PC Toshiba satellite_c670d-11 / Linux Mint 21 Vanessa
Nouveau PC ASUS TUF GAMING A17 GPU RTX 4070 CPU AMD Ryzen 9 7940HS w/ Radeon 780M Graphics / Linux Mint 21.2 Victoria / Kernel: 6.4.8-1-liquorix / Desktop: Cinnamon
Hors ligne
#19 Le 26/07/2014, à 09:24
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
Bonjour ljere,
Bien vu, et je n'ai pas eu le temps de te rapporter le retour hier quand je l'ai corrigé (en relisant chaque lettre du script).
Donc voici ce que j'ai copié hier soir du terminal
no@no-A8SR:~$ sudo nano /home/no/.devilspie
[sudo] password for no:
no@no-A8SR:~$ sudo nano /home/no/.devilspie/thunar.ds
no@no-A8SR:~$ exo-open --launch FileManager && devilspie -d &
[1] 5516
no@no-A8SR:~$ Devil's Pie 0.23 starting...
Loading /etc/devilspie
/etc/devilspie doesn't exist
Loading /home/no/.devilspie
Loading /home/no/.devilspie/debug.ds
Loading /home/no/.devilspie/thunar.ds
2 s-expressions loaded.
Window Title: 'Terminal - no@no-A8SR: ~'; Application Name: 'Terminal Xfce'; Class: 'Xfce4-terminal'; Geometry: 511x399+0+0
Window Title: 'Param?trer et fixer la taille par d?faut des fen?tres Xfce (Page 2) / XFCE / Forum Ubuntu-fr.org - Chromium'; Application Name: 'Param?trer et fixer la taille par d?faut des fen?tres Xfce (Page 2) / XFCE / Forum Ubuntu-fr.org - Chromium'; Class: 'Chromium-browser'; Geometry: 1280x780+0+0
Window Title: 'Bureau'; Application Name: 'xfdesktop'; Class: 'Xfdesktop'; Geometry: 1280x800+0+0
Window Title: 'xfce4-panel'; Application Name: 'xfce4-panel'; Class: 'Xfce4-panel'; Geometry: 1280x20+0+780
Window Title: 'no - Gestionnaire de fichiers'; Application Name: 'Thunar'; Class: 'Thunar'; Geometry: 511x381+511+0
Setting below
Removed decorations
Skipping pager
Setting pinned
Skipping tasklist
Set wintype
Setting geometry '800x315+0+554'
Window Title: 'Opera'; Application Name: 'Opera'; Class: 'Opera'; Geometry: 769x780+174+0
Window Title: 'Rhythmbox'; Application Name: 'Rhythmbox'; Class: 'Rhythmbox'; Geometry: 794x586+0+0
Window Title: 'gedit'; Application Name: 'gedit'; Class: 'Gedit'; Geometry: 521x526+0+0
no@no-A8SR:~$
Excellent.
Comme je te l'ai dit, il me reste à savoir comment l'utiliser et comme la pluie est sur nous ici, la virée est annulée, vive les tutos...
Puis-je abuser et te demander de m'explique pourquoi la version graphique gdevilspie ne fonctionne pas ?
no@no-A8SR:~$ gdevilspie
Traceback (most recent call last):
File "/usr/bin/gdevilspie", line 893, in <module>
MainWindow = RulesListWindow()
File "/usr/bin/gdevilspie", line 374, in __init__
self.UpdateAutostartStatus()
File "/usr/bin/gdevilspie", line 545, in UpdateAutostartStatus
if (os.path.exists(xdg.DesktopEntry.xdg_config_home + "/autostart/devilspie.desktop")):
AttributeError: 'module' object has no attribute 'xdg_config_home'
no@no-A8SR:~$ gksudo thunar
** (gedit:12077): WARNING **: Couldn't connect to accessibility bus: Failed to connect to socket /tmp/dbus-AW7vgXLtAJ: Connexion refusée
(gedit:12077): Gtk-WARNING **: Theme parsing error: gtk-widgets.css:2769:41: Expected a valid selector
Traceback (most recent call last):
File "/usr/bin/gdevilspie", line 893, in <module>
MainWindow = RulesListWindow()
File "/usr/bin/gdevilspie", line 374, in __init__
self.UpdateAutostartStatus()
File "/usr/bin/gdevilspie", line 545, in UpdateAutostartStatus
if (os.path.exists(xdg.DesktopEntry.xdg_config_home + "/autostart/devilspie.desktop")):
AttributeError: 'module' object has no attribute 'xdg_config_home'
no@no-A8SR:~$
Merci encore infiniment.
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#20 Le 26/07/2014, à 09:37
- ljere
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
oulala qu'est ce que tu as fait
je vois ta première commande et je ne comprend pas ou j'ai dis d'utiliser sudo
sudo nano /home/no/.devilspie/thunar.ds ???
en faisant ça tu viens sûrement de rendre root propriétaire du fichier thunar.ds
donne moi le retour de
ls -l /home/no/.devilspie
pour gdevilspie je ne suis pas certain qu'il fonctionne avec xfce
ancien PC Toshiba satellite_c670d-11 / Linux Mint 21 Vanessa
Nouveau PC ASUS TUF GAMING A17 GPU RTX 4070 CPU AMD Ryzen 9 7940HS w/ Radeon 780M Graphics / Linux Mint 21.2 Victoria / Kernel: 6.4.8-1-liquorix / Desktop: Cinnamon
Hors ligne
#21 Le 26/07/2014, à 10:28
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
oulala qu'est ce que tu as fait
je vois ta première commande et je ne comprend pas ou j'ai dis d'utiliser sudosudo nano /home/no/.devilspie/thunar.ds ???
en faisant ça tu viens sûrement de rendre root propriétaire du fichier thunar.ds
Aïe pas la tête patron !
J'utilise toujours "nano" quand il s'agit d'aller dans les fichiers gedit en lieu et place de "gksudo". Je suis les docs sur l'utilistion de "nano", mais je ne savais pas que ça endait le root propriétaire.
donne moi le retour de
ls -l /home/no/.devilspie
OK voilà ça :
no@no-A8SR:~$ ls -l /home/no/.devilspie
total 16
-rw-rw-r-- 1 no no 8 jui 25 20:45 debug.ds
-rw-rw-r-- 1 no no 283 jui 25 20:43 debug.ds~
-rw-rw-r-- 1 no no 272 jui 26 10:10 thunar.ds
-rw-rw-r-- 1 no no 275 jui 25 18:43 thunar.ds~
no@no-A8SR:~$
pour gdevilspie je ne suis pas certain qu'il fonctionne avec xfce
OK, c'était plus pour savoir et je préfère la version-console anyway.
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#22 Le 26/07/2014, à 10:58
- ljere
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
ça va il n'y a pas eu de changement de propriétaire, pour nano c'est très bien,
le souci c'est sudo quand tu modifies un fichier qui t'appartient
la bonne commande est donc
nano /home/no/.devilspie/thunar.ds
ancien PC Toshiba satellite_c670d-11 / Linux Mint 21 Vanessa
Nouveau PC ASUS TUF GAMING A17 GPU RTX 4070 CPU AMD Ryzen 9 7940HS w/ Radeon 780M Graphics / Linux Mint 21.2 Victoria / Kernel: 6.4.8-1-liquorix / Desktop: Cinnamon
Hors ligne
#23 Le 26/07/2014, à 11:14
- hackoeur
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
ça va il n'y a pas eu de changement de propriétaire, pour nano c'est très bien,
le souci c'est sudo quand tu modifies un fichier qui t'appartient
la bonne commande est doncnano /home/no/.devilspie/thunar.ds
Ok, Copy that.
Il ne me reste plus qu'à apprendre à maîtriser la bête.
Je laisse le sujet ouvert pour d'autres, ou pour moi dans mes premiers pas...
Merci encore ljere.
laptop Intel® Core™ Kaby Lake i7-7700T cpu @ 2.9-3.8GHz |
x86_64 | ram 16Gio | FHD 1920x1080 | [Intel® HD Graphics 630]
HDD-SATA 1T/DATA | SSD-m.2 250GB = Manjaro-Xfce stable + Xubuntu-Xfce 20.04 LTS
Hors ligne
#24 Le 26/07/2014, à 11:27
- ljere
Re : [Résolu] Paramétrer et fixer la taille des fenêtres Xfce → devilspie
de rien, oui on laisse e sujet ouvert par contre je le passe en [Résolu]
ancien PC Toshiba satellite_c670d-11 / Linux Mint 21 Vanessa
Nouveau PC ASUS TUF GAMING A17 GPU RTX 4070 CPU AMD Ryzen 9 7940HS w/ Radeon 780M Graphics / Linux Mint 21.2 Victoria / Kernel: 6.4.8-1-liquorix / Desktop: Cinnamon
Hors ligne