Creation of a transparent shutdown GUI using Gtk and Cairo in Python

Δημιουργία μιας διαφανούς γραφικής διεπαφής κλεισίματος με τη χρήση Gtk και Cairo σε Python

· Coding Προγραμματισμός · linux linux cairo cairo gtk gtk python python

Many window managers in Linux are lacking a graphical «shutdown» interface. In our previous article, we showed how we can implement one, quite easily, using GTK and Python. Now, we will show how to make it transparent using Cairo.

Πολλοί διαχειριστές παραθύρων στο Linux δε διαθέτουν μια γραφική διεπαφή «κλεισίματος». Σε προηγούμενο άρθρο μας, δείξαμε πώς μπορείτε να υλοποιήσετε μία, αρκετά εύκολα, χρησιμοποιώντας GTK και Python. Τώρα, θα δείξουμε πως μπορείτε να την κάνετε διάφανη χρησιμοποιώντας Cairo.

Transparent 3x3 square shutdown GUI
You can find free icons for every use on Μπορείτε να βρείτε δωρεάν εικονίδια για κάθε χρήση στο openclipart.org

First, let's add the necessary imports:

Πρώτα από όλα, ας προσθέσουμε τα απαραίτητα import:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import gtk
import cairo
from math import pi
from collections import OrderedDict

We offer two different layouts, one fullscreen and one windowed. Change the following variable according to your preference:

Προσφέρουμε δύο διαφορετικές διατάξεις, μια πλήρους οθόνης και μια παραθυριακή. Αλλάξτε την ακόλουθη μεταβλητή σύμφωνα με την προτίμησή σας:

FULL_SCREEN = False

We define a get_resource_path function to help us find the path of our icons in the system:

Ορίζουμε μια συνάρτηση get_resource_path για να μας βοηθήσει να βρούμε την τοποθεσία των εικονιδίων μας στο σύστημα:

def get_resource_path(rel_path):
    dir_of_py_file = os.path.dirname(__file__)
    rel_path_to_resource = os.path.join(dir_of_py_file, rel_path)
    abs_path_to_resource = os.path.abspath(rel_path_to_resource)
    return abs_path_to_resource

Next, we define our SystemDialog class (inherits from gtk.Window):

Στη συνέχεια, ορίζουμε την κλάση SystemDialog (κληρονομεί από την gtk.Window):

class SystemDialog (gtk.Window):
    ...

Inside the class we define the __init__ (do not forget to call the super):

Μέσα στην κλάση μας ορίζουμε την __init__ (μην ξεχάσετε να καλέσετε την super):

def __init__(self):
    super(SystemDialog, self).__init__()
    ...

Inside the __init__ we define a dictionary with label/command key-value pairs. The reason we use an OrderedDict is that the simple dict in python does not keep the order of the keys but we do not want our buttons to appear in arbitrary order. Keep in mind that you have to edit the button commands according to your system and your needs.

Μέσα στην __init__ ορίζουμε ένα λεξικό με ζευγάρια από ετικέτες και εντολές. Ο λόγος που χρησιμοποιούμε ένα OrderedDict είναι επειδή το απλό dict στην Python δεν συγκρατεί τη σειρά των λημμάτων (=κλειδιών), αλλά εμείς δε θέλουμε τα κουμπιά μας να εμφανίζονται με αυθαίρετη σειρά. Σημειώστε ότι θα πρέπει να τροποποιήσετε τις εντολές που αντιστοιχούν στα κουμπιά ανάλογα με το σύστημα σάς και τις ανάγκες σάς.

self.actions = OrderedDict([
    ("Cancel", None),
    ("Lock", "slock &"),
    ("Restart WM", "sudo killall dwm &"),
    ("Sleep", "sudo pm-suspend &"),
    ("Hibernate", "sudo pm-hibernate &"),
    ("Sleep + Hibernate", "sudo pm-suspend-hybrid &"),
    ("Logout", "sudo killall X &"),
    ("Reboot", "sudo reboot &"),
    ("Shutdown", "sudo poweroff &")])

We need some small_vboxes to align vertically each icon with its label. Οur clickable "buttons" will be the event_boxes which will contain the small_vboxes.

Χρειαζόμαστε μερικά μικρά κουτιά (small_vboxes ) για να στοιχίσουμε κάθετα κάθε εικόνιδιο με την ετικέτα του. Τα κουμπιά μας που θα πατιούνται θα είναι τα event_boxes που θα εμπεριέχουν τα small_vboxes.

self.small_vboxes = {}
self.event_boxes = {}
self.labels = {}
Label

If the system supports it, we will use an rgba colormap (i.e. one with an alpha channel). Note: You need a compositor manager, like compton or xcompmgr for example, in order for transperancy to work.

Αν το σύστημα το υποστηρίζει, θα χρησιμοποιήσουμε ένα χάρτης χρώματος rgba, δηλαδή έναν που διαθέτει και κανάλι άλφα. Σημείωση: Χρειάζεστε έναν διαχειριστή σύνθεσης (compositor manager), όπως ο compton ή ο xcompmgr για παράδειγμα, για να δουλέψει η διαφάνεια.

