#!/usr/bin/python # -*- coding: utf-8 -*- # Created on: 8 Oct 2014 __author__ = 'karas84' import pygtk import gtk import gobject import appindicator import sys import os import threading import time import subprocess import tempfile import PIL.Image import re import sys import Xlib import Xlib.ext from Xlib import X, display, xobject from Xlib.error import BadWindow, CatchError pygtk.require('2.0') def printf(str_format, *args): sys.stdout.write(str_format % args) sys.stdout.flush() class FilteredStdOut(object): """class description""" def __init__(self): self.wrapped_stdout = sys.stdout self.skip_next = False def __getattribute__(self,name): if name == "wrapped_stdout" or name == "write" or name == "filter" or name == "skip_next": return object.__getattribute__(self, name) else: return self.wrapped_stdout.__class__.__getattribute__(self.wrapped_stdout, name) def write(self, s): if self.skip_next: self.skip_next = False elif self.filter(s): self.wrapped_stdout.write(s) def filter(self, s): if "<class 'Xlib.protocol.request.QueryExtension'>" in s: self.skip_next = True return False else: return True sys.stdout = FilteredStdOut() class ViberIcons(object): ICON_NORMAL = """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""" ICON_NOTIF = """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 8fI/bKPVDzA4zUcAAAAASUVORK5CYII=""" INSTANCE = None def icon_exists(self, icon_name, icon_size=24): try: icon_info = self.git.lookup_icon(icon_name, icon_size, 0) return os.path.isfile(icon_info.get_filename()) except: return False def __init__(self): if self.INSTANCE is not None: raise ValueError("An instantiation already exists!") self.git = gtk.icon_theme_get_default() if self.icon_exists('viber-normal') and self.icon_exists('viber-notification'): self.icon_type = "SYSTEM" self.icon_normal = "viber-normal" self.icon_notification = "viber-notification" else: self.icon_type = "BUILTIN" self.temp_icon_normal = tempfile.NamedTemporaryFile() self.temp_icon_notif = tempfile.NamedTemporaryFile() self.temp_icon_normal.write(self.ICON_NORMAL.decode('base64')) self.temp_icon_notif.write(self.ICON_NOTIF.decode('base64')) self.temp_icon_normal.flush() self.temp_icon_notif.flush() self.icon_normal = os.path.abspath(self.temp_icon_normal.name) self.icon_notification = os.path.abspath(self.temp_icon_notif.name) printf("Using %s icons\n", self.icon_type) @classmethod def Instance(cls): if cls.INSTANCE is None: cls.INSTANCE = ViberIcons() return cls.INSTANCE def get_icons(self): return self.icon_normal, self.icon_notification def clean_icons(self): if self.icon_type == "BUILTIN": self.temp_icon_normal.close() self.temp_icon_notif.close() class XTools(object): """class description""" INSTANCE = None def __init__(self): if self.INSTANCE is not None: raise ValueError("An instantiation already exists!") self.display = display.Display() self.root = self.display.screen().root @classmethod def Instance(cls): if cls.INSTANCE is None: cls.INSTANCE = XTools() return cls.INSTANCE def get_root(self): return self.root def get_display(self): return self.display def create_window_from_id(self, window_id): return self.display.create_resource_object('window', window_id) def get_client_list(self): return self.root.get_full_property(self.display.intern_atom('_NET_CLIENT_LIST'), Xlib.X.AnyPropertyType).value def get_mouse_location(self): data = self.root.query_pointer()._data return data["root_x"], data["root_y"] @staticmethod def translate_coordinates(window, src_window, src_x, src_y): data = window.translate_coords(src_window, src_x, src_y)._data return data['x'], data['y'] def get_input_state(self): data = self.root.query_pointer()._data return data['mask'] def mousebutton(self, window, button=1, is_press=True): XButtons = { 1: X.Button1, 2: X.Button2, 3: X.Button3, 4: X.Button4, 5: X.Button5 } XButtonMasks = { 1: X.Button1MotionMask, 2: X.Button2MotionMask, 3: X.Button3MotionMask, 4: X.Button4MotionMask, 5: X.Button5MotionMask } XEvent = { True: Xlib.protocol.event.ButtonPress, False: Xlib.protocol.event.ButtonRelease } root_x, root_y = self.get_mouse_location() state = self.get_input_state() x, y = self.translate_coordinates(window, self.root, root_x, root_y) if not is_press: state |= XButtonMasks[button] XEventFunction = XEvent[is_press] mouse_event = XEventFunction(detail=XButtons[button], root=self.root, root_x=root_x, root_y=root_y, window=window, event_x=x, event_y=y, same_screen=1, state=state, time=X.CurrentTime, child=0) window.send_event(event=mouse_event, event_mask=X.ButtonPressMask, propagate=1) def mouse_up(self, window, button): self.mousebutton(window, button, is_press=False) def mouse_down(self, window, button): self.mousebutton(window, button, is_press=True) def get_window_by_class_name(self, class_name): XTools.Instance().get_display().sync() window = None for win in self.root.query_tree().children: try: window_wm_class = win.get_wm_class() if window_wm_class is not None: if class_name in window_wm_class[0] or class_name in window_wm_class[1]: window = self.display.create_resource_object('window', win.id) break except BadWindow: printf("Error getting window's WM_CLASS of window 0x%08x\n", win.id) pass return window def get_client_by_class_name(self, class_name): XTools.Instance().get_display().sync() window = None for win_id in self.get_client_list(): try: win = self.create_window_from_id(win_id) wclass = win.get_wm_class() if wclass is not None and (class_name in wclass[0] or class_name in wclass[1]): window = win break except BadWindow: printf("Error getting client's WM_CLASS of window 0x%08x\n", win_id) pass return window class XWindow(object): """class description""" class WindowIsNone(Exception): """class description""" def __init__(self): super(XWindow.WindowIsNone, self).__init__("Window is None") def __init__(self, window): if window is None: raise XWindow.WindowIsNone self.XTools = XTools.Instance() self.window = window def click(self, button=1): self.XTools.mouse_down(self.window, button) self.XTools.mouse_up(self.window, button) def double_click(self, button=1): self.click(button) self.click(button) def close(self): _NET_CLOSE_WINDOW = self.XTools.get_display().intern_atom("_NET_CLOSE_WINDOW") close_message = Xlib.protocol.event.ClientMessage(window=self.window, client_type=_NET_CLOSE_WINDOW, data=(32,[0,0,0,0,0])) mask = (X.SubstructureRedirectMask | X.SubstructureNotifyMask) self.XTools.Instance().get_root().send_event(close_message, event_mask=mask) self.XTools.get_display().flush() def hide(self): Xlib.protocol.request.UnmapWindow(display=self.XTools.get_display().display, window=self.window.id) self.XTools.get_display().sync() def show(self): Xlib.protocol.request.MapWindow(display=self.XTools.get_display().display, window=self.window.id) self.XTools.get_display().sync() def move(self, x, y): win = xobject.drawable.Window(self.XTools.get_display().display, self.window.id) w_geom = self.window.get_geometry()._data win.configure(x=x, y=y, width=w_geom['width'], height=w_geom['height']) win.change_attributes(win_gravity=X.NorthWestGravity, bit_gravity=X.StaticGravity) self.XTools.get_display().sync() def read_image(self, width, height, save_to=None): pixmp = self.window.get_image(0, 0, width, height, Xlib.X.ZPixmap, 0xFFFFFFFF) rgbim = PIL.Image.frombytes("RGB", (width, height), pixmp.data, "raw", "BGRX") if save_to is not None: rgbim.save(save_to) return rgbim def next_event(self, instance=None, atom=None): ev = None while ev is None: ev = self.window.display.next_event() if atom is not None: ev = ev if hasattr(ev, 'atom') and ev.atom == atom else None if instance is not None: ev = ev if isinstance(ev, instance) else None return ev class ViberIconPoller(threading.Thread): """class description""" def __init__(self, xviber_window): super(ViberIconPoller, self).__init__() self.viber_window = xviber_window self.setDaemon(True) def run(self): self.poll_viber_icon() def poll_viber_icon(self): vi = ViberIcons.Instance() icon_norml, icon_notif = vi.get_icons() notified = False while True: try: time.sleep(1) n = self.is_notified() except: n = False if n and not notified: notified = True # ind.set_icon(icon_notif) ind.set_status(appindicator.STATUS_ATTENTION) elif not n and notified: notified = False # ind.set_icon(icon_norml) ind.set_status(appindicator.STATUS_ACTIVE) def is_notified(self): viber_img = self.viber_window.read_image(22, 22) r, g, b = viber_img.getpixel((17, 4)) if r >= 200 and g <= 50 and b <= 50: return True return False class ViberChatWindow(XWindow): """class description""" @staticmethod def get_viber_chat(): window = None windowIDs = XTools.Instance().get_client_list() for windowID in windowIDs: window = XTools.Instance().create_window_from_id(windowID) wclass = window.get_wm_class() if wclass is None: continue if "Viber" in wclass[0] or "Viber" in wclass[1]: break return window def __init__(self): super(ViberChatWindow, self).__init__(ViberChatWindow.get_viber_chat()) class NoViberWindowFound(Exception): """class description""" def __init__(self): super(NoViberWindowFound, self).__init__("No Viber Window Found") class CompizNotFound(Exception): def __init__(self): super(CompizNotFound, self).__init__() class ViberAlreadyRunning(Exception): def __init__(self): super(ViberAlreadyRunning, self).__init__() class ViberWindow(XWindow): """class description""" @staticmethod def external_find_viber(): r = subprocess.Popen(["xwininfo", "-root", "-children"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) ou, er = map(lambda s: s.strip(), r.communicate()) if len(ou) > 0 and len(er) == 0: ou = [i for i in ou.split('\n') if "ViberPC" in i and "+0+0" in i] if len(ou) == 0: return None try: viber_window_id = int(re.match(r"^ *(0x[^ ]+) .*$", ou[0]).groups()[0], base=16) except: return None return XTools.Instance().create_window_from_id(viber_window_id) else: return None def find_viber(self): while self.viber_window is None: if self.w_compiz.next_event(): self.viber_window = XTools.Instance().get_window_by_class_name('ViberPC') @staticmethod def get_viber_window(): children = XTools.Instance().get_root().query_tree().children found_viber_window = None for window in children: try: w_class = window.get_wm_class() if w_class is not None: if "viber" in w_class[0].lower() or "viber" in w_class[1].lower(): geom = window.get_geometry()._data if geom['x'] == 0 and geom['y'] == 0 and geom['width'] <= 64 and geom['height'] <= 64: found_viber_window = window except: pass return found_viber_window def poll_viber_window(self, external=False): if external: printf(" (EXTERNAL)\n") finder_fn = ViberWindow.external_find_viber else: printf(" (INTERNAL)\n") finder_fn = ViberWindow.get_viber_window found_viber_window = finder_fn() poll_second_count = 10 while poll_second_count > 0: if found_viber_window is not None: break time.sleep(1) poll_second_count -= 1 found_viber_window = finder_fn() self.viber_window = found_viber_window def __init__(self, close_chat=False, use_old=False, use_external=False): self.viber_window = None self.viber_launcher = ViberLauncher() try: if use_old: raise CompizNotFound() try: self.w_compiz = XWindow(XTools.Instance().get_window_by_class_name('compiz')) except XWindow.WindowIsNone: raise CompizNotFound() printf("Using NEW detection method\n") XTools.Instance().get_root().change_attributes(event_mask=X.SubstructureNotifyMask) self.w_compiz.window.change_attributes(event_mask=X.SubstructureNotifyMask) self.thread = threading.Thread(target=self.find_viber) except CompizNotFound: printf("Using OLD detection method") self.thread = threading.Thread(target=self.poll_viber_window, kwargs={"external": use_old and use_external}) self.thread.setDaemon(True) self.thread.start() self.viber_launcher.start() self.thread.join() super(ViberWindow, self).__init__(self.viber_window) if self.window is None: raise NoViberWindowFound self.move(-128, -128) printf("Viber Found") if close_chat: self.chat_window = ViberChatWindow() self.chat_window.close() def open(self, widget, data=None): self.double_click(button=1) def quit(self, widget, data=None): os.system('pkill -9 Viber') class ProcessFinder(object): """class description""" def __init__(self, process_path): self.process_path = process_path.strip().replace('\t', ' ') self.re = re.compile(r".* " + re.escape(self.process_path) + r"$") self._found = None def _find_process(self): self._found = False ret = subprocess.Popen(['ps', '-aux'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) out_stream, err_stream = ret.communicate() for line in out_stream.split('\n'): if self.re.match(line): self._found = True def find(self): finder_t = threading.Thread(target=self._find_process) finder_t.setDaemon(True) finder_t.start() finder_t.join() process_found = self._found self._found = None return process_found class ViberLauncher(threading.Thread): """class description""" def __init__(self, viber_path="/opt/viber/Viber"): super(ViberLauncher, self).__init__() self.viber_path = viber_path self.setDaemon(True) def start(self): viber_finder = ProcessFinder(self.viber_path) if viber_finder.find(): raise ViberAlreadyRunning() else: super(ViberLauncher, self).start() def run(self): try: printf("Launching Viber (%s) ... ", self.viber_path) p_viber = subprocess.Popen([self.viber_path], stdout=subprocess.PIPE, stderr=subprocess.PIPE) printf("OK\n") p_viber.wait() printf("Viber process terminated. Quitting...\n") except OSError as ose: printf("Error starting Viber. Operating System reported: '%s'\n", ose.strerror) os.system('pkill -9 Viber') ViberIcons.Instance().clean_icons() os._exit(-1) except Exception as ex: printf("Error starting Viber because of exception '%s: %s'\n", ex.__class__.__name__, ex.args[0]) os.system('pkill -9 Viber') ViberIcons.Instance().clean_icons() os._exit(-1) try: gtk.main_quit() except: pass if __name__ == "__main__": viber_icons = ViberIcons.Instance() icon_normal, icon_notification = viber_icons.get_icons() try: arg_close_chat = "--close-chat" in sys.argv arg_use_old = "--use-old-detection-method" in sys.argv arg_use_ext = "--use-external-detector" in sys.argv viber_window = ViberWindow(close_chat=arg_close_chat, use_old=arg_use_old, use_external=arg_use_ext) ind = appindicator.Indicator("Viber Indicator", "", appindicator.CATEGORY_APPLICATION_STATUS) ind.set_status(appindicator.STATUS_ACTIVE) ind.set_icon(icon_normal) ind.set_attention_icon(icon_notification) menu = gtk.Menu() item_open = gtk.MenuItem("Open Viber") item_open.connect("activate", viber_window.open) item_open.show() menu.append(item_open) sep = gtk.SeparatorMenuItem() sep.show() menu.append(sep) item_exit = gtk.MenuItem("Exit") item_exit.connect("activate", viber_window.quit) item_exit.show() menu.append(item_exit) menu.show() ind.set_menu(menu) t_vipoller = ViberIconPoller(viber_window) t_vipoller.setDaemon(True) t_vipoller.start() gobject.threads_init() gtk.main() except ViberAlreadyRunning: sys.stdout.write("Viber Already Running!\n") sys.stdout.flush() except Exception as e: printf("Exiting because of exception '%s: %s'\n", e.__class__.__name__, e.args[0]) os.system('pkill -9 Viber') viber_icons.clean_icons() os._exit(-1) finally: viber_icons.clean_icons() os._exit(0)