|
| 1 | +#!/usr/bin/env python |
| 2 | +# -*- coding: utf-8 -*- |
| 3 | +''' |
| 4 | +************************************************************** |
| 5 | +* Description: A transparent gtk GUI with buttons bound to * |
| 6 | +* commands. It can be used as a logoff/shutdown * |
| 7 | +* manager for windows managers that lack one. * |
| 8 | +* * |
| 9 | +* Licence : Public Domain. * |
| 10 | +* * |
| 11 | +* Author : Antonios Tsolis (2016) * |
| 12 | +************************************************************** |
| 13 | +''' |
| 14 | + |
| 15 | +import os |
| 16 | +import gi |
| 17 | +import cairo |
| 18 | +from math import pi |
| 19 | +gi.require_version("Gtk", "3.0") |
| 20 | +gi.require_version('Gdk', '3.0') |
| 21 | +from gi.repository import Gtk |
| 22 | +from gi.repository import Gdk |
| 23 | +from collections import OrderedDict |
| 24 | + |
| 25 | +FULL_SCREEN = False |
| 26 | + |
| 27 | + |
| 28 | +# We define a get_resource_path function to help us find |
| 29 | +# the path of our icons in the system: |
| 30 | +def get_resource_path(rel_path): |
| 31 | + dir_of_py_file = os.path.dirname(__file__) |
| 32 | + rel_path_to_resource = os.path.join(dir_of_py_file, rel_path) |
| 33 | + abs_path_to_resource = os.path.abspath(rel_path_to_resource) |
| 34 | + return abs_path_to_resource |
| 35 | + |
| 36 | + |
| 37 | +class SystemDialog (Gtk.Window): |
| 38 | + def __init__(self): |
| 39 | + super(SystemDialog, self).__init__() |
| 40 | + |
| 41 | + # We define a dictionary with button label-command key-value |
| 42 | + # pairs. The reason we use an OrderedDict is that in python |
| 43 | + # the simple dict does not keep the order of the keys and we |
| 44 | + # do not want our buttons to appear in arbitrary order. |
| 45 | + # Keep in mind that you have to edit the commands according |
| 46 | + # to your system and your needs. |
| 47 | + self.actions = OrderedDict([ |
| 48 | + ("Cancel", None), |
| 49 | + ("Lock", "slock &"), |
| 50 | + ("Restart WM", "sudo killall dwm &"), |
| 51 | + ("Sleep", "sudo pm-suspend &"), |
| 52 | + ("Hibernate", "sudo pm-hibernate &"), |
| 53 | + ("Sleep + Hibernate", "sudo pm-suspend-hybrid &"), |
| 54 | + ("Logout", "sudo killall X &"), |
| 55 | + ("Reboot", "sudo reboot &"), |
| 56 | + ("Shutdown", "sudo poweroff &")]) |
| 57 | + |
| 58 | + # We need some small vboxes to align vertically each icon with |
| 59 | + # its label. Οur clickable "buttons" will be the event_boxes which |
| 60 | + # will contain the small_vboxes. |
| 61 | + self.small_vboxes = {} |
| 62 | + self.event_boxes = {} |
| 63 | + self.labels = {} |
| 64 | + |
| 65 | + # If the system supports it, we will use an rgba visual |
| 66 | + # (i.e. one with an alpha channel). |
| 67 | + # Note: you need a compositor manager, like compton or xcompmgr |
| 68 | + # for example, in order for transperancy to work. |
| 69 | + screen = self.get_screen() |
| 70 | + visual = screen.get_rgba_visual() |
| 71 | + if visual and screen.is_composited(): |
| 72 | + self.set_visual(visual) |
| 73 | + |
| 74 | + # We make our window undecorated, paintable |
| 75 | + # and we keep it on top. |
| 76 | + self.set_decorated(False) |
| 77 | + self.set_keep_above(True) |
| 78 | + self.set_app_paintable(True) |
| 79 | + |
| 80 | + display = Gdk.Display.get_default() |
| 81 | + monitor = display.get_primary_monitor() |
| 82 | + geometry = monitor.get_geometry() |
| 83 | + scale_factor = monitor.get_scale_factor() |
| 84 | + screen_width = scale_factor * geometry.width |
| 85 | + screen_height = scale_factor * geometry.height |
| 86 | + |
| 87 | + # Some more settings, mostly for the fullscreen option. The function |
| 88 | + # set_border_width sets the size of the outer margin. Adjust it to |
| 89 | + # your liking. |
| 90 | + if FULL_SCREEN: |
| 91 | + self.set_border_width(150) |
| 92 | + self.set_size_request(screen_width, screen_height) |
| 93 | + self.set_position(Gtk.WindowPosition.CENTER) |
| 94 | + else: |
| 95 | + self.set_border_width(5) |
| 96 | + |
| 97 | + # We define one vertical box and three horizontal boxes. |
| 98 | + # The vertical box will contain and align vertical the 3 |
| 99 | + # horizontal boxes while each horizontal box while keep and align |
| 100 | + # horizontally 3 of our buttons. Therefore, in the end, we will |
| 101 | + # have a nice 3x3 buttons square |
| 102 | + self.vbox = Gtk.VBox(homogeneous=True, spacing=20) |
| 103 | + self.hboxes = [Gtk.HBox(homogeneous=True, spacing=5), Gtk.HBox(homogeneous=True, spacing=5), Gtk.HBox(homogeneous=True, spacing=5)] |
| 104 | + |
| 105 | + # Now, we are ready to create our "buttons". We load each icon and |
| 106 | + # we pack/align it vertically with its label inside a small_vbox. |
| 107 | + # We add each of the small_vboxes to an event_box. We connect their |
| 108 | + # click with a callback event handler and we pack them inside the |
| 109 | + # empty horizontal boxes (3 "buttons" in each horizontal box). |
| 110 | + # Finally, to make our "buttons" transparent, we have to connect |
| 111 | + # their draw event with our self.draw function and to set them |
| 112 | + # as paintable. |
| 113 | + c = 0 |
| 114 | + boxIndex = 0 |
| 115 | + for key in self.actions.keys(): |
| 116 | + # Load image |
| 117 | + ico = Gtk.Image() |
| 118 | + ico.set_from_file(get_resource_path("images/"+key+".png")) |
| 119 | + # Load label |
| 120 | + self.labels[key] = Gtk.Label(label=key) |
| 121 | + #self.labels[key].override_color(Gtk.StateFlags.NORMAL, Gdk.RGBA(1, 1, 1, 1)) |
| 122 | + # Pack/align image and label vertically |
| 123 | + self.small_vboxes[key] = Gtk.VBox(homogeneous=False, spacing=3) |
| 124 | + self.small_vboxes[key].pack_start(ico, 0, 1, 0) |
| 125 | + self.small_vboxes[key].pack_start(self.labels[key], True, True, 0) |
| 126 | + # Create transparent "buttons" (event_boxes) |
| 127 | + self.event_boxes[key] = Gtk.EventBox() |
| 128 | + self.event_boxes[key].add(self.small_vboxes[key]) |
| 129 | + self.event_boxes[key].connect('button-release-event', |
| 130 | + self.callback, key) |
| 131 | + self.event_boxes[key].connect('draw', self.draw) |
| 132 | + self.event_boxes[key].set_app_paintable(True) |
| 133 | + # Pack/align up to 3 "buttons" (event_boxes) horizontally |
| 134 | + self.hboxes[boxIndex].pack_start((self.event_boxes[key]), True, True, 0) |
| 135 | + c += 1 |
| 136 | + if not (c % 3): |
| 137 | + boxIndex += 1 |
| 138 | + |
| 139 | + # And now, let's pack the horizontal boxes inside the vertical box and |
| 140 | + # the vertical box inside our window. Do not forget to show all our |
| 141 | + # widgets with self.show_all() |
| 142 | + for hbox in self.hboxes: |
| 143 | + self.vbox.pack_start(hbox, True, True, 0) |
| 144 | + self.add(self.vbox) |
| 145 | + self.show_all() |
| 146 | + |
| 147 | + # Finally, we connect some events for our window with |
| 148 | + # their callback handlers: |
| 149 | + # For our window to be transparent |
| 150 | + self.connect('draw', self.draw) |
| 151 | + # If our window is destroyed, call self.callback (to exit) |
| 152 | + self.connect("delete-event", self.callback) |
| 153 | + # If a key is pressed, call self.key_press_event |
| 154 | + self.connect("key-press-event", self.key_press_event) |
| 155 | + |
| 156 | + # If our window is not fullscreen, we can move it where we want it. |
| 157 | + # Let's put it on the bottom right corner of the screen. |
| 158 | + if not FULL_SCREEN: |
| 159 | + w, h = self.get_size() |
| 160 | + self.move(screen_width - w, screen_height - h - 40) |
| 161 | + |
| 162 | + # This is the function where the magic of transparency happens. Using |
| 163 | + # the function set_source_rgba(r, g, b, a) you can set the color and the |
| 164 | + # opacity of our window. |
| 165 | + def draw(self, widget, event): |
| 166 | + cr = widget.get_window().cairo_create() |
| 167 | + cr.set_source_rgba(0, 0, 0, 0.65) |
| 168 | + cr.set_operator(cairo.OPERATOR_SOURCE) |
| 169 | + cr.paint() |
| 170 | + cr.set_operator(cairo.OPERATOR_OVER) |
| 171 | + return False |
| 172 | + |
| 173 | + # This is the our keyboard callback/event handler function. |
| 174 | + # If the user has pressed Escape, we quit. |
| 175 | + def key_press_event(self, widget=None, event=None): |
| 176 | + keyval = event.keyval |
| 177 | + keyval_name = Gdk.keyval_name(keyval) |
| 178 | + # state = event.state |
| 179 | + # ctrl = (state & Gdk.ModifierType.CONTROL_MASK) |
| 180 | + if keyval_name == "Escape": |
| 181 | + Gtk.main_quit() |
| 182 | + return False |
| 183 | + |
| 184 | + # This is the our generic callback function |
| 185 | + def callback(self, widget=None, event=None, data=None): |
| 186 | + if (data is not None and |
| 187 | + data in self.actions and |
| 188 | + self.actions[data] is not None and |
| 189 | + event.button == 1): # left click |
| 190 | + os.system(self.actions[data]) |
| 191 | + Gtk.main_quit() |
| 192 | + |
| 193 | + |
| 194 | +if __name__ == "__main__": |
| 195 | + SystemDialog() |
| 196 | + Gtk.main() |
0 commit comments