self.screen = self.get_screen()
self.rgba_colormap = self.screen.get_rgba_colormap()
if (self.rgba_colormap is not None and self.screen.is_composited()):
    self.set_colormap(self.rgba_colormap)

We make our window undecorated, paintable and we keep it on top.

Ρυθμίζουμε το παράθυρό μας να μην έχει διακοσμητικά στοιχεία, να μπορεί να ζωγραφιστεί και να παραμένει πάνω από τα άλλα παράθυρα.

self.set_decorated(False)
self.set_app_paintable(True)
self.set_keep_above(True)

Some more settings, mostly for the fullscreen case. The function set_border_width sets the size of the outer margin. Adjust it to your liking.

Μερικές ακόμη ρυθμίσεις, κυρίως για την περίπτωση της πλήρους οθόνης. Η συνάρτηση set_border_width θέτει το μέγεθος του εξωτερικού περιθωρίου. Προσαρμόστε το κατά την αρεσκεία σας.

if FULL_SCREEN:
    self.set_border_width(150)
    self.set_size_request(self.screen.get_width(),
                          self.screen.get_height())
    self.set_position(gtk.WIN_POS_CENTER)
else:
    self.set_border_width(5)

We define one vertical box and three horizontal boxes. The vertical box will contain and align vertically the 3 horizontal boxes while each horizontal box while keep and align horizontally 3 of our "buttons (events_boxes). Therefore, in the end, we will have a nice 3x3 icon square, like this:

Ορίζουμε ένα κάθετο κουτί και τρία οριζόντια κουτιά. Το κάθετο κουτί θα εμπεριέχει και θα στοιχίζει κάθετα τα 3 οριζόντια κουτιά, ενώ κάθε οριζόντιο κουτί θα περιέχει και θα στοιχίζει οριζόντια 3 από τα "κουμπιά" μας (events_boxes). Οπότε, τελικά, θα έχουμε ένα ωραίο τετράγωνο 3x3 γεμάτο με εικόνιδια, όπως αυτό:

self.vbox = gtk.VBox(True, 5)
self.hboxes = [gtk.HBox(True, 5),
               gtk.HBox(True, 5),
               gtk.HBox(True, 5)]

The True values is about the homogenous (i.e. the same) size of the children and 5 is the amount of space between them.

Οι τιμές True έχουν να κάνουν με την ισόποση κατανομή του χώρου μεταξύ των περιεχομένων, ενώ το 5 είναι το κενό διάστημα ανάμεσά τους.

Now, we are ready to create our "buttons". We load each icon and we pack/align it vertically with its label inside a small_vbox. We add each of the small_vboxes to an event_box. We connect the click of event_box with a callback event handler and we pack them inside the empty horizontal boxes (3 "buttons" in each horizontal box). Finally, to make our "buttons" transparent, we have to connect their expose-event with our self.expose function and to set them as paintable.

Τώρα, είμαστε έτοιμοι να δημιουργήσουμε τα "κουμπιά" μας. Φορτώνουμε κάθε εικονίδιο και το πακετάρουμε/στοιχίζουμε κάθετα με την ετίκετά του μέσα σε ένα small_vbox. Προσθέτουμε το καθένα από τα small_vboxes σε ένα διαφανές event_box. Συνδέσουμε το πάτημά των event_boxes με έναν επανακαλούμενο χειριστή συμβάντων και τα πακετάρουμε μέσα στα άδεια οριζόντια κουτιά (3 κουμπιά σε κάθε οριζόντιο κουτί). Τέλος, για να καταστήσουμε τα "κουμπιά" μας διαφανή, θα πρεπει να συνδεσουμε το expose-event τους με την self.expose συνάρτηση μας και να τα θέσουμε ως επιδεκτικά βαψίματος (paintable).

c = 0
boxIndex = 0

for key in self.actions.keys():

    # Load image
    ico = gtk.Image()
    ico.set_from_file(get_resource_path("images/"+key+".png"))
    
    # Load label
    self.labels[key] = gtk.Label(key)
    self.labels[key].modify_fg(gtk.STATE_NORMAL,
                               gtk.gdk.color_parse('#FFFFFF'))
                               
    # Pack/align image and label vertically
    self.small_vboxes[key] = gtk.VBox(False, 3)
    self.small_vboxes[key].pack_start(ico)
    self.small_vboxes[key].pack_start(self.labels[key])
    
    # Create transparent "buttons" (event_boxes)
    self.event_boxes[key] = gtk.EventBox()
    self.event_boxes[key].add(self.small_vboxes[key])
    self.event_boxes[key].connect('button-release-event',
                                  self.callback, key)
    self.event_boxes[key].connect('expose-event', self.expose)
    self.event_boxes[key].set_app_paintable(True)
    
    # Pack/align up to 3 "buttons" (event_boxes) horizontally
    self.hboxes[boxIndex].pack_start((self.event_boxes[key]))
    c += 1
    if not (c % 3):
        boxIndex += 1

And now, let's pack the horizontal boxes inside the vertical box and the vertical box inside our window. Do not forget to show all our widgets with self.show_all().

