Skip to content

Commit 7dc60a9

Browse files
committed
melange/lookingGlass: better object inspection
Reworks object parsing code so that we can inspect more types, present the information more clearly, and prevent misbehavior with types we shouldn't iterate like importers. Notable behavior changes: - Boxed instance proxies such as Clutter.Color, Clutter.Rect, etc are now inspectable like other objects. - Inspecting importers no longer loads every file. - Errors during command eval() are now inspectable. - Values are currently truncated at 120 characters. When an inspectable object's toString value is longer you will need to manually call toString() on it to view the full value. Strings are viewable in a popup dialog. - The 'shortValue' key in the Inspect dbus method is no longer used as it is not available on all of the methods, and so easier to deal shortening values on the Melange side.
1 parent aa431c2 commit 7dc60a9

File tree

4 files changed

+131
-64
lines changed

4 files changed

+131
-64
lines changed

files/usr/share/cinnamon/cinnamon-looking-glass/page_inspect.py

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,13 @@ def onRowActivated(self, treeview, path, view_column):
5454

5555
def setInspectionData(self, path, data):
5656
self.store.clear()
57+
data.sort(key=lambda item: item["name"])
5758
for item in data:
58-
self.store.append([item["name"], item["type"], item["shortValue"], item["value"], path + "['" + item["name"] + "']"])
59+
self.store.append([item["name"],
60+
item["type"],
61+
pageutils.shortenValue(item["value"]),
62+
item["value"],
63+
path + "['" + item["name"] + "']"])
5964

6065
class ModulePage(pageutils.WindowAndActionBars):
6166
def __init__(self, parent):
@@ -124,7 +129,7 @@ def pushInspectionElement(self):
124129
self.insert.set_sensitive(True)
125130

126131
def updateInspector(self, path, objType, name, value, pushToStack=False):
127-
if objType == "object":
132+
if objType in ["array", "object"]:
128133
if pushToStack:
129134
self.pushInspectionElement()
130135

@@ -144,8 +149,8 @@ def updateInspector(self, path, objType, name, value, pushToStack=False):
144149
self.view.store.clear()
145150
else:
146151
self.view.store.clear()
147-
elif objType == "undefined":
148-
pageutils.ResultTextDialog("Value for '" + name + "'", "Value is <undefined>")
152+
elif objType == "undefined" or objType == "null":
153+
pageutils.ResultTextDialog("Value for '" + name + "'", "Value is <" + objType + ">")
149154
else:
150155
pageutils.ResultTextDialog("Value for " + objType + " '" + name + "'", value)
151156

files/usr/share/cinnamon/cinnamon-looking-glass/page_results.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
class ModulePage(pageutils.BaseListView):
77
def __init__(self, parent):
8-
store = Gtk.ListStore(int, str, str, str, str)
8+
store = Gtk.ListStore(int, str, str, str, str, str)
99
pageutils.BaseListView.__init__(self, store)
1010

1111
self.parent = parent
@@ -41,7 +41,7 @@ def onRowActivated(self, treeview, path, view_column):
4141
resultId = self.store.get_value(treeIter, 0)
4242
name = self.store.get_value(treeIter, 1)
4343
objType = self.store.get_value(treeIter, 2)
44-
value = self.store.get_value(treeIter, 3)
44+
value = self.store.get_value(treeIter, 5)
4545

4646
melangeApp.pages["inspect"].inspectElement("r(%d)" % resultId, objType, name, value)
4747

@@ -55,7 +55,12 @@ def getUpdates(self):
5555
if success:
5656
try:
5757
for item in data:
58-
self.store.append([int(item["index"]), item["command"], item["type"], item["object"], item["tooltip"]])
58+
self.store.append([int(item["index"]),
59+
item["command"],
60+
item["type"],
61+
pageutils.shortenValue(item["object"]),
62+
item["tooltip"],
63+
item["object"]])
5964
self._changed = True
6065
self.parent.activatePage("results")
6166
except Exception as e:

files/usr/share/cinnamon/cinnamon-looking-glass/pageutils.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,23 @@
22

