Codebase list gnome-shell-extension-appindicator / fe348d76-15f2-48da-b1af-1e22d803ae78/upstream/53
Import upstream version 53 Debian Janitor 11 months ago
12 changed file(s) with 860 addition(s) and 533 deletion(s). Raw diff Collapse all Expand all
88
99 # Conform to https://gitlab.gnome.org/GNOME/gjs/-/raw/gnome-3-34/.eslintrc.yml
1010 parserOptions:
11 ecmaVersion: 2017
11 ecmaVersion: 2019
4747 else
4848 PromiseUtils._promisify(Gtk.IconInfo.prototype, 'load_symbolic_async', 'load_symbolic_finish');
4949
50 const MAX_UPDATE_FREQUENCY = 100; // In ms
50 const MAX_UPDATE_FREQUENCY = 30; // In ms
51 const FALLBACK_ICON_NAME = 'image-loading-symbolic';
52 const PIXMAPS_FORMAT = imports.gi.Cogl.PixelFormat.ARGB_8888;
5153
5254 // eslint-disable-next-line no-unused-vars
5355 const SNICategory = Object.freeze({
8082 },
8183 });
8284
83 var AppIndicatorProxy = GObject.registerClass({
84 Signals: { 'destroy': {} },
85 }, class AppIndicatorProxy extends Gio.DBusProxy {
85 var AppIndicatorProxy = GObject.registerClass(
86 class AppIndicatorProxy extends Util.DBusProxy {
8687 static get interfaceInfo() {
8788 if (!this._interfaceInfo) {
8889 this._interfaceInfo = Gio.DBusInterfaceInfo.new_for_xml(
108109 return this._tupleType;
109110 }
110111
111 static get TUPLE_VARIANT_TYPE() {
112 if (!this._tupleVariantType)
113 this._tupleVariantType = new GLib.VariantType('(v)');
114
115 return this._tupleVariantType;
116 }
117
118112 static destroy() {
119113 delete this._interfaceInfo;
120 delete this._tupleVariantType;
121114 delete this._tupleType;
122115 }
123116
124117 _init(busName, objectPath) {
125118 const { interfaceInfo } = AppIndicatorProxy;
126119
127 super._init({
128 g_connection: Gio.DBus.session,
129 g_interface_name: interfaceInfo.name,
130 g_interface_info: interfaceInfo,
131 g_name: busName,
132 g_object_path: objectPath,
133 g_flags: Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES,
134 });
120 super._init(busName, objectPath, interfaceInfo,
121 Gio.DBusProxyFlags.GET_INVALIDATED_PROPERTIES);
135122
136123 this.set_cached_property('Status',
137124 new GLib.Variant('s', SNIStatus.PASSIVE));
138125
139 this._signalIds = [];
126
140127 this._accumulatedProperties = new Set();
141128 this._cancellables = new Map();
142129 this._changedProperties = Object.create(null);
143
144 this._signalIds.push(this.connect('g-signal',
145 (_proxy, ...args) => this._onSignal(...args).catch(logError)));
146
147 this._signalIds.push(this.connect('notify::g-name-owner', () => {
148 this._resetNeededProperties();
149 if (!this.gNameOwner)
150 this._cancelRefreshProperties();
151 else
152 this._setupProxyPropertyList();
153 }));
154130 }
155131
156132 async initAsync(cancellable) {
157 cancellable = new Util.CancellableChild(cancellable);
158 await this.init_async(GLib.PRIORITY_DEFAULT, cancellable);
159 this._cancellable = cancellable;
160
161 this.gInterfaceInfo.methods.map(m => m.name).forEach(method =>
162 this._ensureAsyncMethod(method));
133 await super.initAsync(cancellable);
163134
164135 this._setupProxyPropertyList();
165136 }
166137
167138 destroy() {
168 this.emit('destroy');
169 this._signalIds.forEach(id => this.disconnect(id));
170
171139 const cachedProperties = this.get_cached_property_names();
172140 if (cachedProperties) {
173141 cachedProperties.forEach(propertyName =>
174142 this.set_cached_property(propertyName, null));
175143 }
176144
177 if (this._cancellable)
178 this._cancellable.cancel();
179
180 this._cancellables.clear();
145 super.destroy();
146 }
147
148 _onNameOwnerChanged() {
149 this._resetNeededProperties();
150
151 if (!this.gNameOwner)
152 this._cancelRefreshProperties();
153 else
154 this._setupProxyPropertyList();
181155 }
182156
183157 _setupProxyPropertyList() {
235209 }));
236210 }
237211
238 async _onSignal(_sender, signal, params) {
212 _onSignal(...args) {
213 this._onSignalAsync(...args).catch(e => {
214 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
215 logError(e);
216 });
217 }
218
219 async _onSignalAsync(_sender, signal, params) {
239220 const property = this._signalToPropertyName(signal);
240221 if (!property)
241222 return;
266247 return;
267248
268249 this._signalsAccumulator = new PromiseUtils.TimeoutPromise(
269 GLib.PRIORITY_DEFAULT_IDLE, MAX_UPDATE_FREQUENCY, this._cancellable);
250 MAX_UPDATE_FREQUENCY, GLib.PRIORITY_DEFAULT_IDLE, this._cancellable);
270251 try {
271252 await this._signalsAccumulator;
272253 const refreshPropertiesPromises =
285266 _resetNeededProperties() {
286267 AppIndicator.NEEDED_PROPERTIES.forEach(p =>
287268 this.set_cached_property(p, null));
288 }
289
290 // This can be removed when we will have GNOME 43 as minimum version
291 _ensureAsyncMethod(method) {
292 if (this[`${method}Async`])
293 return;
294
295 if (!this[`${method}Remote`])
296 throw new Error(`Missing remote method '${method}'`);
297
298 this[`${method}Async`] = function (...args) {
299 return new Promise((resolve, reject) => {
300 this[`${method}Remote`](...args, (ret, e) => {
301 if (e)
302 reject(e);
303 else
304 resolve(ret);
305 });
306 });
307 };
308 }
309
310 getProperty(propertyName, cancellable) {
311 return this.g_connection.call(this.g_name,
312 this.g_object_path, 'org.freedesktop.DBus.Properties', 'Get',
313 GLib.Variant.new('(ss)', [this.g_interface_name, propertyName]),
314 AppIndicatorProxy.TUPLE_VARIANT_TYPE, Gio.DBusCallFlags.NONE, -1,
315 cancellable);
316 }
317
318 getProperties(cancellable) {
319 return this.g_connection.call(this.g_name,
320 this.g_object_path, 'org.freedesktop.DBus.Properties', 'GetAll',
321 GLib.Variant.new('(s)', [this.g_interface_name]),
322 GLib.VariantType.new('(a{sv})'), Gio.DBusCallFlags.NONE, -1,
323 cancellable);
324269 }
325270
326271 async refreshAllProperties() {
411356 addNew: true,
412357 });
413358 }
414 this._propertiesEmitTimeout = new PromiseUtils.TimeoutPromise(16,
415 GLib.PRIORITY_DEFAULT_IDLE, params.cancellable);
359 this._propertiesEmitTimeout = new PromiseUtils.TimeoutPromise(
360 MAX_UPDATE_FREQUENCY * 2, GLib.PRIORITY_DEFAULT_IDLE, params.cancellable);
416361 await this._propertiesEmitTimeout;
417362
418363 if (Object.keys(this._changedProperties).length) {
643588 };
644589 }
645590
591 get hasOverlayIcon() {
592 const { name, pixmap } = this.overlayIcon;
593
594 return name || (pixmap && pixmap.n_children());
595 }
596
646597 get hasNameOwner() {
647598 if (this._nameWatcher && !this._nameWatcher.nameOnBus)
648599 return false;
897848 };
898849 Signals.addSignalMethods(AppIndicator.prototype);
899850
900 let StTextureCacheSkippingGIcon;
851 let StTextureCacheSkippingFileIcon;
901852
902853 if (imports.system.version >= 17501) {
903854 try {
904 StTextureCacheSkippingGIcon = GObject.registerClass({
855 StTextureCacheSkippingFileIcon = GObject.registerClass({
905856 Implements: [Gio.Icon],
906 }, class StTextureCacheSkippingGIconClass extends Gio.EmblemedIcon {
857 }, class StTextureCacheSkippingFileIconImpl extends Gio.EmblemedIcon {
858 _init(params) {
859 // FIXME: We can't just inherit from Gio.FileIcon for some reason
860 super._init({ gicon: new Gio.FileIcon(params) });
861 }
862
907863 vfunc_to_tokens() {
908864 // Disables the to_tokens() vfunc so that the icon to_string()
909865 // method won't work and thus can't be kept forever around by
917873 } catch (e) {}
918874 }
919875
920 var IconActor = GObject.registerClass({
921 Signals: {
922 'requires-custom-image': {},
923 },
924 },
876 var IconActor = GObject.registerClass(
925877 class AppIndicatorsIconActor extends St.Icon {
878
879 static get DEFAULT_STYLE() {
880 return 'padding: 0';
881 }
882
883 static get USER_WRITABLE_PATHS() {
884 if (!this._userWritablePaths) {
885 this._userWritablePaths = [
886 GLib.get_user_cache_dir(),
887 GLib.get_user_data_dir(),
888 GLib.get_user_config_dir(),
889 GLib.get_user_runtime_dir(),
890 GLib.get_home_dir(),
891 GLib.get_tmp_dir(),
892 ];
893
894 this._userWritablePaths.push(Object.values(GLib.UserDirectory).slice(
895 0, -1).map(dirId => GLib.get_user_special_dir(dirId)));
896 }
897
898 return this._userWritablePaths;
899 }
926900
927901 _init(indicator, iconSize) {
928902 super._init({
929903 reactive: true,
930904 style_class: 'system-status-icon',
931 fallback_icon_name: 'image-loading-symbolic',
905 fallbackIconName: FALLBACK_ICON_NAME,
932906 });
933907
934908 this.name = this.constructor.name;
935909 this.add_style_class_name('appindicator-icon');
936910 this.add_style_class_name('status-notifier-icon');
937 this.set_style('padding:0');
911 this.set_style(AppIndicatorsIconActor.DEFAULT_STYLE);
938912
939913 let themeContext = St.ThemeContext.get_for_stage(global.stage);
940914 this.height = iconSize * themeContext.scale_factor;
948922
949923 Object.values(SNIconType).forEach(t => (this._loadingIcons[t] = new Map()));
950924
951 Util.connectSmart(this._indicator, 'icon', this, () => this._updateIcon().catch(logError));
952 Util.connectSmart(this._indicator, 'overlay-icon', this, this._updateOverlayIcon);
953 Util.connectSmart(this._indicator, 'reset', this, () => this._invalidateIcon());
925 Util.connectSmart(this._indicator, 'icon', this, () => {
926 if (this.is_mapped())
927 this._updateIcon();
928 });
929 Util.connectSmart(this._indicator, 'overlay-icon', this, () => {
930 if (this.is_mapped())
931 this._updateIcon();
932 });
933 Util.connectSmart(this._indicator, 'reset', this,
934 () => this._invalidateIconWhenFullyReady());
954935
955936 const settings = SettingsManager.getDefaultGSettings();
956 Util.connectSmart(settings, 'changed::icon-size', this, () => this._invalidateIcon());
937 Util.connectSmart(settings, 'changed::icon-size', this, () =>
938 this._updateWhenFullyReady());
957939 Util.connectSmart(settings, 'changed::custom-icons', this, () => {
958940 this._updateCustomIcons();
959 this._invalidateIcon();
941 this._invalidateIconWhenFullyReady();
960942 });
961943
962944 if (GObject.signal_lookup('resource-scale-changed', this))
965947 this.connect('notify::resource-scale', () => this._invalidateIcon());
966948
967949 Util.connectSmart(themeContext, 'notify::scale-factor', this, tc => {
968 this.height = iconSize * tc.scale_factor;
950 this._updateIconSize();
951 this.height = this._iconSize * tc.scale_factor;
952 this.width = -1;
969953 this._invalidateIcon();
970954 });
971955
972 Util.connectSmart(this._indicator, 'ready', this, () => {
973 this._updateIconClass();
974 this._updateCustomIcons();
975 this._invalidateIcon();
976 });
977
978 Util.connectSmart(Util.getDefaultTheme(), 'changed', this, this._invalidateIcon);
979
980 if (indicator.isReady) {
981 this._updateCustomIcons();
982
983 if (this.get_stage()) {
984 this._invalidateIcon();
985 } else {
986 const id = this.connect('parent-set', () => {
987 if (this.get_stage()) {
988 this.disconnect(id);
989 this._invalidateIcon();
990 }
991 });
992 }
993 }
956 Util.connectSmart(Util.getDefaultTheme(), 'changed', this,
957 () => this._invalidateIconWhenFullyReady());
958
959 this.connect('notify::mapped', () => {
960 if (!this.is_mapped())
961 this._updateWhenFullyReady();
962 });
963
964 this._updateWhenFullyReady();
994965
995966 this.connect('destroy', () => {
996967 this._iconCache.destroy();
998969 this._cancellable = null;
999970 this._indicator = null;
1000971 this._loadingIcons = null;
972 this._iconTheme = null;
1001973 });
1002974 }
1003975
1004976 get debugId() {
1005977 return this._indicator ? this._indicator.id : this.toString();
978 }
979
980 async _waitForFullyReady() {
981 const waitConditions = [];
982
983 if (!this.is_mapped()) {
984 waitConditions.push(new PromiseUtils.SignalConnectionPromise(
985 this, 'notify::mapped', this._cancellable));
986 }
987
988 if (!this._indicator.isReady) {
989 waitConditions.push(new PromiseUtils.SignalConnectionPromise(
990 this._indicator, 'ready', this._cancellable));
991 }
992
993 if (!waitConditions.length)
994 return true;
995
996 await Promise.all(waitConditions);
997 return this._waitForFullyReady();
998 }
999
1000 async _updateWhenFullyReady() {
1001 if (this._waitingReady)
1002 return;
1003
1004 try {
1005 this._waitingReady = true;
1006 await this._waitForFullyReady();
1007
1008 this._updateIconSize();
1009 this._updateIconClass();
1010 this._updateCustomIcons();
1011 this._invalidateIcon();
1012 } catch (e) {
1013 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
1014 logError(e);
1015 } finally {
1016 delete this._waitingReady;
1017 }
10061018 }
10071019
10081020 _updateIconClass() {
10671079 if (gicon)
10681080 return gicon;
10691081
1070 const iconInfo = this._getIconInfo(iconName, themePath, iconSize, iconScaling);
1071 const loadingId = iconInfo.path || id;
1082 const iconData = this._getIconData(iconName, themePath, iconSize, iconScaling);
1083 const loadingId = iconData.file ? iconData.file.get_path() : id;
10721084
10731085 const cancellable = await this._getIconLoadingCancellable(iconType, id);
10741086 try {
1075 gicon = await this._createIconByIconData(iconInfo, iconSize,
1087 gicon = await this._createIconByIconData(iconData, iconSize,
10761088 iconScaling, cancellable);
10771089 } finally {
10781090 this._cleanupIconLoadingCancellable(iconType, loadingId);
10831095 }
10841096
10851097 _getIconLookupFlags(themeNode) {
1086 // FIXME: Use St version if available (>= 44)
10871098 let lookupFlags = 0;
10881099
10891100 if (!themeNode)
11041115 return lookupFlags;
11051116 }
11061117
1107 _getIconLoadingColors(themeNode) {
1118 _getIconLoadingColors() {
1119 const themeNode = this.get_theme_node();
1120
11081121 if (!themeNode)
11091122 return null;
11101123
11441157 }
11451158 }
11461159
1147 async _createIconByIconInfo(iconInfo, iconSize, iconScaling, cancellable) {
1148 const themeNode = this.get_theme_node();
1149 const iconColors = this._getIconLoadingColors(themeNode);
1150
1160 async _createIconByIconInfo(iconInfo, iconSize, iconScaling, iconColors, cancellable) {
11511161 if (iconColors) {
11521162 const args = St.IconInfo && iconInfo instanceof St.IconInfo
11531163 ? [iconColors] : [iconColors.foreground, iconColors.success,
11611171 iconSize, iconScaling, cancellable);
11621172 }
11631173
1164 async _createIconByIconData({ iconInfo, path }, iconSize, iconScaling, cancellable) {
1165 if (!path && !iconInfo) {
1174 async _createIconByIconData(iconData, iconSize, iconScaling, cancellable) {
1175 const { file, iconInfo, name } = iconData;
1176
1177 if (!file && !name) {
11661178 if (this._createIconIdle) {
11671179 throw new GLib.Error(Gio.IOErrorEnum, Gio.IOErrorEnum.PENDING,
11681180 'Already in progress');
11791191 } finally {
11801192 delete this._createIconIdle;
11811193 }
1182 return null;
1194 return this.gicon;
11831195 } else if (this._createIconIdle) {
11841196 this._createIconIdle.cancel();
11851197 delete this._createIconIdle;
11861198 }
11871199
1200 if (name)
1201 return new Gio.ThemedIcon({ name });
1202
1203 if (!file)
1204 throw new Error('Neither file or name are set');
1205
1206 if (!this._isFileInWritableArea(file))
1207 return new Gio.FileIcon({ file });
1208
11881209 try {
11891210 const [format, width, height] = await GdkPixbuf.Pixbuf.get_file_info_async(
1190 path, cancellable);
1211 file.get_path(), cancellable);
11911212
11921213 if (!format) {
1193 Util.Logger.critical(`${this.debugId}, Invalid image format: ${path}`);
1214 Util.Logger.critical(`${this.debugId}, Invalid image format: ${file.get_path()}`);
11941215 return null;
11951216 }
11961217
11971218 if (width >= height * 1.5) {
11981219 /* Hello indicator-multiload! */
1199 await this._loadCustomImage(Gio.File.new_for_path(path),
1200 width, height, cancellable);
1220 await this._loadCustomImage(file,
1221 width, height, iconSize, iconScaling, cancellable);
12011222 return null;
1202 } else if (StTextureCacheSkippingGIcon) {
1223 } else if (StTextureCacheSkippingFileIcon) {
12031224 /* We'll wrap the icon so that it won't be cached forever by the shell */
1204 return new Gio.FileIcon({ file: Gio.File.new_for_path(path) });
1225 return new StTextureCacheSkippingFileIcon({ file });
12051226 } else if (iconInfo) {
12061227 return this._createIconByIconInfo(iconInfo, iconSize,
1207 iconScaling, cancellable);
1208 } else {
1209 return this._createIconByFile(Gio.File.new_for_path(path),
1210 iconSize, iconScaling, cancellable);
1211 }
1228 iconScaling, this._getIconLoadingColors(), cancellable);
1229 } else if (format.name === 'svg') {
1230 const iconColors = this._getIconLoadingColors();
1231
1232 if (iconColors) {
1233 const fileIcon = new Gio.FileIcon({ file });
1234 const iconTheme = this._iconTheme || this._createIconTheme();
1235 const fileIconInfo = iconTheme.lookup_by_gicon_for_scale(
1236 fileIcon, iconSize, iconScaling,
1237 this._getIconLookupFlags(this.get_theme_node()));
1238
1239 if (fileIconInfo) {
1240 return this._createIconByIconInfo(fileIconInfo,
1241 iconSize, iconScaling, iconColors, cancellable);
1242 }
1243 }
1244 }
1245
1246 return this._createIconByFile(file,
1247 iconSize, iconScaling, cancellable);
12121248 } catch (e) {
12131249 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
12141250 Util.Logger.warn(
1215 `${this.debugId}, Impossible to read image info from path '${path}': ${e}`);
1251 `${this.debugId}, Impossible to read image info from ` +
1252 `path '${file ? file.get_path() : null}' or name '${name}': ${e}`);
12161253 }
12171254 throw e;
12181255 }
12191256 }
12201257
1221 async _loadCustomImage(file, width, height, cancellable) {
1222 if (!(this instanceof CustomImageIconActor)) {
1223 this.emit('requires-custom-image');
1224 throw new GLib.Error(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED,
1225 'Loading cancelled, need specific class');
1226 }
1227
1228 const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
1258 async _loadCustomImage(file, width, height, iconSize, iconScaling, cancellable) {
12291259 const textureCache = St.TextureCache.get_default();
1230 const resourceScale = this._getResourceScale();
1231
12321260 const customImage = textureCache.load_file_async(file, -1,
1233 height, scaleFactor, resourceScale);
1234
1235 customImage.set({
1236 xAlign: imports.gi.Clutter.ActorAlign.CENTER,
1237 yAlign: imports.gi.Clutter.ActorAlign.CENTER,
1238 });
1261 height, 1, iconScaling);
1262
1263 const setCustomImageActor = imageActor => {
1264 const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
1265 const { content } = imageActor;
1266 imageActor.content = null;
1267 imageActor.destroy();
1268
1269 this._setImageContent(content,
1270 width * scaleFactor, height * scaleFactor);
1271 };
12391272
12401273 if (customImage.content) {
1241 this._setCustomImage(customImage, width, height);
1274 setCustomImageActor(customImage);
12421275 return;
12431276 }
12441277
12521285 try {
12531286 await Promise.race(racingPromises);
12541287 if (!waitPromise.resolved())
1255 this._setCustomImage(customImage, width, height);
1288 setCustomImageActor(customImage);
12561289 } catch (e) {
12571290 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
12581291 throw e;
12591292 } finally {
12601293 racingPromises.forEach(p => p.cancel());
1261
1262 if (this._customImage !== customImage)
1263 customImage.destroy();
1264 }
1265 }
1266
1267 _setCustomImage(imageActor, width, height) {
1268 if (this._customImage)
1269 this._customImage.destroy();
1270
1271 this._customImage = imageActor;
1272 this.add_child(this._customImage);
1273 this.width = width;
1274 this.height = height;
1275 }
1276
1277 _getIconInfo(name, themePath, size, scale) {
1278 if (name && name[0] === '/') {
1279 // HACK: icon is a path name. This is not specified by the api but at least inidcator-sensors uses it.
1280 return { iconInfo: null, path: name };
1281 } else if (name) {
1282 // we manually look up the icon instead of letting st.icon do it for us
1283 // this allows us to sneak in an indicator provided search path and to avoid ugly upscaled icons
1284
1285 // indicator-application looks up a special "panel" variant, we just replicate that here
1286 name += '-panel';
1287
1288 // icon info as returned by the lookup
1289 let iconInfo = null;
1290
1291 // we try to avoid messing with the default icon theme, so we'll create a new one if needed
1292 let iconTheme = null;
1293 const defaultTheme = Util.getDefaultTheme();
1294 if (themePath) {
1295 iconTheme = St.IconTheme ? new St.IconTheme() : new Gtk.IconTheme();
1296 defaultTheme.get_search_path().forEach(p =>
1297 iconTheme.append_search_path(p));
1298 iconTheme.append_search_path(themePath);
1299
1300 if (!Meta.is_wayland_compositor() && !St.IconTheme) {
1301 const defaultScreen = imports.gi.Gdk.Screen.get_default();
1302 if (defaultScreen)
1303 iconTheme.set_screen(defaultScreen);
1304 }
1294 }
1295 }
1296
1297 _isFileInWritableArea(file) {
1298 // No need to use IO here, we can just do some assumptions
1299 // print('Writable paths', IconActor.USER_WRITABLE_PATHS)
1300 const path = file.get_path();
1301 return IconActor.USER_WRITABLE_PATHS.some(writablePath =>
1302 path.startsWith(writablePath));
1303 }
1304
1305 _createIconTheme(searchPath = []) {
1306 if (St.IconTheme) {
1307 const iconTheme = new St.IconTheme();
1308 iconTheme.set_search_path(searchPath);
1309
1310 return iconTheme;
1311 }
1312
1313 const iconTheme = new Gtk.IconTheme();
1314 iconTheme.set_search_path(searchPath);
1315
1316 if (!Meta.is_wayland_compositor()) {
1317 const defaultScreen = Gdk.Screen.get_default();
1318 if (defaultScreen)
1319 iconTheme.set_screen(defaultScreen);
1320 }
1321
1322 return iconTheme;
1323 }
1324
1325 _getIconData(name, themePath, size, scale) {
1326 const emptyIconData = { iconInfo: null, file: null, name: null };
1327
1328 if (!name) {
1329 delete this._iconTheme;
1330 return emptyIconData;
1331 }
1332
1333 // HACK: icon is a path name. This is not specified by the API,
1334 // but at least indicator-sensors uses it.
1335 if (name[0] === '/') {
1336 delete this._iconTheme;
1337
1338 const file = Gio.File.new_for_path(name);
1339 return { file, iconInfo: null, name: null };
1340 }
1341
1342 if (name.includes('.')) {
1343 const splits = name.split('.');
1344
1345 if (['svg', 'png'].includes(splits[splits.length - 1]))
1346 name = splits.slice(0, -1).join('');
1347 }
1348
1349 if (themePath && Util.getDefaultTheme().get_search_path().includes(themePath))
1350 themePath = null;
1351
1352 if (themePath) {
1353 // If a theme path is provided, we need to lookup the icon ourself
1354 // as St won't be able to do it unless we mess with default theme
1355 // that is something we prefer not to do, as it would imply lots of
1356 // St.TextureCache cleanups.
1357
1358 const newSearchPath = [themePath];
1359 if (!this._iconTheme) {
1360 this._iconTheme = this._createIconTheme(newSearchPath);
13051361 } else {
1306 iconTheme = defaultTheme;
1307 }
1308 if (iconTheme) {
1309 // try to look up the icon in the icon theme
1310 iconInfo = iconTheme.lookup_icon_for_scale(name, size, scale,
1311 this._getIconLookupFlags(this.get_theme_node()) |
1312 (St.IconLookupFlags
1313 ? St.IconLookupFlags.GENERIC_FALLBACK
1314 : Gtk.IconLookupFlags.GENERIC_FALLBACK));
1315 // no icon? that's bad!
1316 if (iconInfo === null) {
1317 const msg = `${this.debugId}, Impossible to lookup icon for '${name}' in`;
1318 Util.Logger.warn(`${msg} ${themePath ? `path ${themePath}` : 'default theme'}`);
1319 } else { // we have an icon
1320 // get the icon path
1321 return { iconInfo, path: iconInfo.get_filename() };
1322 }
1323 }
1324 }
1325 return { iconInfo: null, path: null };
1326 }
1327
1328 async _argbToRgba(src, cancellable) {
1329 await new PromiseUtils.IdlePromise(GLib.PRIORITY_LOW, cancellable);
1330
1331 return PixmapsUtils.argbToRgba(src);
1332 }
1333
1334 async _createIconFromPixmap(iconType, iconSize, iconScaling, pixmapsVariant) {
1335 iconSize *= iconScaling;
1336
1362 const currentSearchPath = this._iconTheme.get_search_path();
1363
1364 if (!currentSearchPath.includes(newSearchPath))
1365 this._iconTheme.set_search_path(newSearchPath);
1366 }
1367
1368 // try to look up the icon in the icon theme
1369 const iconInfo = this._iconTheme.lookup_icon_for_scale(`${name}`,
1370 size, scale, this._getIconLookupFlags(this.get_theme_node()) |
1371 (St.IconLookupFlags
1372 ? St.IconLookupFlags.GENERIC_FALLBACK
1373 : Gtk.IconLookupFlags.GENERIC_FALLBACK));
1374
1375 if (iconInfo) {
1376 return {
1377 iconInfo,
1378 file: Gio.File.new_for_path(iconInfo.get_filename()),
1379 name: null,
1380 };
1381 }
1382
1383 const logger = this.gicon ? Util.Logger.debug : Util.Logger.warn;
1384 logger(`${this.debugId}, Impossible to lookup icon ` +
1385 `for '${name}' in ${themePath}`);
1386
1387 return emptyIconData;
1388 }
1389
1390 delete this._iconTheme;
1391 return { name, iconInfo: null, file: null };
1392 }
1393
1394 _setImageContent(content, width, height) {
1395 this.set({
1396 content,
1397 width,
1398 height,
1399 contentGravity: Clutter.ContentGravity.RESIZE_ASPECT,
1400 fallbackIconName: null,
1401 });
1402 }
1403
1404 async _createIconFromPixmap(iconType, iconSize, iconScaling, scaleFactor, pixmapsVariant) {
13371405 const { pixmapVariant, width, height, rowStride } =
1338 PixmapsUtils.getBestPixmap(pixmapsVariant, iconSize);
1406 PixmapsUtils.getBestPixmap(pixmapsVariant, iconSize * iconScaling);
13391407
13401408 const id = `__PIXMAP_ICON_${width}x${height}`;
13411409
1410 const imageContent = new St.ImageContent({
1411 preferredWidth: width,
1412 preferredHeight: height,
1413 });
1414
1415 imageContent.set_bytes(pixmapVariant.get_data_as_bytes(), PIXMAPS_FORMAT,
1416 width, height, rowStride);
1417
1418 if (iconType !== SNIconType.OVERLAY && !this._indicator.hasOverlayIcon) {
1419 const scaledSize = iconSize * scaleFactor;
1420 this._setImageContent(imageContent, scaledSize, scaledSize);
1421 return null;
1422 }
1423
13421424 const cancellable = this._getIconLoadingCancellable(iconType, id);
13431425 try {
1344 return GdkPixbuf.Pixbuf.new_from_bytes(
1345 await this._argbToRgba(pixmapVariant.deep_unpack(), cancellable),
1346 GdkPixbuf.Colorspace.RGB, true,
1347 8, width, height, rowStride);
1426 // FIXME: async API results in a gray icon for some reason
1427 const [inputStream] = imageContent.load(iconSize, cancellable);
1428 return await GdkPixbuf.Pixbuf.new_from_stream_at_scale_async(
1429 inputStream, -1, iconSize * iconScaling, true, cancellable);
13481430 } catch (e) {
13491431 // the image data was probably bogus. We don't really know why, but it _does_ happen.
13501432 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
13591441 // the cached one (as in some cases it may be equal, but not the same object).
13601442 // So when it's not need anymore we make sure to check the active state
13611443 // and set it to false so that it can be picked up by the garbage collector.
1362 _setGicon(iconType, gicon, iconSize) {
1444 _setGicon(iconType, gicon) {
13631445 if (iconType !== SNIconType.OVERLAY) {
13641446 if (gicon) {
1365 const isPixbuf = gicon instanceof GdkPixbuf.Pixbuf;
1366 this.gicon = StTextureCacheSkippingGIcon && !isPixbuf
1367 ? new StTextureCacheSkippingGIcon({ gicon })
1368 : new Gio.EmblemedIcon({ gicon });
1447 if (this.gicon === gicon ||
1448 (this.gicon && this.gicon.get_icon() === gicon))
1449 return;
1450
1451 if (gicon instanceof Gio.EmblemedIcon)
1452 this.gicon = gicon;
1453 else
1454 this.gicon = new Gio.EmblemedIcon({ gicon });
13691455
13701456 this._iconCache.updateActive(SNIconType.NORMAL, gicon,
13711457 this.gicon.get_icon() === gicon);
1372
1373 this.set_icon_size(iconSize);
13741458 } else {
13751459 this.gicon = null;
13761460 }
14301514 e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.PENDING))
14311515 return null;
14321516
1433
1434 if (iconType === SNIconType.OVERLAY)
1517 if (iconType === SNIconType.OVERLAY) {
14351518 logError(e, `${this.debugId} unable to update icon emblem`);
1436 else
1519 } else {
1520 this.fallbackIconName = FALLBACK_ICON_NAME;
14371521 logError(e, `${this.debugId} unable to update icon`);
1438 }
1439
1440 try {
1441 this._setGicon(iconType, gicon, iconSize);
1522 }
1523 }
1524
1525 try {
1526 this._setGicon(iconType, gicon);
14421527
14431528 if (pixmap && this.gicon) {
14441529 // The pixmap has been saved, we can free the variants memory
14691554 return gicon;
14701555 }
14711556
1472 if (pixmap && pixmap.n_children())
1473 return this._createIconFromPixmap(iconType, iconSize, iconScaling, pixmap);
1557 if (pixmap && pixmap.n_children()) {
1558 return this._createIconFromPixmap(iconType,
1559 iconSize, iconScaling, scaleFactor, pixmap);
1560 }
14741561
14751562 return null;
14761563 }
14891576 let iconType = this._indicator.status === SNIStatus.NEEDS_ATTENTION
14901577 ? SNIconType.ATTENTION : SNIconType.NORMAL;
14911578
1492 this._updateIconSize();
1493
14941579 try {
14951580 await this._updateIconByType(iconType, this._iconSize);
14961581 } catch (e) {
15201605 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED) &&
15211606 !e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.PENDING))
15221607 logError(e, `${this.debugId}: Updating overlay icon failed`);
1608 }
1609 }
1610
1611 async _invalidateIconWhenFullyReady() {
1612 if (this._waitingInvalidation)
1613 return;
1614
1615 try {
1616 this._waitingInvalidation = true;
1617 await this._waitForFullyReady();
1618 this._invalidateIcon();
1619 } catch (e) {
1620 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
1621 logError(e);
1622 } finally {
1623 delete this._waitingInvalidation;
15231624 }
15241625 }
15251626
15381639 _updateIconSize() {
15391640 const settings = SettingsManager.getDefaultGSettings();
15401641 const sizeValue = settings.get_int('icon-size');
1642
15411643 if (sizeValue > 0) {
15421644 if (!this._defaultIconSize)
15431645 this._defaultIconSize = this._iconSize;
15471649 this._iconSize = this._defaultIconSize;
15481650 delete this._defaultIconSize;
15491651 }
1652
1653 const themeIconSize = Math.round(
1654 this.get_theme_node().get_length('icon-size'));
1655 let iconStyle = AppIndicatorsIconActor.DEFAULT_STYLE;
1656
1657 if (themeIconSize > 0) {
1658 const { scaleFactor } = St.ThemeContext.get_for_stage(global.stage);
1659
1660 if (themeIconSize / scaleFactor !== this._iconSize) {
1661 iconStyle = `${AppIndicatorsIconActor.DEFAULT_STYLE};` +
1662 'icon-size: 0';
1663 }
1664 }
1665
1666 this.set_style(iconStyle);
1667 this.set_icon_size(this._iconSize);
15501668 }
15511669
15521670 _updateCustomIcons() {
15621680 });
15631681 }
15641682 });
1565
1566 var CustomImageIconActor = GObject.registerClass(
1567 class CustomImageIconActor extends IconActor {
1568 vfunc_paint(paintContext) {
1569 if (this._customImage) {
1570 this.paint_background(paintContext);
1571 this._customImage.paint(paintContext);
1572 return;
1573 }
1574
1575 super.vfunc_paint(paintContext);
1576 }
1577 });
1313 // along with this program; if not, write to the Free Software
1414 // Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
1515 const Gio = imports.gi.Gio;
16 const GObject = imports.gi.GObject;
1617 const GLib = imports.gi.GLib;
1718 const GdkPixbuf = imports.gi.GdkPixbuf;
1819 const PopupMenu = imports.ui.popupMenu;
198199 Signals.addSignalMethods(DbusMenuItem.prototype);
199200
200201
201 const BusClientProxy = Gio.DBusProxy.makeProxyWrapper(DBusInterfaces.DBusMenu);
202
203202 /**
204203 * The client does the heavy lifting of actually reading layouts and distributing events
205204 */
206 var DBusClient = class AppIndicatorsDBusClient {
207
208 constructor(busName, busPath) {
209 this._cancellable = new Gio.Cancellable();
210 this._proxy = new BusClientProxy(Gio.DBus.session,
211 busName,
212 busPath,
213 this._clientReady.bind(this),
214 this._cancellable);
215 this._items = new Map([
216 [
217 0,
218 new DbusMenuItem(this, 0, {
219 'children-display': GLib.Variant.new_string('submenu'),
220 }, []),
221 ],
222 ]);
223
224 // will be set to true if a layout update is requested while one is already in progress
225 // then the handler that completes the layout update will request another update
205
206 var DBusClient = GObject.registerClass({
207 Signals: { 'ready-changed': {} },
208 }, class AppIndicatorsDBusClient extends Util.DBusProxy {
209 static get interfaceInfo() {
210 if (!this._interfaceInfo) {
211 this._interfaceInfo = Gio.DBusInterfaceInfo.new_for_xml(
212 DBusInterfaces.DBusMenu);
213 }
214 return this._interfaceInfo;
215 }
216
217 static get baseItems() {
218 if (!this._baseItems) {
219 this._baseItems = {
220 'children-display': GLib.Variant.new_string('submenu'),
221 };
222 }
223 return this._baseItems;
224 }
225
226 static destroy() {
227 delete this._interfaceInfo;
228 }
229
230 _init(busName, objectPath) {
231 const { interfaceInfo } = AppIndicatorsDBusClient;
232
233 super._init(busName, objectPath, interfaceInfo,
234 Gio.DBusProxyFlags.DO_NOT_LOAD_PROPERTIES);
235
236 this._items = new Map();
237 this._items.set(0, new DbusMenuItem(this, 0, DBusClient.baseItems, []));
238 this._flagItemsUpdateRequired = false;
239
240 // will be set to true if a layout update is needed once active
226241 this._flagLayoutUpdateRequired = false;
227 this._flagLayoutUpdateInProgress = false;
228242
229243 // property requests are queued
230244 this._propertiesRequestedFor = new Set(/* ids */);
231245
232246 this._layoutUpdated = false;
233 Util.connectSmart(this._proxy, 'notify::g-name-owner', this, () => {
234 if (this.isReady)
235 this._requestLayoutUpdate();
236 });
247 this._active = false;
248 }
249
250 async initAsync(cancellable) {
251 await super.initAsync(cancellable);
252
253 this._requestLayoutUpdate();
254 }
255
256 _onNameOwnerChanged() {
257 if (this.isReady)
258 this._requestLayoutUpdate();
237259 }
238260
239261 get isReady() {
240 return this._layoutUpdated && !!this._proxy.g_name_owner;
262 return this._layoutUpdated && !!this.gNameOwner;
241263 }
242264
243265 get cancellable() {
249271 }
250272
251273 _requestLayoutUpdate() {
252 if (this._flagLayoutUpdateInProgress)
253 this._flagLayoutUpdateRequired = true;
254 else
255 this._beginLayoutUpdate();
256 }
257
258 async _requestProperties(id) {
259 this._propertiesRequestedFor.add(id);
274 const cancellable = new Util.CancellableChild(this._cancellable);
275 this._beginLayoutUpdate(cancellable);
276 }
277
278 async _requestProperties(propertyId, cancellable) {
279 this._propertiesRequestedFor.add(propertyId);
280
281 if (this._propertiesRequest && this._propertiesRequest.pending())
282 return;
260283
261284 // if we don't have any requests queued, we'll need to add one
262 if (!this._propertiesRequest || !this._propertiesRequest.pending()) {
263 this._propertiesRequest = new PromiseUtils.IdlePromise(
264 GLib.PRIORITY_DEFAULT_IDLE, this._cancellable);
265 await this._propertiesRequest;
266 this._beginRequestProperties();
267 }
268 }
269
270 _beginRequestProperties() {
271 this._proxy.GetGroupPropertiesRemote(
272 Array.from(this._propertiesRequestedFor),
273 [],
274 this._cancellable,
275 this._endRequestProperties.bind(this));
276
285 this._propertiesRequest = new PromiseUtils.IdlePromise(
286 GLib.PRIORITY_DEFAULT_IDLE, cancellable);
287 await this._propertiesRequest;
288
289 const requestedProperties = Array.from(this._propertiesRequestedFor);
277290 this._propertiesRequestedFor.clear();
278 return false;
279 }
280
281 _endRequestProperties(result, error) {
282 if (error) {
283 if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
284 Util.Logger.warn(`Could not retrieve properties: ${error}`);
285 return;
286 }
287
288 // for some funny reason, the result array is hidden in an array
289 result[0].forEach(([id, properties]) => {
291 const [result] = await this.GetGroupPropertiesAsync(requestedProperties,
292 [], cancellable);
293
294 result.forEach(([id, properties]) => {
290295 let item = this._items.get(id);
291296 if (!item)
292297 return;
316321
317322 // the original implementation will only request partial layouts if somehow possible
318323 // we try to save us from multiple kinds of race conditions by always requesting a full layout
319 _beginLayoutUpdate() {
324 _beginLayoutUpdate(cancellable) {
325 this._layoutUpdateUpdateAsync(cancellable).catch(e => {
326 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
327 logError(e);
328 });
329 }
330
331 // the original implementation will only request partial layouts if somehow possible
332 // we try to save us from multiple kinds of race conditions by always requesting a full layout
333 async _layoutUpdateUpdateAsync(cancellable) {
320334 // we only read the type property, because if the type changes after reading all properties,
321335 // the view would have to replace the item completely which we try to avoid
322 this._proxy.GetLayoutRemote(0, -1,
323 ['type', 'children-display'],
324 this._cancellable,
325 this._endLayoutUpdate.bind(this));
326
327 this._flagLayoutUpdateRequired = false;
328 this._flagLayoutUpdateInProgress = true;
336 if (this._layoutUpdateCancellable)
337 this._layoutUpdateCancellable.cancel();
338
339 this._layoutUpdateCancellable = cancellable;
340
341 try {
342 const [revision_, root] = await this.GetLayoutAsync(0, -1,
343 ['type', 'children-display'], cancellable);
344
345 this._updateLayoutState(true);
346 this._doLayoutUpdate(root, cancellable);
347 this._gcItems();
348 this._flagLayoutUpdateRequired = false;
349 this._flagItemsUpdateRequired = false;
350 } catch (e) {
351 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
352 this._updateLayoutState(false);
353 throw e;
354 } finally {
355 if (this._layoutUpdateCancellable === cancellable)
356 this._layoutUpdateCancellable = null;
357 }
329358 }
330359
331360 _updateLayoutState(state) {
335364 this.emit('ready-changed');
336365 }
337366
338 _endLayoutUpdate(result, error) {
339 if (error) {
340 if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED)) {
341 Util.Logger.warn(`While reading menu layout on proxy ${this._proxy.g_name_owner}: ${error}`);
342 this._updateLayoutState(false);
343 }
344 return;
345 }
346
347 let [revision_, root] = result;
348 this._updateLayoutState(true);
349 this._doLayoutUpdate(root);
350 this._gcItems();
351
352 if (this._flagLayoutUpdateRequired)
353 this._beginLayoutUpdate();
354 else
355 this._flagLayoutUpdateInProgress = false;
356 }
357
358 _doLayoutUpdate(item) {
359 let [id, properties, children] = item;
360
361 let childrenUnpacked = children.map(c => c.deep_unpack());
362 let childrenIds = childrenUnpacked.map(c => c[0]);
367 _doLayoutUpdate(item, cancellable) {
368 const [id, properties, children] = item;
369
370 const childrenUnpacked = children.map(c => c.deep_unpack());
371 const childrenIds = childrenUnpacked.map(([c]) => c);
363372
364373 // make sure all our children exist
365 childrenUnpacked.forEach(c => this._doLayoutUpdate(c));
374 childrenUnpacked.forEach(c => this._doLayoutUpdate(c, cancellable));
366375
367376 // make sure we exist
368377 const menuItem = this._items.get(id);
378
369379 if (menuItem) {
370380 // we do, update our properties if necessary
371381 for (let prop in properties)
372382 menuItem.propertySet(prop, properties[prop]);
373
374383
375384 // make sure our children are all at the right place, and exist
376385 let oldChildrenIds = menuItem.getChildrenIds();
395404
396405 // remove any old children that weren't reused
397406 oldChildrenIds.forEach(c => menuItem.removeChild(c));
398 } else {
399 // we don't, so let's create us
400 this._items.set(id, new DbusMenuItem(this, id, properties, childrenIds));
401 this._requestProperties(id).catch(e => {
402 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
403 Util.Logger.warn(`Could not get menu properties menu proxy: ${e}`);
404 });
405 }
407
408 if (!this._flagItemsUpdateRequired)
409 return id;
410 }
411
412 // we don't, so let's create us
413 let newMenuItem = menuItem;
414
415 if (!newMenuItem) {
416 newMenuItem = new DbusMenuItem(this, id, properties, childrenIds);
417 this._items.set(id, newMenuItem);
418 }
419
420 this._requestProperties(id, cancellable).catch(e => {
421 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
422 Util.Logger.warn(`Could not get menu properties menu proxy: ${e}`);
423 });
406424
407425 return id;
408426 }
409427
410 _clientReady(result, error) {
411 if (error) {
412 if (!error.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
413 Util.Logger.warn(`Could not initialize menu proxy: ${error}`);
414 return;
415 }
416
417 this._requestLayoutUpdate();
418
419 // listen for updated layouts and properties
420 this._proxy.connectSignal('LayoutUpdated', this._onLayoutUpdated.bind(this));
421 this._proxy.connectSignal('ItemsPropertiesUpdated', this._onPropertiesUpdated.bind(this));
428 async _doPropertiesUpdateAsync(cancellable) {
429 if (this._propertiesUpdateCancellable)
430 this._propertiesUpdateCancellable.cancel();
431
432 this._propertiesUpdateCancellable = cancellable;
433
434 try {
435 const requests = [];
436
437 this._items.forEach((_, id) =>
438 requests.push(this._requestProperties(id, cancellable)));
439
440 await Promise.all(requests);
441 } finally {
442 if (this._propertiesUpdateCancellable === cancellable)
443 this._propertiesUpdateCancellable = null;
444 }
445 }
446
447 _doPropertiesUpdate() {
448 const cancellable = new Util.CancellableChild(this._cancellable);
449 this._doPropertiesUpdateAsync(cancellable).catch(e => {
450 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
451 Util.Logger.warn(`Could not get menu properties menu proxy: ${e}`);
452 });
453 }
454
455
456 set active(active) {
457 const wasActive = this._active;
458 this._active = active;
459
460 if (active && wasActive !== active) {
461 if (this._flagLayoutUpdateRequired) {
462 this._requestLayoutUpdate();
463 } else if (this._flagItemsUpdateRequired) {
464 this._doPropertiesUpdate();
465 this._flagItemsUpdateRequired = false;
466 }
467 }
468 }
469
470 _onSignal(_sender, signal, params) {
471 if (signal === 'LayoutUpdated') {
472 if (!this._active) {
473 this._flagLayoutUpdateRequired = true;
474 return;
475 }
476
477 this._requestLayoutUpdate();
478 } else if (signal === 'ItemsPropertiesUpdated') {
479 if (!this._active) {
480 this._flagItemsUpdateRequired = true;
481 return;
482 }
483
484 this._onPropertiesUpdated(params.deep_unpack());
485 }
422486 }
423487
424488 getItem(id) {
429493 }
430494
431495 // we don't need to cache and burst-send that since it will not happen that frequently
432 sendAboutToShow(id) {
496 async sendAboutToShow(id) {
433497 /* Some indicators (you, dropbox!) don't use the right signature
434498 * and don't return a boolean, so we need to support both cases */
435 let connection = this._proxy.get_connection();
436 connection.call(this._proxy.get_name(), this._proxy.get_object_path(),
437 this._proxy.get_interface_name(), 'AboutToShow',
438 new GLib.Variant('(i)', [id]), null,
439 Gio.DBusCallFlags.NONE, -1, null, (proxy, res) => {
440 try {
441 let ret = proxy.call_finish(res);
442 if ((ret.is_of_type(new GLib.VariantType('(b)')) &&
443 ret.get_child_value(0).get_boolean()) ||
444 ret.is_of_type(new GLib.VariantType('()')))
445 this._requestLayoutUpdate();
446
447 } catch (e) {
448 Util.Logger.warn(`Impossible to send about-to-show to menu: ${e}`);
449 }
450 });
499 try {
500 const ret = await this.gConnection.call(this.gName, this.gObjectPath,
501 this.gInterfaceName, 'AboutToShow', new GLib.Variant('(i)', [id]),
502 null, Gio.DBusCallFlags.NONE, -1, this._cancellable);
503
504 if ((ret.is_of_type(new GLib.VariantType('(b)')) &&
505 ret.get_child_value(0).get_boolean()) ||
506 ret.is_of_type(new GLib.VariantType('()')))
507 this._requestLayoutUpdate();
508 } catch (e) {
509 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
510 logError(e);
511 }
451512 }
452513
453514 sendEvent(id, event, params, timestamp) {
454 if (!this._proxy)
515 if (!this.gNameOwner)
455516 return;
456517
457 this._proxy.EventRemote(id, event, params, timestamp, this._cancellable,
458 () => { /* we don't care */ });
459 }
460
461 _onLayoutUpdated() {
462 this._requestLayoutUpdate();
463 }
464
465 _onPropertiesUpdated(proxy, name, [changed, removed]) {
518 this.EventAsync(id, event, params, timestamp, this._cancellable).catch(e => {
519 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
520 logError(e);
521 });
522 }
523
524 _onPropertiesUpdated([changed, removed]) {
466525 changed.forEach(([id, props]) => {
467526 let item = this._items.get(id);
468527 if (!item)
479538 propNames.forEach(propName => item.propertySet(propName, null));
480539 });
481540 }
482
483 destroy() {
484 this.emit('destroy');
485
486 this._cancellable.cancel();
487 Signals._disconnectAll.apply(this._proxy);
488
489 this._proxy = null;
490 }
491 };
492 Signals.addSignalMethods(DBusClient.prototype);
541 });
542
543 if (imports.system.version < 17101) {
544 /* In old versions wrappers are not applied to sub-classes, so let's do it */
545 DBusClient.prototype.init_async = Gio.DBusProxy.prototype.init_async;
546 }
493547
494548 // ////////////////////////////////////////////////////////////////////////
495549 // PART TWO: "View" frontend implementation.
769823 this._rootMenu = null; // the shell menu
770824 this._rootItem = null; // the DbusMenuItem for the root
771825 this.indicator = indicator;
826 this.cancellable = new Util.CancellableChild(this.indicator.cancellable);
827
828 this._client.initAsync(this.cancellable).catch(e => {
829 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
830 logError(e);
831 });
772832
773833 Util.connectSmart(this._client, 'ready-changed', this,
774834 () => this.emit('ready-changed'));
775 }
776
777 get cancellable() {
778 return this._client.cancellable;
779835 }
780836
781837 get isReady() {
787843 attachToMenu(menu) {
788844 this._rootMenu = menu;
789845 this._rootItem = this._client.getRoot();
846 this._itemsBeingAdded = new Set();
790847
791848 // cleanup: remove existing children (just in case)
792849 this._rootMenu.removeAll();
806863 this._rootItem.sendAboutToShow();
807864
808865 // fill the menu for the first time
809 this._rootItem.getChildren().forEach(child =>
810 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child)));
866 const children = this._rootItem.getChildren();
867 children.forEach(child =>
868 this._onRootChildAdded(this._rootItem, child));
811869 }
812870
813871 _setOpenedSubmenu(submenu) {
827885 }
828886
829887 _onRootChildAdded(dbusItem, child, position) {
830 this._rootMenu.addMenuItem(MenuItemFactory.createItem(this, child), position);
888 // Menu additions can be expensive, so let's do it in different chunks
889 const basePriority = this.isOpen ? GLib.PRIORITY_DEFAULT : GLib.PRIORITY_LOW;
890 const idlePromise = new PromiseUtils.IdlePromise(
891 basePriority + this._itemsBeingAdded.size, this.cancellable);
892 this._itemsBeingAdded.add(child);
893
894 idlePromise.then(() => {
895 if (!this._itemsBeingAdded.has(child))
896 return;
897
898 this._rootMenu.addMenuItem(
899 MenuItemFactory.createItem(this, child), position);
900 }).catch(e => {
901 if (!e.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
902 logError(e);
903 }).finally(() => this._itemsBeingAdded.delete(child));
831904 }
832905
833906 _onRootChildRemoved(dbusItem, child) {
834907 // children like to play hide and seek
835908 // but we know how to find it for sure!
836 this._rootMenu._getMenuItems().forEach(item => {
837 if (item._dbusItem === child)
838 item.destroy();
839 });
909 const item = this._rootMenu._getMenuItems().find(it =>
910 it._dbusItem === child);
911
912 if (item)
913 item.destroy();
914 else
915 this._itemsBeingAdded.delete(child);
916
840917 }
841918
842919 _onRootChildMoved(dbusItem, child, oldpos, newpos) {
846923 _onMenuOpened(menu, state) {
847924 if (!this._rootItem)
848925 return;
926
927 this._client.active = state;
849928
850929 if (state) {
851930 if (this._openedSubMenu && this._openedSubMenu.isOpen)
868947 this._rootItem = null;
869948 this._rootMenu = null;
870949 this.indicator = null;
950 this._itemsBeingAdded = null;
871951 }
872952 };
873953 Signals.addSignalMethods(Client.prototype);
6060 16, Gtk.IconLookupFlags.GENERIC_FALLBACK);
6161 let iconFile = Gio.File.new_for_path(iconInfo.get_filename());
6262 let [, extension] = iconFile.get_basename().split('.');
63 let newName = `${iconName}-${Math.floor(Math.random() * 100)}.${extension}`;
63 let newName = `${Math.floor(Math.random() * 100)}${iconName}.${extension}`;
6464 let newFile = Gio.File.new_for_path(
6565 `${GLib.dir_make_tmp('indicator-test-XXXXXX')}/${newName}`);
6666 temporaryFiles.push(newFile, newFile.get_parent());
6767 iconFile.copy(newFile, Gio.FileCopyFlags.OVERWRITE, null, null);
6868
6969 indicator.set_icon_theme_path(newFile.get_parent().get_path());
70 indicator.set_icon(newFile.get_basename());
70 indicator.set_icon(newFile.get_basename().split('.').slice(0, -1).join(''));
7171 };
7272
7373 var menu = new Gtk.Menu();
7676 menu.append(item);
7777
7878 item = Gtk.MenuItem.new_with_label('Foo');
79 const fooItem = item;
80 let fooId = item.connect('activate', () => {
81 GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
82 print('Changing item label', fooItem.get_label());
83 fooItem.set_label('Destroy me now...');
84 fooItem.connect('activate', () => {
85 print('Removed item labeled', fooItem.get_label());
86 fooItem.destroy();
87 });
88 fooItem.disconnect(fooId);
89
90 const barItem = Gtk.MenuItem.new_with_label('Bar');
91 menu.insert(barItem, 2);
92 barItem.show();
93 return GLib.SOURCE_REMOVE;
94 });
95 });
7996 menu.append(item);
8097
8198 item = Gtk.ImageMenuItem.new_with_label('Calculator');
83100 menu.append(item);
84101
85102 item = Gtk.CheckMenuItem.new_with_label('Check me!');
103 const checkItem = item;
104 item.connect('activate', () => {
105 GLib.timeout_add(GLib.PRIORITY_DEFAULT, 500, () => {
106 print('changed item label', checkItem.get_label());
107 checkItem.set_label(`Checked at ${new Date().getTime()}`);
108 return GLib.SOURCE_REMOVE;
109 });
110 });
86111 menu.append(item);
87112
88113 item = Gtk.MenuItem.new_with_label('Blub');
245245 Util.connectSmart(this._indicator, 'reset', this, () => {
246246 this._updateStatus();
247247 this._updateLabel();
248 });
249 Util.connectSmart(this.icon, 'requires-custom-image', this, () => {
250 this._setIconActor(new AppIndicator.CustomImageIconActor(
251 indicator, Panel.PANEL_ICON_SIZE));
252248 });
253249 Util.connectSmart(this._indicator, 'accessible-name', this, () =>
254250 this.set_accessible_name(this._indicator.accessibleName));
594590 iconSize = Panel.PANEL_ICON_SIZE;
595591
596592 this.height = -1;
597 this._icon.set_width(iconSize * scaleFactor);
598 this._icon.set_height(iconSize * scaleFactor);
599 this._icon.set_y_align(Clutter.ActorAlign.CENTER);
600 this._icon.set_x_align(Clutter.ActorAlign.CENTER);
593 this._icon.set({
594 width: iconSize * scaleFactor,
595 height: iconSize * scaleFactor,
596 xAlign: Clutter.ActorAlign.CENTER,
597 yAlign: Clutter.ActorAlign.CENTER,
598 });
601599 }
602600 });
4848 <arg type="ai" name="idErrors" direction="out" />
4949 </method>
5050
51 <!-- Signals -->
51 <!-- Signals
5252 <signal name="ItemsPropertiesUpdated">
5353 <arg type="a(ia{sv})" name="updatedProps" direction="out" />
5454 <arg type="a(ias)" name="removedProps" direction="out" />
6161 <arg type="i" name="id" direction="out" />
6262 <arg type="u" name="timestamp" direction="out" />
6363 </signal>
64 -->
6465 </interface>
8282 <arg name="orientation" type="s" direction="in"/>
8383 </method>
8484
85 <!-- Signals: the client wants to change something in the status-->
85 <!-- Signals: the client wants to change something in the status
8686 <signal name="NewTitle">
8787 </signal>
8888
9494
9595 <signal name="NewOverlayIcon">
9696 </signal>
97
97 -->
9898 <!-- We disable this as we don't support tooltip, so no need to go through it
9999 <signal name="NewToolTip">
100100 </signal>
101101 -->
102102
103 <!--
103104 <signal name="NewStatus">
104105 <arg name="status" type="s"/>
105106 </signal>
107 -->
106108
107109
108 <!-- The following items are not supported by specs, but widely used -->
110 <!-- The following items are not supported by specs, but widely used
109111 <signal name="NewIconThemePath">
110112 <arg type="s" name="icon_theme_path" direction="out" />
111113 </signal>
112114
113115 <signal name="NewMenu"></signal>
116 -->
114117
115118 <!-- ayatana labels -->
116119 <!-- These are commented out because GDBusProxy would otherwise require them,
2121
2222
2323 <!-- signals -->
24
2524 <signal name="StatusNotifierItemRegistered">
2625 <arg type="s"/>
2726 </signal>
77 msgstr ""
88 "Project-Id-Version: AppIndicatorExtension\n"
99 "Report-Msgid-Bugs-To: \n"
10 "POT-Creation-Date: 2021-09-27 20:43+0200\n"
10 "POT-Creation-Date: 2023-03-07 02:36+0100\n"
1111 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
1212 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
1313 "Language-Team: LANGUAGE <LL@li.org>\n"
1616 "Content-Type: text/plain; charset=CHARSET\n"
1717 "Content-Transfer-Encoding: 8bit\n"
1818
19 #: prefs.js:67
19 #: prefs.js:59
20 msgid "Enable Legacy Tray Icons support"
21 msgstr ""
22
23 #: prefs.js:91
2024 msgid "Opacity (min: 0, max: 255)"
2125 msgstr ""
2226
23 #: prefs.js:95
27 #: prefs.js:120
2428 msgid "Desaturation (min: 0.0, max: 1.0)"
2529 msgstr ""
2630
27 #: prefs.js:123
31 #: prefs.js:148
2832 msgid "Brightness (min: -1.0, max: 1.0)"
2933 msgstr ""
3034
31 #: prefs.js:151
35 #: prefs.js:176
3236 msgid "Contrast (min: -1.0, max: 1.0)"
3337 msgstr ""
3438
35 #: prefs.js:179
39 #: prefs.js:204
3640 msgid "Icon size (min: 0, max: 96)"
3741 msgstr ""
3842
39 #: prefs.js:207
43 #: prefs.js:232
4044 msgid "Tray horizontal alignment"
4145 msgstr ""
4246
43 #: prefs.js:212
47 #: prefs.js:237
4448 msgid "Center"
4549 msgstr ""
4650
47 #: prefs.js:213
51 #: prefs.js:238
4852 msgid "Left"
4953 msgstr ""
5054
51 #: prefs.js:214
55 #: prefs.js:239
5256 msgid "Right"
5357 msgstr ""
5458
55 #: prefs.js:259
59 #: prefs.js:286
5660 msgid "Indicator ID"
5761 msgstr ""
5862
59 #: prefs.js:260
63 #: prefs.js:287
6064 msgid "Icon Name"
6165 msgstr ""
6266
63 #: prefs.js:261
67 #: prefs.js:288
6468 msgid "Attention Icon Name"
6569 msgstr ""
6670
67 #: prefs.js:336
71 #: prefs.js:363
6872 msgid "Preferences"
6973 msgstr ""
7074
71 #: prefs.js:338
75 #: prefs.js:365
7276 msgid "Custom Icons"
7377 msgstr ""
7478
7579 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:5
80 msgid "Enable legacy tray icons support"
81 msgstr ""
82
83 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:9
7684 msgid "Saturation"
7785 msgstr ""
7886
79 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:9
87 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:13
8088 msgid "Brightness"
8189 msgstr ""
8290
83 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:13
91 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:17
8492 msgid "Contrast"
8593 msgstr ""
8694
87 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:17
95 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:21
8896 msgid "Opacity"
8997 msgstr ""
9098
91 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:21
99 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:25
92100 msgid "Icon size"
93101 msgstr ""
94102
95 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:22
103 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:26
96104 msgid "Icon size in pixel"
97105 msgstr ""
98106
99 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:26
107 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:30
100108 msgid "Icon spacing"
101109 msgstr ""
102110
103 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:27
111 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:31
104112 msgid "Icon spacing within the tray"
105113 msgstr ""
106114
107 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:31
115 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:35
108116 msgid "Position in tray"
109117 msgstr ""
110118
111 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:32
119 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:36
112120 msgid "Set where the Icon tray should appear in Gnome tray"
113121 msgstr ""
114122
115 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:36
123 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:40
116124 msgid "Order in tray"
117125 msgstr ""
118126
119 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:37
127 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:41
120128 msgid "Set where the Icon tray should appear among other trays"
121129 msgstr ""
122130
123 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:41
131 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:45
124132 msgid "Custom icons"
125133 msgstr ""
126134
127 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:42
135 #: schemas/org.gnome.shell.extensions.appindicator.gschema.xml:46
128136 msgid "Replace any icons with custom icons from themes"
129137 msgstr ""
00 project('gnome-shell-ubuntu-appindicators',
1 version : '50',
1 version : '53',
22 meson_version : '>= 0.53',
33 license: 'GPL2',
44 )
2121 const Extension = imports.misc.extensionUtils.getCurrentExtension();
2222
2323 const AppIndicator = Extension.imports.appIndicator;
24 const DBusMenu = Extension.imports.dbusMenu;
2425 const IndicatorStatusIcon = Extension.imports.indicatorStatusIcon;
2526 const Interfaces = Extension.imports.interfaces;
2627 const PromiseUtils = Extension.imports.promiseUtils;
148149 // StatusNotifierItem interface... However let's do it after a low
149150 // priority idle, so that it won't affect startup.
150151 const cancellable = this._cancellable;
151 await new PromiseUtils.IdlePromise(GLib.PRIORITY_LOW, cancellable);
152152 const bus = Gio.DBus.session;
153153 const uniqueNames = await Util.getBusNames(bus, cancellable);
154154 const introspectName = async name => {
155 const nodes = await Util.introspectBusObject(bus, name, cancellable);
155 const nodes = Util.introspectBusObject(bus, name, cancellable,
156 ['org.kde.StatusNotifierItem']);
156157 const services = [...uniqueNames.get(name)];
157 nodes.forEach(({ nodeInfo, path }) => {
158 if (Util.dbusNodeImplementsInterfaces(nodeInfo, ['org.kde.StatusNotifierItem'])) {
159 const ids = services.map(s => Util.indicatorId(s, name, path));
160 if (ids.every(id => !this._items.has(id))) {
161 const service = services.find(s =>
162 s.startsWith('org.kde.StatusNotifierItem')) || services[0];
163 const id = Util.indicatorId(
164 path === DEFAULT_ITEM_OBJECT_PATH ? service : null,
165 name, path);
166 Util.Logger.warn(`Using Brute-force mode for StatusNotifierItem ${id}`);
167 this._registerItem(service, name, path);
168 }
158
159 for await (const node of nodes) {
160 const { path } = node;
161 const ids = services.map(s => Util.indicatorId(s, name, path));
162 if (ids.every(id => !this._items.has(id))) {
163 const service = services.find(s =>
164 s && s.startsWith('org.kde.StatusNotifierItem')) || services[0];
165 const id = Util.indicatorId(
166 path === DEFAULT_ITEM_OBJECT_PATH ? service : null,
167 name, path);
168 Util.Logger.warn(`Using Brute-force mode for StatusNotifierItem ${id}`);
169 this._registerItem(service, name, path);
169170 }
170 });
171 }
171172 };
172173 await Promise.allSettled([...uniqueNames.keys()].map(n => introspectName(n)));
173174 }
274275 Util.Logger.warn(`Failed to unexport watcher object: ${e}`);
275276 }
276277
278 DBusMenu.DBusClient.destroy();
277279 AppIndicator.AppIndicatorProxy.destroy();
280 Util.DBusProxy.destroy();
281 Util.destroyDefaultTheme();
278282
279283 this._dbusImpl.run_dispose();
280284 delete this._dbusImpl;
1515
1616 /* exported CancellableChild, getUniqueBusName, getBusNames,
1717 introspectBusObject, dbusNodeImplementsInterfaces, waitForStartupCompletion,
18 connectSmart, disconnectSmart, versionCheck, getDefaultTheme,
19 getProcessName, indicatorId, tryCleanupOldIndicators */
18 connectSmart, disconnectSmart, versionCheck, getDefaultTheme, destroyDefaultTheme,
19 getProcessName, indicatorId, tryCleanupOldIndicators, DBusProxy */
2020
2121 const ByteArray = imports.byteArray;
2222 const Gio = imports.gi.Gio;
3131 const Config = imports.misc.config;
3232 const ExtensionUtils = imports.misc.extensionUtils;
3333 const Extension = ExtensionUtils.getCurrentExtension();
34 const IndicatorStatusIcon = Extension.imports.indicatorStatusIcon;
3534 const PromiseUtils = Extension.imports.promiseUtils;
3635 const Signals = imports.signals;
3736
113112 return ByteArray.toString(bytes.toArray().map(v => !v ? 0x20 : v));
114113 }
115114
116 async function introspectBusObject(bus, name, cancellable, path = undefined) {
115 async function* introspectBusObject(bus, name, cancellable,
116 interfaces = undefined, path = undefined) {
117117 if (!path)
118118 path = '/';
119119
120120 const [introspection] = (await bus.call(name, path, 'org.freedesktop.DBus.Introspectable',
121121 'Introspect', null, new GLib.VariantType('(s)'), Gio.DBusCallFlags.NONE,
122 -1, cancellable)).deep_unpack();
122 5000, cancellable)).deep_unpack();
123123
124124 const nodeInfo = Gio.DBusNodeInfo.new_for_xml(introspection);
125 const nodes = [{ nodeInfo, path }];
125
126 if (!interfaces || dbusNodeImplementsInterfaces(nodeInfo, interfaces))
127 yield { nodeInfo, path };
126128
127129 if (path === '/')
128130 path = '';
129131
130 const requests = [];
131 for (const subNodes of nodeInfo.nodes) {
132 const subPath = `${path}/${subNodes.path}`;
133 requests.push(introspectBusObject(bus, name, cancellable, subPath));
134 }
135
136 for (const result of await Promise.allSettled(requests)) {
137 if (result.status === 'fulfilled')
138 result.value.forEach(n => nodes.push(n));
139 else if (!result.reason.matches(Gio.IOErrorEnum, Gio.IOErrorEnum.CANCELLED))
140 Logger.debug(`Impossible to get node info: ${result.reason}`);
141 }
142
143 return nodes;
132 for (const subNodeInfo of nodeInfo.nodes) {
133 const subPath = `${path}/${subNodeInfo.path}`;
134 yield* introspectBusObject(bus, name, cancellable, interfaces, subPath);
135 }
144136 }
145137
146138 function dbusNodeImplementsInterfaces(nodeInfo, interfaces) {
260252 throw new TypeError('Unexpected number of arguments');
261253 }
262254
255 let _defaultTheme;
263256 function getDefaultTheme() {
264 if (St.IconTheme)
265 return new St.IconTheme();
257 if (_defaultTheme)
258 return _defaultTheme;
259
260 if (St.IconTheme) {
261 _defaultTheme = new St.IconTheme();
262 return _defaultTheme;
263 }
266264
267265 if (Gdk.Screen && Gdk.Screen.get_default()) {
268 const defaultTheme = Gtk.IconTheme.get_default();
269 if (defaultTheme)
270 return defaultTheme;
271 }
272
273 const defaultTheme = new Gtk.IconTheme();
274 defaultTheme.set_custom_theme(St.Settings.get().gtk_icon_theme);
275 return defaultTheme;
266 _defaultTheme = Gtk.IconTheme.get_default();
267 if (_defaultTheme)
268 return _defaultTheme;
269 }
270
271 _defaultTheme = new Gtk.IconTheme();
272 _defaultTheme.set_custom_theme(St.Settings.get().gtk_icon_theme);
273 return _defaultTheme;
274 }
275
276 function destroyDefaultTheme() {
277 _defaultTheme = null;
276278 }
277279
278280 // eslint-disable-next-line valid-jsdoc
392394 }
393395
394396 function tryCleanupOldIndicators() {
397 const IndicatorStatusIcon = Extension.imports.indicatorStatusIcon;
395398 const indicatorType = IndicatorStatusIcon.BaseStatusIcon;
396399 const indicators = Object.values(Main.panel.statusArea).filter(i => i instanceof indicatorType);
397400
465468 this._realCancel();
466469 }
467470 });
471
472 var DBusProxy = GObject.registerClass({
473 Signals: { 'destroy': {} },
474 }, class DBusProxy extends Gio.DBusProxy {
475 static get TUPLE_VARIANT_TYPE() {
476 if (!this._tupleVariantType)
477 this._tupleVariantType = new GLib.VariantType('(v)');
478
479 return this._tupleVariantType;
480 }
481
482 static destroy() {
483 delete this._tupleType;
484 }
485
486 _init(busName, objectPath, interfaceInfo, flags = Gio.DBusProxyFlags.NONE) {
487 if (interfaceInfo.signals.length)
488 Logger.warn('Avoid exposing signals to gjs!');
489
490 super._init({
491 gConnection: Gio.DBus.session,
492 gInterfaceName: interfaceInfo.name,
493 gInterfaceInfo: interfaceInfo,
494 gName: busName,
495 gObjectPath: objectPath,
496 gFlags: flags,
497 });
498
499 this._signalIds = [];
500
501 if (!(flags & Gio.DBusProxyFlags.DO_NOT_CONNECT_SIGNALS)) {
502 this._signalIds.push(this.connect('g-signal',
503 (_proxy, ...args) => this._onSignal(...args)));
504 }
505
506 this._signalIds.push(this.connect('notify::g-name-owner', () =>
507 this._onNameOwnerChanged()));
508 }
509
510 async initAsync(cancellable) {
511 cancellable = new CancellableChild(cancellable);
512 await this.init_async(GLib.PRIORITY_DEFAULT, cancellable);
513 this._cancellable = cancellable;
514
515 this.gInterfaceInfo.methods.map(m => m.name).forEach(method =>
516 this._ensureAsyncMethod(method));
517 }
518
519 destroy() {
520 this.emit('destroy');
521
522 this._signalIds.forEach(id => this.disconnect(id));
523
524 if (this._cancellable)
525 this._cancellable.cancel();
526 }
527
528 // This can be removed when we will have GNOME 43 as minimum version
529 _ensureAsyncMethod(method) {
530 if (this[`${method}Async`])
531 return;
532
533 if (!this[`${method}Remote`])
534 throw new Error(`Missing remote method '${method}'`);
535
536 this[`${method}Async`] = function (...args) {
537 return new Promise((resolve, reject) => {
538 this[`${method}Remote`](...args, (ret, e) => {
539 if (e)
540 reject(e);
541 else
542 resolve(ret);
543 });
544 });
545 };
546 }
547
548 _onSignal() {
549 }
550
551 getProperty(propertyName, cancellable) {
552 return this.gConnection.call(this.gName,
553 this.gObjectPath, 'org.freedesktop.DBus.Properties', 'Get',
554 GLib.Variant.new('(ss)', [this.gInterfaceName, propertyName]),
555 DBusProxy.TUPLE_VARIANT_TYPE, Gio.DBusCallFlags.NONE, -1,
556 cancellable);
557 }
558
559 getProperties(cancellable) {
560 return this.gConnection.call(this.gName,
561 this.gObjectPath, 'org.freedesktop.DBus.Properties', 'GetAll',
562 GLib.Variant.new('(s)', [this.gInterfaceName]),
563 GLib.VariantType.new('(a{sv})'), Gio.DBusCallFlags.NONE, -1,
564 cancellable);
565 }
566 });
567
568 if (imports.system.version < 17101) {
569 /* In old versions wrappers are not applied to sub-classes, so let's do it */
570 DBusProxy.prototype.init_async = Gio.DBusProxy.prototype.init_async;
571 }