Και τώρα, ας πακετάρουμε τα οριζόντια κουτιά μέσα στο κάθετο κουτί και το κάθετο κουτί μέσα στο παράθυρό μας. Μην ξεχάσετε να εμφανίσετε όλα τα γραφικά στοιχεία σας με την self.show_all().

for hbox in self.hboxes:
    self.vbox.pack_start(hbox, False, False, 0)
self.add(self.vbox)
self.show_all()

Finally, we connect some events for our window with their callback handlers:

Τέλος, συνδέουμε μερικά συμβάντα για το παράθυρό μας με τους επανακαλούμενους χειριστές τους:

# For our window to be transparent
self.connect('expose-event', self.expose)
# If our window is destroyed, call self.callback (to exit)
self.connect("delete-event", self.callback)
# If a key is pressed, call self.key_press_event
self.connect("key-press-event", self.key_press_event)

If our window is not fullscreen, we can move it where we want it. Let's put it on the bottom right corner of the screen.

Αν το παράθυρο μας δεν είναι πλήρους οθόνης, μπορούμε να το μετακινήσουμε όπου θέλουμε. Ας το βάλουμε στην κάτω δεξιά γωνία της οθόνης.

if not FULL_SCREEN:
    w, h = self.get_size()
    self.move(gtk.gdk.screen_width() - w,
              gtk.gdk.screen_height() - h - 22)

The function expose is where the magic of transparency happens. Υou can set the color and the opacity (alpha channel) of our window via the set_source_rgba(r, g, b, a) .

Η μαγεία της διαφάνειας συμβαίνει μέσα στην συνάρτηση expose. Μπορείτε να καθορίσετε την απόχρωση και την αδιαφάνεια (κανάλι άλφα) του παραθύρου μας μέσω της set_source_rgba(r, g, b, a).

def expose(self, widget, event):
    cr = widget.get_window().cairo_create()
    cr.set_source_rgba(0, 0, 0, 0.65)
    cr.set_operator(cairo.OPERATOR_SOURCE)
    cr.paint()
    cr.set_operator(cairo.OPERATOR_OVER)
    return False

This is the our keyboard callback/event handler function. If the user has pressed Escape, we quit:

Αυτή είναι η επανακαλούμενη συνάρτησή μας για τον χειρισμό συμβάντων πληκτρολογίου. Αν ο χρήστης έχει πατήσει το Escape, βγαίνουμε από την εφαρμογή:

def key_press_event(self, widget = None, event = None):
    keyval = event.keyval
    keyval_name = gtk.gdk.keyval_name(keyval)
    if keyval_name == "Escape":
        gtk.main_quit()
    return False

If you would like to check for some key modifier/combination you can -for example- get the ctrl key status like this:

Αν τυχόν επιθυμείτε να ελέγξετε για κάποιο ειδικό πλήκτρο ή συνδυασμό μπορείτε -για παράδειγμα- να λάβετε την κατάσταση του πλήκτρου ctrl ως εξής:

state = event.state
ctrl = (state & gtk.gdk.CONTROL_MASK)

This is the our generic callback function which executes the commands:

Αυτή είναι η γενική μας επανακαλούμενη συνάρτηση που εκτελεί τις εντολές:

def callback(self, widget=None, event=None, data=None):
    if (data is not None and
       data in self.actions and
       self.actions[data] is not None and
       event.button == 1):  # left click
            os.system(self.actions[data])
    gtk.main_quit()

When our program executes, an SystemDialog object is created and gtk.main() is called:

Όταν η εφαρμογή μας εκτελείται, δημιουργείται ένα αντικείμενο SystemDialog και καλείται η gtk.main():

if __name__ == "__main__":
    SystemDialog()
    gtk.main()

Last but not least: In order for our program to function without having to ask for a password, you need to add some lines in your /etc/sudoers file. This is a special system file and you have to run visudo with root privileges to edit it:

Σημαντικό: Προκειμένου η εφαρμογή μας να λειτουργεί χωρίς να χρειάζεται να ζητάει κωδικό, θα πρέπει να προσθέσετε μερικές γραμμές στο αρχείο /etc/sudoers. Πρόκειται για ένα ειδικό αρχείο του συστήματος το οποίο, για να το διορθώσετε, θα πρέπει να τρέξετε την εντολή visudo έχοντας δικαιώματα διαχειριστή:

# visudo
...
username ALL=(root) NOPASSWD: /sbin/reboot
username ALL=(root) NOPASSWD: /sbin/poweroff
username ALL=(root) NOPASSWD: /usr/bin/killall
username ALL=(root) NOPASSWD: /usr/sbin/pm-suspend
username ALL=(root) NOPASSWD: /usr/sbin/pm-suspend-hybrid
username ALL=(root) NOPASSWD: /usr/sbin/pm-hibernate
...

Source codeΠηγαίος κώδικας

Here is the full code listing: shutdown-manager-transparent

For the gtk3 version, see here: shutdown-manager-transparent-gtk3

Μπορείτε να δείτε τον πλήρη κώδικα εδώ: shutdown-manager-transparent

Για την gtk3 έκδοση, δείτε εδώ: shutdown-manager-transparent-gtk3

See also...

Δείτε επίσης...