33
from gi.repository import Gtk
44

5+
# truncate a string to single line 121 char max with ellipsis
6+
def shortenValue(value):
7+
changed = False
8+
truncPos = value.find("\n")
9+
if truncPos > 0:
10+
changed = True
11+
value = value[:truncPos]
12+
13+
if len(value) > 120:
14+
changed = True
15+
value = value[:120]
16+
17+
if changed:
18+
value += "…"
19+
20+
return value
21+
522
class ResultTextDialog(Gtk.Dialog):
623
def __init__(self, title, text):
724
Gtk.Dialog.__init__(self, title, None, 0,

js/ui/lookingGlass.js

Lines changed: 97 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ const Clutter = imports.gi.Clutter;
55
const Cogl = imports.gi.Cogl;
66
const Gio = imports.gi.Gio;
77
const GLib = imports.gi.GLib;
8+
const GObject = imports.gi.GObject;
89
const Lang = imports.lang;
910
const Meta = imports.gi.Meta;
1011
const Signals = imports.signals;
@@ -36,13 +37,94 @@ var commandHeader = 'const Clutter = imports.gi.Clutter; ' +
3637
'const r = Lang.bind(Main.lookingGlass, Main.lookingGlass.getResult); ';
3738

3839
const HISTORY_KEY = 'looking-glass-history';
39-
function objectToString(o) {
40-
if (typeof(o) == typeof(objectToString)) {
41-
// special case this since the default is way, way too verbose
42-
return '<js function>';
40+
41+
/* fake types for special cases:
42+
* -"array": objects that pass Array.isArray() and should only show enumerable properties
43+
* -"importer": objects that load modules on property access
44+
*/
45+
46+
// returns [typeString, valueString] for any object
47+
function getObjInfo(o) {
48+
let type, value;
49+
if (o === null)
50+
type = "null";
51+
else if (o === undefined)
52+
type = "undefined";
53+
54+
if (type) {
55+
value = "[" + type + "]";
4356
} else {
44-
return '' + o;
57+
// try to detect importers via their string representation
58+
try {
59+
value = o.toString();
60+
} catch (e) {
61+
if (e.message.includes("not an object instance - cannot convert to GObject*")) {
62+
// work around Clutter.Actor.prototype.toString override not handling being
63+
// called with the prototype itself as 'this'
64+
value = GObject.Object.prototype.toString.call(o);
65+
} else {
66+
value = "[error getting value]";
67+
}
68+
}
69+
70+
if (value.startsWith("[GjsFileImporter")
71+
|| value.startsWith("[object GjsModule gi")) {
72+
type = "importer";
73+
} else if (Array.isArray(o)) {
74+
type = "array";
75+
} else {
76+
type = typeof(o);
77+
}
78+
79+
// make empty strings/arrays obvious
80+
if (value === "")
81+
value = "[empty]";
82+
}
83+
84+
return [type, value];
85+
}
86+
87+
// returns an array of dictionaries conforming to the Inspect dbus schema
88+
function getObjKeysInfo(obj) {
89+
let [type, ] = getObjInfo(obj);
90+
if (!["array", "object"].includes(type))
91+
return [];
92+
93+
let keys = new Set();
94+
let curProto = obj;
95+
96+
// we ignore the Object prototype
97+
while (curProto && curProto !== Object.prototype) {
98+
let ownKeys;
99+
if (type === "array")
100+
ownKeys = curProto.keys(); // index properties only
101+
else
102+
ownKeys = Reflect.ownKeys(curProto); // all own properties and symbols
103+
104+
// adding to set ignores duplicates
105+
for (let key of ownKeys)
106+
keys.add(key);
107+
108+
curProto = Object.getPrototypeOf(curProto);
109+
}
110+
111+
112+
return Array.from(keys).map((k) => {
113+
let [t, v] = getObjInfo(obj[k]);
114+
return { name: k.toString(), type: t, value: v, shortValue: "" };
115+
});
116+
}
117+
118+
// always returns an object we can give back to melange.
119+
// it may be useful to inspect Error() objects
120+
function tryEval(js) {
121+
let out;
122+
try {
123+
out = eval(js);
124+
} catch (e) {
125+
out = e;
45126
}
127+
return out;
46128
}
47129

48130
function WindowList() {
@@ -93,13 +175,13 @@ WindowList.prototype = {
93175

94176
let lgInfo = {
95177
id: metaWindow._lgId.toString(),
96-
title: objectToString(metaWindow.title),
97-
wmclass: objectToString(metaWindow.get_wm_class()),
178+
title: metaWindow.title + '',
179+
wmclass: metaWindow.get_wm_class() + '',
98180
app: '' };
99181

100182
let app = tracker.get_window_app(metaWindow);
101183
if (app != null && !app.is_window_backed()) {
102-
lgInfo.app = objectToString(app.get_id());
184+
lgInfo.app = app.get_id() + '';
103185
} else {
104186
lgInfo.app = '<untracked>';
105187
}
@@ -454,43 +536,18 @@ Melange.prototype = {
454536
_pushResult: function(command, obj, tooltip) {
455537
let index = this._results.length;
456538
let result = {"o": obj, "index": index};
457-
this.rawResults.push({command: command, type: typeof(obj), object: objectToString(obj), index: index.toString(), tooltip: tooltip});
539+
let [type, value] = getObjInfo(obj);
540+
this.rawResults.push({command: command, type: type, object: value, index: index.toString(), tooltip: tooltip});
458541
this.emitResultUpdate();
459-
542+
460543
this._results.push(result);
461544
this._it = obj;
462545
},
463546

464547
inspect: function(path) {
465548
let fullCmd = commandHeader + path;
466-
467-
let result = eval(fullCmd);
468-
let resultObj = [];
469-
for (let key in result) {
470-
let type = typeof(result[key]);
471-
472-
//fixme: move this shortvalue stuff to python lg
473-
let shortValue, value;
474-
if (type === "undefined") {
475-
value = "";
476-
shortValue = "";
477-
} else if (result[key] === null) {
478-
value = "[null]";
479-
shortValue = value;
480-
} else {
481-
value = result[key].toString();
482-
shortValue = value;
483-
let i = value.indexOf('\n');
484-
let j = value.indexOf('\r');
485-
if (j != -1 && (i == -1 || i > j))
486-
i = j;
487-
if (i != -1)
488-
shortValue = value.substr(0, i) + '.. <more>';
489-
}
490-
resultObj.push({ name: key, type: type, value: value, shortValue: shortValue});
491-
}
492-
493-
return resultObj;
549+
let result = tryEval(fullCmd);
550+
return getObjKeysInfo(result);
494551
},
495552

496553
getIt: function () {
@@ -519,24 +576,14 @@ Melange.prototype = {
519576
this._history.addItem(command);
520577

521578
let fullCmd = commandHeader + command;
522-
523-
let resultObj;
524-
525579
let ts = GLib.get_monotonic_time();
526580

527-
try {
528-
resultObj = eval(fullCmd);
529-
} catch (e) {
530-
resultObj = '<exception ' + e + '>';
531-
}
581+
let resultObj = tryEval(fullCmd);
532582

533583
let ts2 = GLib.get_monotonic_time();
534-
535584
let tooltip = _("Execution time (ms): ") + (ts2 - ts) / 1000;
536585

537586
this._pushResult(command, resultObj, tooltip);
538-
539-
return;
540587
},
541588

542589
// DBus function
@@ -547,14 +594,7 @@ Melange.prototype = {
547594
// DBus function
548595
AddResult: function(path) {
549596
let fullCmd = commandHeader + path;
550-
551-
let resultObj;
552-
try {
553-
resultObj = eval(fullCmd);
554-
} catch (e) {
555-
resultObj = '<exception ' + e + '>';
556-
}
557-
this._pushResult(path, resultObj, "");
597+
this._pushResult(path, tryEval(fullCmd), "");
558598
},
559599

560600
// DBus function

0 commit comments

Comments
 (0)