def destroy(self): InfiniteGlass.DEBUG( "destroy", "Window destroyed %s" % self.window.get("WM_NAME", self.window)) InfiniteGlass.DEBUG("destroy", " key=%s" % self.key()) InfiniteGlass.DEBUG( "destroy", " %s" % ("IGNORED" if self.is_ignored() else "NOT IGNORED")) InfiniteGlass.DEBUG( "destroy", " %s" % ("has ghost" if self.ghost else "no ghost")) if not self.is_ignored(): if not self.ghost: self.ghost = glass_ghosts.ghost.Shadow(self.manager, self.properties) else: self.ghost.properties.update(self.properties) self.ghost.update_key() self.ghost.activate() else: if self.ghost: self.ghost.destroy() self.manager.windows.pop(self.id, None) self.manager.display.eventhandlers.remove(self.PropertyNotify) self.manager.display.eventhandlers.remove(self.DeleteMessage) self.manager.display.eventhandlers.remove(self.CloseMessage) self.manager.display.eventhandlers.remove(self.SleepMessage) self.manager.display.eventhandlers.remove(self.UnmapNotify) self.manager.display.eventhandlers.remove(self.ConfigureNotify) if self.client: self.client.remove_window(self)
def animationfn(): while True: tick[0] += 1 current = time.time() progress = (current - start) / timeframe if progress > 1.: window[atom] = dst InfiniteGlass.DEBUG( "final", "SET FINAL %s.%s=%s\n" % (window.__window__(), atom, dst)) else: pos = easing(progress) res = [ pos * dstval + (1. - pos) * srcval for srcval, dstval in values ] if isint: res = [int(item) for item in res] window[atom] = res InfiniteGlass.DEBUG( "transition", "SET %s.%s=%s\n" % (window.__window__(), atom, [ progress * dstval + (1. - progress) * srcval for srcval, dstval in values ])) display.flush() display.sync() if progress > 1.: return yield
def animationfn(): while True: tick[0] += 1 current = time.time() progress = (current - start) / timeframe if progress > 1.: current_values = dst InfiniteGlass.DEBUG( "final", "SET FINAL [%s]=%s\n" % (window.__window__(), dst)) else: pos = easing(progress) current_values = [ int(pos * dstval + (1. - pos) * srcval) for srcval, dstval in values ] if tick[0] % 100 == 0: InfiniteGlass.DEBUG( "transition", "SET [%s]=%s\n" % (window.__window__(), [ progress * dstval + (1. - progress) * srcval for srcval, dstval in values ])) window.configure(x=current_values[0], y=current_values[1], width=current_values[2], height=current_values[3]) display.flush() display.sync() if progress > 1.: return yield
def destroy(self): InfiniteGlass.DEBUG("destroy", "ISLAND DESTROY %s\n" % (self, )) sys.stderr.flush() try: if self.window is not None: self.window.destroy() self.manager.display.eventhandlers.remove(self.RestartMessage) self.manager.display.eventhandlers.remove(self.DestroyNotify) self.manager.display.eventhandlers.remove(self.PropertyNotify) self.manager.display.eventhandlers.remove(self.WMDelete) self.manager.display.eventhandlers.remove(self.CloseMessage) self.manager.display.eventhandlers.remove(self.Expose) self.window = None except Exception as e: InfiniteGlass.DEBUG("destroy", "Error closing window: %s" % e) self.manager.islands.pop(self.key, None) cur = self.manager.dbconn.cursor() cur.execute( """ delete from islands where key = ? """, (self.key, )) self.manager.dbconn.commit()
def zoom_to_window_to_the(self, event, direction): print("ZOOM OUT TO WINDOW TO %s" % (direction)) view = list(self.display.root["IG_VIEW_DESKTOP_VIEW"]) vx = view[0] + view[2] / 2. vy = view[1] + view[3] / 2. def angular_diff(a, b): diff = (a - b) % (2 * numpy.pi) if diff <= numpy.pi: return diff return 2 * numpy.pi - diff windows = [] visible, overlap, invisible = InfiniteGlass.windows.get_windows( self.display, view) for child, coords in invisible + overlap: if child.get("IG_LAYER", "IG_LAYER_DESKTOP") != "IG_LAYER_DESKTOP": continue x = coords[0] + coords[2] / 2. y = coords[1] - coords[3] / 2. wdist = numpy.sqrt((x - vx)**2 + (y - vy)**2) wdir = angular_diff(numpy.arctan2(y - vy, x - vx), direction) / (numpy.pi / 4) if wdir < 1.0: windows.append((wdist * (1 + wdir), (x, y), coords, child)) if not windows: screeny = numpy.sin(direction) screenx = numpy.cos(direction) view = [view[0] + screenx * view[2], view[1] + screeny * view[3] ] + view[2:] else: windows.sort(key=lambda a: a[0]) dist, center, coords, next_window = windows[0] InfiniteGlass.DEBUG( "visible", "Visible: %s\n" % (",".join("%s/%s[%s] @ %s" % (v.get("WM_NAME", None), v.get("WM_CLASS", None), v.__window__(), c) for v, c in visible), )) InfiniteGlass.DEBUG( "next", "Next window %s/%s[%s] @ %s\n" % (next_window.get("WM_NAME", None), next_window.get( "WM_CLASS", None), next_window.__window__(), coords)) view = zoom_to_window_calc(self, view, next_window, view_center=(vx, vy), coords=coords, center=center) InfiniteGlass.DEBUG("view", "View %s\n" % (view, )) self.display.root["IG_VIEW_DESKTOP_VIEW_ANIMATE"] = view self.display.animate_window.send(self.display.animate_window, "IG_ANIMATE", self.display.root, "IG_VIEW_DESKTOP_VIEW", .5)
def set(ctx, window, name, value): """Name is the property atom name as a string. Value is the property value as a JSON value, parsed using InfiniteGlass.fromjson (so any __jsonclass__ values supported by it are supported). In particular, strings are interpreted as atom names, actual string values need to use the __jsonclass__ encoding. Usage examples: \b glass-action window set --window 1234567 IG_COORDS "[1.0, 1.0, 1.0, 1.0]" """ with InfiniteGlass.Display() as display: @glass_action.window_tools.str_to_win(display, window) def set(win): v = value try: v = json.loads(v) except: pass properties = json.loads( json.dumps({name: v}), object_hook=InfiniteGlass.fromjson(display)) for k, v in properties.items(): win[k] = v display.flush() sys.exit(0)
def update_key(self): key = self.key() if key != self.current_key and key in self.manager.ghosts: InfiniteGlass.DEBUG("ghost", "DUPLICATE SHADOW %s\n" % (self, )) self.destroy() if not self.manager.restoring_ghosts and not self.from_config: cur = self.manager.dbconn.cursor() for name, value in self.properties.items(): if self._properties.get(name, NoValue) != value: cur.execute( """ insert or replace into ghosts (key, name, value) VALUES (?, ?, ?) """, (key, name, json.dumps(value, default=InfiniteGlass.tojson( self.manager.display)))) self.manager.changes = True for name, value in self._properties.items(): if name not in self.properties: cur.execute( """ delete from ghosts where key=? and name=? """, (key, name)) self.manager.changes = True if key != self.current_key and self.current_key is not None: try: cur.execute( """ update ghosts set key=? where key=? """, (key, self.current_key)) except Exception as e: InfiniteGlass.DEBUG( "ghost.database", "Error updating key in db: %s\nkey=%s => key=%s\n" % (e, self.current_key, key)) self.destroy() self.manager.changes = True if key == self.current_key: return client = self.manager.clients.get(self.properties.get("SM_CLIENT_ID")) if self.current_key is not None: del self.manager.ghosts[self.current_key] if client: del client.ghosts[self.current_key] InfiniteGlass.DEBUG( "ghost", "UPDATE KEY from %s to %s\n" % (self.current_key, key)) sys.stderr.flush() self.current_key = key self.manager.ghosts[self.current_key] = self if client: client.ghosts[self.current_key] = self
def zoom_1_1_1(self, event): "Zoom to make the current window full-screen, and change its resolution to the same as the screen" win = self.get_event_window(event) if not win or win == self.display.root: return InfiniteGlass.DEBUG("zoom", "zoom_screen_to_window_and_window_to_screen\n") size = self.display.root["IG_VIEW_DESKTOP_SIZE"] coords = list(win["IG_COORDS"]) screen = list(self.display.root["IG_VIEW_DESKTOP_VIEW"]) coords[3] = (size[1] * coords[2]) / size[0] screen[2] = coords[2] screen[3] = coords[3] screen[0] = coords[0] screen[1] = coords[1] - screen[3] InfiniteGlass.DEBUG("zoom", " screen=%s geom=%s\n" % (screen, size)) win["IG_COORDS_ANIMATE"] = coords win["IG_SIZE_ANIMATE"] = size self.display.root["IG_VIEW_DESKTOP_VIEW_ANIMATE"] = screen self.display.animate_window.send(self.display.animate_window, "IG_ANIMATE", win, "IG_COORDS", .5) self.display.animate_window.send(self.display.animate_window, "IG_ANIMATE", win, "IG_SIZE", .5) self.display.animate_window.send(self.display.animate_window, "IG_ANIMATE", self.display.root, "IG_VIEW_DESKTOP_VIEW", .5) self.display.flush() self.display.sync()
def main(): manager = None try: with InfiniteGlass.Display() as display: overlay = display.root.composite_get_overlay_window().overlay_window overlay_geom = overlay.get_geometry() gc = overlay.create_gc( foreground = display.screen().black_pixel, background = display.screen().white_pixel) overlay.rectangle(gc, 0, 0, overlay_geom.width, overlay_geom.height, onerror = None) manager = glass_ghosts.manager.GhostManager(display) sys.stdout.write("%s\n" % manager.session.listen_address()) sys.stdout.flush() InfiniteGlass.DEBUG("init", "Session manager listening to %s\n" % manager.session.listen_address()) manager.components.shutdown() except Exception as e: print("Ghost manager systemic failure, restarting: %s" % (e,)) traceback.print_exc() try: if manager is not None and hasattr(manager, "components") and hasattr(manager.components, "components_by_pid"): for pid in manager.components.components_by_pid.keys(): os.kill(pid, signal.SIGINT) except Exception as e: print(e) traceback.print_exc() os.execlp(sys.argv[0], *sys.argv) print("END")
def adjust_view(self, view, win=None): visible, overlap, invisible = InfiniteGlass.windows.get_windows( self.display, view) visible_pixelcontent = [(w, c) for w, c in visible if "IG_CONTENT" not in w] if win is None: for win in visible: if "IG_CONTENT" not in win: break else: InfiniteGlass.DEBUG( "zoom", "No focus window, and no visible windows without IG_CONTENT to focus instead\n" ) return view if "IG_CONTENT" in win: return view zoomed_view = item_zoom_1_1_to_window_calc(self, win=win, screen=view) if len(visible_pixelcontent) > 1: bbox = utils.bbox([c for w, c in visible]) InfiniteGlass.DEBUG("zoom", "Visible windows bbox: %s\n" % (bbox, )) if zoomed_view[2] >= bbox[2] and zoomed_view[3] >= bbox[3]: InfiniteGlass.DEBUG( "zoom", "Zoom 1-to-1 to %s @ %s (View still contains all visible windows)\n" % (win, win.get("IG_COORDS"))) view = zoomed_view view[0] = bbox[0] - ((view[2] - bbox[2]) / 2) view[1] = bbox[1] - bbox[3] - ((view[3] - bbox[3]) / 2) else: InfiniteGlass.DEBUG( "zoom", "Zoom 1-to-1 to %s @ %s (Less than 2 visible windows)\n" % (win, win.get("IG_COORDS"))) return zoomed_view InfiniteGlass.DEBUG("zoom", "Zoom-adjusted view: %s\n" % (view, )) # Move the view as long as that makes more windows visible without removing any windows while True: view[0] = bbox[0] - ((view[2] - bbox[2]) / 2) view[1] = bbox[1] - bbox[3] - ((view[3] - bbox[3]) / 2) new_visible, new_overlap, new_invisible = InfiniteGlass.windows.get_windows( self.display, view) visible_set = set(w.__window__() for w, c in visible) new_visible_set = set(w.__window__() for w, c in new_visible) if len(visible_set - new_visible_set) != 0 or len(new_visible_set - visible_set) == 0: return view visible = new_visible bbox = utils.bbox([c for w, c in visible])
def handle_event(display, event): InfiniteGlass.DEBUG("event", "HANDLE %s\n" % event) mode = display.input_stack[-1] if mode.handle(event): InfiniteGlass.DEBUG("event", " BY %s\n" % (mode, )) return True InfiniteGlass.DEBUG("event", " UNHANDLED\n") return False
def send_sleep(self, event): "Make the active application store its state and exit" win = self.get_event_window(event) InfiniteGlass.DEBUG("sleep", "Sleep %s %s\n" % (win, win.get("WM_NAME", None))) if win and win != self.display.root: InfiniteGlass.DEBUG("sleep", "SENDING SLEEP %s\n" % win) win.send(win, "IG_SLEEP", event_mask=Xlib.X.StructureNotifyMask) self.display.flush()
def send_close(self, event): "Close the active window" win = self.get_event_window(event) InfiniteGlass.DEBUG("close", "Close %s %s\n" % (win, win.get("WM_NAME", None))) if win and win != self.display.root: InfiniteGlass.DEBUG("close", "SENDING CLOSE %s\n" % win) win.send(win, "IG_CLOSE", event_mask=Xlib.X.StructureNotifyMask) self.display.flush()
def main2(*arg, **kw): mode.load_config() with InfiniteGlass.Display() as display: # extension_info = display.query_extension('XInputExtension') # xinput_major = extension_info.major_opcode version_info = display.xinput_query_version() print('Found XInput version %u.%u' % ( version_info.major_version, version_info.minor_version, )) font = display.open_font('cursor') display.input_cursor = font.create_glyph_cursor( font, Xlib.Xcursorfont.box_spiral, Xlib.Xcursorfont.box_spiral + 1, (65535, 65535, 65535), (0, 0, 0)) display.animate_window = -1 @display.root.require("IG_ANIMATE") def animate_window(root, win): display.animate_window = win # Do not allow setting the input focus to None as that makes our keygrabs break... @display.root.on() def FocusIn(win, event): if event.detail == Xlib.X.NotifyDetailNone: display.root.set_input_focus(Xlib.X.RevertToNone, Xlib.X.CurrentTime) @display.root.on(mask="SubstructureNotifyMask", client_type="IG_INPUT_ACTION") def ClientMessage(win, event): win, atom = event.parse("WINDOW", "ATOM") action = json.loads(win[atom]) InfiniteGlass.DEBUG("message", "RECEIVED INPUT ACTION %s" % (action, )) sys.stderr.flush() event.window = win display.input_stack[-1].last_event = event display.input_stack[-1].action(None, action, event) @display.eventhandlers.append def handle(event): if display.animate_window == -1: return False return mode.handle_event(display, event) mode.push_by_name(display, "base_mode") display.root.xinput_select_events([ (Xlib.ext.xinput.AllDevices, Xlib.ext.xinput.RawMotionMask), ]) InfiniteGlass.DEBUG("init", "Input handler started\n")
def expression(ctx, value): with InfiniteGlass.Display() as display: try: value = json.loads(value, object_hook=InfiniteGlass.fromjson(display)) except: pass itemtype, items, fmt = InfiniteGlass.parse_value(display, value) print("%s(%s): %s" % (itemtype, fmt, items)) sys.exit(0)
def toggle_sleep(self, event): win = self.get_event_window(event) if win and win != self.display.root: if win.get("IG_GHOST", None): InfiniteGlass.DEBUG("restart", "SENDING RESTART %s\n" % win) win.send(win, "IG_RESTART", event_mask=Xlib.X.StructureNotifyMask) self.display.flush() else: InfiniteGlass.DEBUG("sleep", "SENDING SLEEP %s\n" % win) win.send(win, "IG_SLEEP", event_mask=Xlib.X.StructureNotifyMask) self.display.flush()
def ClientMessage(win, event): InfiniteGlass.DEBUG("ghost", "GHOST WM_DELETE_WINDOW %s\n" % (self, )) sys.stderr.flush() if event.parse("ATOM")[0] == "WM_DELETE_WINDOW": win.destroy() else: InfiniteGlass.DEBUG( "ghost", "%s: Unknown WM_PROTOCOLS message: %s\n" % (self, event)) sys.stderr.flush()
def PropertyNotify(win, event): name = self.manager.display.get_atom_name(event.atom) try: self.properties[name] = win[name] InfiniteGlass.DEBUG("island.property", "%s=%s\n" % (name, self.properties[name])) sys.stderr.flush() except: pass else: self.save_changes() InfiniteGlass.DEBUG("setprop", "%s=%s" % (name, self.properties.get(name)))
def apply(self, window, type="set"): InfiniteGlass.DEBUG( "ghost", "SHADOW APPLY window_id=%s %s\n" % (window.__window__(), self)) sys.stderr.flush() if self.properties.get("IG_GHOSTS_DISABLED", 0): window["IG_GHOSTS_DISABLED"] = 1 else: for key in self.manager.config[type]: if key in self.properties: InfiniteGlass.DEBUG( "ghost.properties", "%s=%s\n" % (key, str(self.properties[key])[:100])) sys.stderr.flush() window[key] = self.properties[key]
def PropertyNotify(win, event): name = self.manager.display.get_atom_name(event.atom) if name not in self.manager.config["ghost_update"]: return try: self.properties.update( glass_ghosts.helpers.expand_property(win, name)) InfiniteGlass.DEBUG("ghost.property", "%s=%s\n" % (name, self.properties[name])) sys.stderr.flush() except: pass else: self.update_key() InfiniteGlass.DEBUG("setprop", "%s=%s" % (name, self.properties.get(name)))
def zoom_to_fewer_windows(self, event, margin=0.01): "Zoom in the screen so that one fewer window is visible" print("ZOOM IN TO FEWER WINDOWS") view = list(self.display.root["IG_VIEW_DESKTOP_VIEW"]) vx = view[0] + view[2] / 2. vy = view[1] + view[3] / 2. windows = [] visible, overlap, invisible = InfiniteGlass.windows.get_windows(self.display, view) for child, coords in visible: d = max( math.sqrt((coords[0] - vx)**2 + (coords[1] - vy)**2), math.sqrt((coords[0] + coords[2] - vx)**2 + (coords[1] - vy)**2), math.sqrt((coords[0] - vx)**2 + (coords[1] - coords[3] - vy)**2), math.sqrt((coords[0] + coords[2] - vx)**2 + (coords[1] - coords[3] - vy)**2),) windows.append((d, coords, child)) if len(windows) <= 1: return zoom.zoom_in(self, event) windows.sort(key=lambda a: a[0]) ratio = view[2] / view[3] def get_view(removed=1): xs = [x for d, window, w in windows[:-removed] for x in (window[0], window[0] + window[2])] ys = [y for d, window, w in windows[:-removed] for y in (window[1], window[1] - window[3])] view = [min(xs), min(ys), max(xs) - min(xs), max(ys) - min(ys)] if view[2] / ratio > view[3]: view[3] = view[2] / ratio else: view[2] = ratio * view[3] return view for i in range(1, len(windows)): new_view = get_view(i) if (new_view[2] * (1 + margin) < view[2]) or (new_view[3] * (1 + margin) < view[3]): adjusted_view = item_zoom_to.adjust_view(self, new_view, windows[-i-1][2]) if adjusted_view[2] < new_view[2] and adjusted_view[3] < new_view[3]: new_view = adjusted_view print("Removed %s windows to reduce width by %s and height by %s" % (i, view[2] - new_view[2], view[3] - new_view[3])) InfiniteGlass.DEBUG("view", "View %s\n" % (new_view,)) # self.display.root["IG_VIEW_DESKTOP_VIEW"] = new_view self.display.root["IG_VIEW_DESKTOP_VIEW_ANIMATE"] = new_view self.display.animate_window.send(self.display.animate_window, "IG_ANIMATE", self.display.root, "IG_VIEW_DESKTOP_VIEW", .5) return InfiniteGlass.DEBUG("view", "Windows are all overlapping... Not sure what to do...\n")
def stop(ctx, name): with InfiniteGlass.Display() as display: key = "IG_COMPONENT_%s" % name spec = json.loads(display.root[key].decode("utf-8")) spec["run"] = False display.root[key] = json.dumps(spec).encode("utf-8") display.flush()
def process(fd): InfiniteGlass.DEBUG("session", "PROCESS %s %s\n" % (fd, conn)) try: conn.IceProcessMessages() except Exception as e: print(e) self.display.mainloop.remove(conn.IceConnectionNumber())
def close_connection(self, *arg, **kw): InfiniteGlass.DEBUG("session", "close_connection %s %s\n" % (arg, kw)) sys.stderr.flush() self.manager.display.mainloop.remove(self.fd) if self.client: self.client.remove_connection(self)
def island_toggle_sleep(self, event): island = self.get_event_window(event) if island and island != self.display.root: old = island.get("IG_ISLAND_PAUSED", 0) if old == 1: is_paused = 0 else: is_paused = 1 InfiniteGlass.DEBUG("island_toggle_sleep", "%s.IG_ISLAND_PAUSED=%s\n" % (island, is_paused)) island["IG_ISLAND_PAUSED"] = is_paused windows = [win for win, coords in island_windows(self.display, island)] clients_done = set() if is_paused: for win in windows: if "IG_GHOST" not in win: if "SM_CLIENT_ID" not in win or win["SM_CLIENT_ID"] not in clients_done: win.send(win, "IG_SLEEP", event_mask=Xlib.X.StructureNotifyMask) if "SM_CLIENT_ID" in win: clients_done.add(win["SM_CLIENT_ID"]) else: for win in windows: if "IG_GHOST" in win and "SM_CLIENT_ID" in win and win["SM_CLIENT_ID"] not in clients_done: win.send(win, "IG_RESTART", event_mask=Xlib.X.StructureNotifyMask) clients_done.add(win["SM_CLIENT_ID"]) self.display.flush()
def ClientMessage(win, event): InfiniteGlass.DEBUG( "message", "RECEIVED DELETE %s %s %s" % (win, event, self.client)) sys.stderr.flush() self.under_deletion = True self.close()
def set_background(ctx, window, background): with InfiniteGlass.Display() as display: @glass_action.window_tools.str_to_win(display, window) def set_background(window): if background: background_path = background[0] else: import gi gi.require_version('Gtk', '3.0') from gi.repository import Gtk as gtk dlg = gtk.FileChooserDialog( title="Change background (SVG files only)", action=gtk.FileChooserAction.OPEN, buttons=(gtk.STOCK_CANCEL, gtk.ResponseType.CANCEL, gtk.STOCK_OPEN, gtk.ResponseType.OK)) filter = gtk.FileFilter() filter.add_pattern("*.svg") dlg.set_filter(filter) response = dlg.run() background_path = dlg.get_filename() dlg.destroy() if response != gtk.ResponseType.OK: sys.exit(0) return window["IG_CONTENT"] = ("IG_SVG", "file://" + background_path) display.flush() sys.exit(0)
def main(*arg, **kw): with InfiniteGlass.Display() as display: configpath = os.path.expanduser( os.environ.get("GLASS_THEME_CONFIG", "~/.config/glass/theme.json")) with open(configpath) as f: config = yaml.load(f, Loader=yaml.SafeLoader) module_name, cls_name = config["name"].rsplit(".", 1) module = importlib.import_module(module_name) importlib.reload(module) cls = getattr(module, cls_name) cls(display, **config.get("args", {})) display.flush() InfiniteGlass.DEBUG("init", "Theme started: %s\n" % config["name"])
def start(ctx, name, command): with InfiniteGlass.Display() as display: key = "IG_COMPONENT_%s" % name display.root[key] = json.dumps({ "command": command, "name": name }).encode("utf-8") display.flush()
def error_handler(self, swap, offendingMinorOpcode, offendingSequence, errorClass, severity): InfiniteGlass.DEBUG( "error", "Error: %s: swap=%s, offendingMinorOpcode=%s, offendingSequence=%s, errorClass=%s, severity=%s)\n" % (self, swap, offendingMinorOpcode, offendingSequence, errorClass, severity)) self.close_connection()