1# Copyright (c) 2017-2021 Cedric Bellegarde <cedric.bellegarde@adishatz.org> 2# This program is free software: you can redistribute it and/or modify 3# it under the terms of the GNU General Public License as published by 4# the Free Software Foundation, either version 3 of the License, or 5# (at your option) any later version. 6# This program is distributed in the hope that it will be useful, 7# but WITHOUT ANY WARRANTY; without even the implied warranty of 8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 9# GNU General Public License for more details. 10# You should have received a copy of the GNU General Public License 11# along with this program. If not, see <http://www.gnu.org/licenses/>. 12 13from gi.repository import Gdk, GLib, Gtk, Pango, GdkPixbuf 14 15from math import pi 16import unicodedata 17import string 18import cairo 19from threading import current_thread 20from urllib.parse import urlparse 21from random import choice 22from base64 import b64encode 23 24from eolie.logger import Logger 25from eolie.define import ArtSize, LoadingType 26 27 28def get_char_surface(char): 29 """ 30 Draw a char with a random color 31 @param char as str 32 @return cairo surface 33 """ 34 colors = [[0.102, 0.737, 0.612], # Turquoise 35 [0.204, 0.596, 0.859], # Peterriver 36 [0.608, 0.349, 0.714], # Amethyst 37 [0.204, 0.286, 0.369], # Wetasphalt 38 [0.086, 0.627, 0.522], # Greensea 39 [0.153, 0.682, 0.376], # Nephritis 40 [0.161, 0.502, 0.725], # Belizehole 41 [0.557, 0.267, 0.678], # Wisteria 42 [0.173, 0.243, 0.314], # Midnightblue 43 [0.827, 0.329, 0.0], # Pumpkin 44 [0.753, 0.224, 0.169], # Pomegranate 45 [0.498, 0.549, 0.553] # Asbestos 46 ] 47 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 48 ArtSize.FAVICON, 49 ArtSize.FAVICON) 50 context = cairo.Context(surface) 51 try: 52 index = string.ascii_lowercase.index(char) 53 except: 54 index = 0 55 colors_count = len(colors) 56 while int(index) >= colors_count: 57 index /= 2 58 color = colors[int(index)] 59 context.set_source_rgb(color[0], color[1], color[2]) 60 context.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, 61 cairo.FONT_WEIGHT_BOLD) 62 context.set_font_size(ArtSize.FAVICON) 63 (xbearing, ybearing, 64 width, height, 65 xadvance, yadvance) = context.text_extents(char) 66 context.move_to(ArtSize.FAVICON / 2 - (xadvance + xbearing) / 2, 67 ArtSize.FAVICON / 2 - ybearing - height / 2) 68 context.show_text(char) 69 context.stroke() 70 return surface 71 72 73def get_baseuri(uri): 74 """ 75 Get Base URI 76 @param uri as str 77 @return str 78 """ 79 parsed = urlparse(uri) 80 return "%s://%s" % (parsed.scheme, parsed.netloc) 81 82 83def get_safe_netloc(uri): 84 """ 85 Get netloc (scheme if empty) 86 @param uri as str 87 """ 88 parsed = urlparse(uri) 89 netloc = parsed.netloc 90 if not netloc: 91 netloc = "%s://" % urlparse(uri).scheme 92 else: 93 split = netloc.split(".") 94 if len(split) > 2: 95 netloc = ".".join(split[-2:]) 96 return netloc 97 98 99def wanted_loading_type(index): 100 """ 101 Return window type based on current index 102 """ 103 if index == 0: 104 return LoadingType.FOREGROUND 105 elif index == 1: 106 return LoadingType.BACKGROUND 107 else: 108 return LoadingType.OFFLOAD 109 110 111def emit_signal(obj, signal, *args): 112 """ 113 Emit signal 114 @param obj as GObject.Object 115 @param signal as str 116 @thread safe 117 """ 118 if current_thread().getName() == "MainThread": 119 obj.emit(signal, *args) 120 else: 121 GLib.idle_add(obj.emit, signal, *args) 122 123 124def get_round_surface(image, scale_factor, radius): 125 """ 126 Get rounded surface from pixbuf 127 @param image as GdkPixbuf.Pixbuf/cairo.Surface 128 @return surface as cairo.Surface 129 @param scale_factor as int 130 @param radius as int 131 @warning not thread safe! 132 """ 133 width = image.get_width() 134 height = image.get_height() 135 is_pixbuf = isinstance(image, GdkPixbuf.Pixbuf) 136 if is_pixbuf: 137 width = width // scale_factor 138 height = height // scale_factor 139 radius = radius // scale_factor 140 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, width, height) 141 ctx = cairo.Context(surface) 142 degrees = pi / 180 143 ctx.arc(width - radius, radius, radius, -90 * degrees, 0 * degrees) 144 ctx.arc(width - radius, height - radius, 145 radius, 0 * degrees, 90 * degrees) 146 ctx.arc(radius, height - radius, radius, 90 * degrees, 180 * degrees) 147 ctx.arc(radius, radius, radius, 180 * degrees, 270 * degrees) 148 ctx.close_path() 149 ctx.set_line_width(10) 150 if is_pixbuf: 151 image = Gdk.cairo_surface_create_from_pixbuf(image, scale_factor, None) 152 ctx.set_source_surface(image, 0, 0) 153 ctx.clip() 154 ctx.paint() 155 return surface 156 157 158def is_unity(): 159 """ 160 Return True if desktop is Gnome 161 """ 162 return GLib.getenv("XDG_CURRENT_DESKTOP") == "ubuntu:GNOME" 163 164 165def on_query_tooltip(label, x, y, keyboard, tooltip): 166 """ 167 Show label tooltip if needed 168 @param label as Gtk.Label 169 @param x as int 170 @param y as int 171 @param keyboard as bool 172 @param tooltip as Gtk.Tooltip 173 """ 174 layout = label.get_layout() 175 if layout.is_ellipsized(): 176 tooltip.set_markup(label.get_label()) 177 return True 178 179 180def resize_favicon(favicon): 181 """ 182 Resize surface to match favicon size 183 @param favicon as cairo.surface 184 @return cairo.surface 185 """ 186 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 187 ArtSize.FAVICON, 188 ArtSize.FAVICON) 189 factor = ArtSize.FAVICON / favicon.get_width() 190 context = cairo.Context(surface) 191 context.scale(factor, factor) 192 context.set_source_surface(favicon, 0, 0) 193 context.paint() 194 return surface 195 196 197# TODO Use Lollypop menu builder 198def update_popover_internals(widget): 199 """ 200 Little hack to force Gtk.ModelButton to show image 201 @param widget as Gtk.Widget 202 """ 203 if isinstance(widget, Gtk.Image): 204 widget.show() 205 elif isinstance(widget, Gtk.Label): 206 widget.set_ellipsize(Pango.EllipsizeMode.END) 207 widget.set_max_width_chars(40) 208 widget.set_tooltip_text(widget.get_text()) 209 elif hasattr(widget, "forall"): 210 GLib.idle_add(widget.forall, update_popover_internals) 211 212 213def get_snapshot(webview, result, callback, *args): 214 """ 215 Set snapshot on main image 216 @param webview as WebKit2.WebView 217 @param result as Gio.AsyncResult 218 @return cairo.Surface 219 """ 220 ART_RATIO = 1.5 # ArtSize.START_WIDTH / ArtSize.START_HEIGHT 221 try: 222 snapshot = webview.get_snapshot_finish(result) 223 # Set start image scale factor 224 ratio = snapshot.get_width() / snapshot.get_height() 225 if ratio > ART_RATIO: 226 factor = ArtSize.START_HEIGHT / snapshot.get_height() 227 else: 228 factor = ArtSize.START_WIDTH / snapshot.get_width() 229 surface = cairo.ImageSurface(cairo.FORMAT_ARGB32, 230 ArtSize.START_WIDTH, 231 ArtSize.START_HEIGHT) 232 context = cairo.Context(surface) 233 context.scale(factor, factor) 234 context.set_source_surface(snapshot, factor, 0) 235 context.paint() 236 callback(surface, *args) 237 except Exception as e: 238 Logger.error("get_snapshot(): %s", e) 239 callback(None, *args) 240 241 242def get_random_string(size): 243 """ 244 Get a rand string at size 245 @param size as int 246 return str 247 """ 248 s = ''.join(choice(string.printable) for c in range(size)) 249 return b64encode(s.encode("utf-8"))[:size].decode("utf-8") 250 251 252def get_current_monitor_model(window): 253 """ 254 Return monitor model as string 255 @param window as Gtk.Window 256 @return str 257 """ 258 screen = Gdk.Screen.get_default() 259 display = screen.get_display() 260 monitor = display.get_monitor_at_window(window.get_window()) 261 width_mm = monitor.get_width_mm() 262 height_mm = monitor.get_height_mm() 263 geometry = monitor.get_geometry() 264 return "%sx%s/%sx%s" % (width_mm, height_mm, 265 geometry.width, geometry.height) 266 267 268def noaccents(string): 269 """ 270 Return string without accents 271 @param string as str 272 @return str 273 """ 274 nfkd_form = unicodedata.normalize('NFKD', string) 275 return u"".join([c for c in nfkd_form if not unicodedata.combining(c)]) 276 277 278def get_ftp_cmd(): 279 """ 280 Try to guess best ftp app 281 @return app cmd as str 282 """ 283 for app in ["filezilla", "nautilus", "thunar", "nemo", "true"]: 284 cmd = GLib.find_program_in_path(app) 285 if cmd is not None: 286 return cmd 287