/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */ const Clutter = imports.gi.Clutter; const GLib = imports.gi.GLib; const Gio = imports.gi.Gio; const Pango = imports.gi.Pango; const St = imports.gi.St; const Shell = imports.gi.Shell; const Signals = imports.signals; const Lang = imports.lang; const Mainloop = imports.mainloop; const Gettext = imports.gettext.domain('gnome-shell'); const _ = Gettext.gettext; const ExtensionSystem = imports.ui.extensionSystem; const Link = imports.ui.link; const Tweener = imports.ui.tweener; const Main = imports.ui.main; /* Imports...feel free to add here as needed */ var commandHeader = 'const Clutter = imports.gi.Clutter; ' + 'const GLib = imports.gi.GLib; ' + 'const Gtk = imports.gi.Gtk; ' + 'const Mainloop = imports.mainloop; ' + 'const Meta = imports.gi.Meta; ' + 'const Shell = imports.gi.Shell; ' + 'const Main = imports.ui.main; ' + 'const Lang = imports.lang; ' + 'const Tweener = imports.ui.tweener; ' + /* Utility functions...we should probably be able to use these * in the shell core code too. */ 'const stage = global.stage; ' + 'const color = function(pixel) { let c= new Clutter.Color(); c.from_pixel(pixel); return c; }; ' + /* Special lookingGlass functions */ 'const it = Main.lookingGlass.getIt(); ' + 'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); '; function Notebook() { this._init(); } Notebook.prototype = { _init: function() { this.actor = new St.BoxLayout({ vertical: true }); this.tabControls = new St.BoxLayout({ style_class: 'labels' }); this._selectedIndex = -1; this._tabs = []; }, appendPage: function(name, child) { let labelBox = new St.BoxLayout({ style_class: 'notebook-tab', reactive: true, track_hover: true }); let label = new St.Button({ label: name }); label.connect('clicked', Lang.bind(this, function () { this.selectChild(child); return true; })); labelBox.add(label, { expand: true }); this.tabControls.add(labelBox); let scrollview = new St.ScrollView({ x_fill: true, y_fill: true }); scrollview.get_hscroll_bar().hide(); scrollview.add_actor(child); let tabData = { child: child, labelBox: labelBox, label: label, scrollView: scrollview, _scrollToBottom: false }; this._tabs.push(tabData); scrollview.hide(); this.actor.add(scrollview, { expand: true }); let vAdjust = scrollview.vscroll.adjustment; vAdjust.connect('changed', Lang.bind(this, function () { this._onAdjustScopeChanged(tabData); })); vAdjust.connect('notify::value', Lang.bind(this, function() { this._onAdjustValueChanged(tabData); })); if (this._selectedIndex == -1) this.selectIndex(0); }, _unselect: function() { if (this._selectedIndex < 0) return; let tabData = this._tabs[this._selectedIndex]; tabData.labelBox.remove_style_pseudo_class('selected'); tabData.scrollView.hide(); this._selectedIndex = -1; }, selectIndex: function(index) { if (index == this._selectedIndex) return; this._unselect(); if (index < 0) { this.emit('selection', null); return; } let tabData = this._tabs[index]; tabData.labelBox.add_style_pseudo_class('selected'); tabData.scrollView.show(); this._selectedIndex = index; this.emit('selection', tabData.child); }, selectChild: function(child) { if (child == null) this.selectIndex(-1); else { for (let i = 0; i < this._tabs.length; i++) { let tabData = this._tabs[i]; if (tabData.child == child) { this.selectIndex(i); return; } } } }, scrollToBottom: function(index) { let tabData = this._tabs[index]; tabData._scrollToBottom = true; }, _onAdjustValueChanged: function (tabData) { let vAdjust = tabData.scrollView.vscroll.adjustment; if (vAdjust.value < (vAdjust.upper - vAdjust.lower - 0.5)) tabData._scrolltoBottom = false; }, _onAdjustScopeChanged: function (tabData) { if (!tabData._scrollToBottom) return; let vAdjust = tabData.scrollView.vscroll.adjustment; vAdjust.value = vAdjust.upper - vAdjust.page_size; } }; Signals.addSignalMethods(Notebook.prototype); function objectToString(o) { if (typeof(o) == typeof(objectToString)) { // special case this since the default is way, way too verbose return ""; } else { return "" + o; } } function ObjLink(o, title) { this._init(o, title); } ObjLink.prototype = { __proto__: Link.Link, _init: function(o, title) { let text; if (title) text = title; else text = objectToString(o); text = GLib.markup_escape_text(text, -1); this._obj = o; Link.Link.prototype._init.call(this, { label: text }); this.actor.get_child().single_line_mode = true; this.actor.connect('clicked', Lang.bind(this, this._onClicked)); }, _onClicked: function (link) { Main.lookingGlass.inspectObject(this._obj, this.actor); } }; function Result(command, o, index) { this._init(command, o, index); } Result.prototype = { _init : function(command, o, index) { this.index = index; this.o = o; this.actor = new St.BoxLayout({ vertical: true }); let cmdTxt = new St.Label({ text: command }); cmdTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END; this.actor.add(cmdTxt); let box = new St.BoxLayout({}); this.actor.add(box); let resultTxt = new St.Label({ text: 'r(' + index + ') = ' }); resultTxt.clutter_text.ellipsize = Pango.EllipsizeMode.END; box.add(resultTxt); let objLink = new ObjLink(o); box.add(objLink.actor); let line = new Clutter.Rectangle({ name: 'Separator' }); let padBin = new St.Bin({ name: 'Separator', x_fill: true, y_fill: true }); padBin.add_actor(line); this.actor.add(padBin); } }; function WindowList() { this._init(); } WindowList.prototype = { _init : function () { this.actor = new St.BoxLayout({ name: 'Windows', vertical: true, style: 'spacing: 8px' }); let display = global.screen.get_display(); let tracker = Shell.WindowTracker.get_default(); this._updateId = Main.initializeDeferredWork(this.actor, Lang.bind(this, this._updateWindowList)); display.connect('window-created', Lang.bind(this, this._updateWindowList)); tracker.connect('tracked-windows-changed', Lang.bind(this, this._updateWindowList)); }, _updateWindowList: function() { this.actor.get_children().forEach(function (actor) { actor.destroy(); }); let windows = global.get_windows(); let tracker = Shell.WindowTracker.get_default(); for (let i = 0; i < windows.length; i++) { let metaWindow = windows[i].metaWindow; metaWindow.connect('unmanaged', Lang.bind(this, this._updateWindowList)); let box = new St.BoxLayout({ vertical: true }); this.actor.add(box); let windowLink = new ObjLink(metaWindow, metaWindow.title); box.add(windowLink.actor, { x_align: St.Align.START, x_fill: false }); let propsBox = new St.BoxLayout({ vertical: true, style: 'padding-left: 6px;' }); box.add(propsBox); propsBox.add(new St.Label({ text: 'wmclass: ' + metaWindow.get_wm_class() })); let app = tracker.get_window_app(metaWindow); if (app != null && !app.is_transient()) { let icon = app.create_icon_texture(22); let propBox = new St.BoxLayout({ style: 'spacing: 6px; ' }); propsBox.add(propBox); propBox.add(new St.Label({ text: 'app: ' }), { y_fill: false }); let appLink = new ObjLink(app, app.get_id()); propBox.add(appLink.actor, { y_fill: false }); propBox.add(icon, { y_fill: false }); } else { propsBox.add(new St.Label({ text: '' })); } } } }; Signals.addSignalMethods(WindowList.prototype); function ObjInspector() { this._init(); } ObjInspector.prototype = { _init : function () { this._obj = null; this._previousObj = null; this._parentList = []; this.actor = new St.ScrollView({ x_fill: true, y_fill: true }); this.actor.get_hscroll_bar().hide(); this._container = new St.BoxLayout({ name: 'LookingGlassPropertyInspector', style_class: 'lg-dialog', vertical: true }); this.actor.add_actor(this._container); }, selectObject: function(obj, skipPrevious) { if (!skipPrevious) this._previousObj = this._obj; else this._previousObj = null; this._obj = obj; this._container.get_children().forEach(function (child) { child.destroy(); }); let hbox = new St.BoxLayout({ style_class: 'lg-obj-inspector-title' }); this._container.add_actor(hbox); let label = new St.Label({ text: 'Inspecting: %s: %s'.format(typeof(obj), objectToString(obj)) }); label.single_line_mode = true; hbox.add(label, { expand: true, y_fill: false }); let button = new St.Button({ label: 'Insert', style_class: 'lg-obj-inspector-button' }); button.connect('clicked', Lang.bind(this, this._onInsert)); hbox.add(button); if (this._previousObj != null) { button = new St.Button({ label: 'Back', style_class: 'lg-obj-inspector-button' }); button.connect('clicked', Lang.bind(this, this._onBack)); hbox.add(button); } button = new St.Button({ style_class: 'window-close' }); button.connect('clicked', Lang.bind(this, this.close)); hbox.add(button); if (typeof(obj) == typeof({})) { for (let propName in obj) { let valueStr; let link; try { let prop = obj[propName]; link = new ObjLink(prop).actor; } catch (e) { link = new St.Label({ text: '' }); } let hbox = new St.BoxLayout(); let propText = propName + ": " + valueStr; hbox.add(new St.Label({ text: propName + ': ' })); hbox.add(link); this._container.add_actor(hbox); } } }, open: function(sourceActor) { if (this._open) return; this._previousObj = null; this._open = true; this.actor.show(); if (sourceActor) { this.actor.set_scale(0, 0); let [sourceX, sourceY] = sourceActor.get_transformed_position(); let [sourceWidth, sourceHeight] = sourceActor.get_transformed_size(); this.actor.move_anchor_point(Math.floor(sourceX + sourceWidth / 2), Math.floor(sourceY + sourceHeight / 2)); Tweener.addTween(this.actor, { scale_x: 1, scale_y: 1, transition: "easeOutQuad", time: 0.2 }); } else { this.actor.set_scale(1, 1); } }, close: function() { if (!this._open) return; this._open = false; this.actor.hide(); this._previousObj = null; this._obj = null; }, _onInsert: function() { let obj = this._obj; this.close(); Main.lookingGlass.insertObject(obj); }, _onBack: function() { this.selectObject(this._previousObj, true); } }; function Inspector() { this._init(); } Inspector.prototype = { _init: function() { let width = 150; let primary = global.get_primary_monitor(); let eventHandler = new St.BoxLayout({ name: 'LookingGlassDialog', vertical: false, y: primary.y + Math.floor(primary.height / 2), reactive: true }); eventHandler.connect('notify::allocation', Lang.bind(this, function () { eventHandler.x = primary.x + Math.floor((primary.width - eventHandler.width) / 2); })); Main.uiGroup.add_actor(eventHandler); let displayText = new St.Label(); eventHandler.add(displayText, { expand: true }); let borderPaintTarget = null; let borderPaintId = null; eventHandler.connect('destroy', Lang.bind(this, function() { if (borderPaintTarget != null) borderPaintTarget.disconnect(borderPaintId); })); eventHandler.connect('button-press-event', Lang.bind(this, function (actor, event) { Clutter.ungrab_pointer(eventHandler); let [stageX, stageY] = event.get_coords(); let target = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, stageX, stageY); this.emit('target', target, stageX, stageY); eventHandler.destroy(); this.emit('closed'); return true; })); eventHandler.connect('motion-event', Lang.bind(this, function (actor, event) { let [stageX, stageY] = event.get_coords(); let target = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, stageX, stageY); let id, style_class; if (target instanceof St.Widget) { id = target.get_theme_node().get_element_id(); style_class = target.get_theme_node().get_element_class(); } let position = ''; let style = '