~darkxst/ubuntu/saucy/gnome-shell/upstart_log

« back to all changes in this revision

Viewing changes to js/ui/status/keyboard.js

  • Committer: Package Import Robot
  • Author(s): Jeremy Bicha
  • Date: 2013-05-31 12:01:12 UTC
  • mfrom: (1.1.49) (19.1.36 experimental)
  • Revision ID: package-import@ubuntu.com-20130531120112-ew91khxf051x9i2r
Tags: 3.8.2-1ubuntu1
* Merge with Debian (LP: #1185869, #1185721). Remaining changes:
  - debian/control.in:
    + Build-depend on libsystemd-login-dev & libsystemd-daemon-dev
    + Depend on gdm instead of gdm3
    + Don't recommend gnome-session-fallback
  - debian/patches/40_change-pam-name-to-match-gdm.patch:
  - debian/patches/revert-suspend-break.patch:
    + Disabled, not needed on Ubuntu
  - debian/patches/ubuntu-lightdm-user-switching.patch:
    + Allow user switching when using LightDM. Thanks Gerhard Stein
      for rebasing against gnome-shell 3.8!
  - debian/patches/ubuntu_lock_on_suspend.patch
    + Respect Ubuntu's lock-on-suspend setting.
      Disabled until it can be rewritten.
  - debian/patches/git_relock_screen_after_crash.patch:
    + Add Upstream fix for unlocked session after crash (LP: #1064584)
* Note that the new GNOME Classic mode (which requires installing
  gnome-shell-extensions) won't work until gnome-session 3.8 is
  available in Ubuntu

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
1
// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
2
2
 
 
3
const Clutter = imports.gi.Clutter;
3
4
const Gio = imports.gi.Gio;
4
5
const GLib = imports.gi.GLib;
5
6
const GnomeDesktop = imports.gi.GnomeDesktop;
6
7
const Lang = imports.lang;
 
8
const Meta = imports.gi.Meta;
7
9
const Shell = imports.gi.Shell;
8
10
const Signals = imports.signals;
9
11
const St = imports.gi.St;
21
23
const Main = imports.ui.main;
22
24
const PopupMenu = imports.ui.popupMenu;
23
25
const PanelMenu = imports.ui.panelMenu;
 
26
const SwitcherPopup = imports.ui.switcherPopup;
24
27
const Util = imports.misc.util;
25
28
 
26
29
const DESKTOP_INPUT_SOURCES_SCHEMA = 'org.gnome.desktop.input-sources';
42
45
        this._readyCallback = readyCallback;
43
46
        this._candidatePopup = new IBusCandidatePopup.CandidatePopup();
44
47
 
45
 
        this._ibus = null;
46
48
        this._panelService = null;
47
49
        this._engines = {};
48
50
        this._ready = false;
49
51
        this._registerPropertiesId = 0;
 
52
        this._currentEngineName = null;
50
53
 
51
 
        this._nameWatcherId = Gio.DBus.session.watch_name(IBus.SERVICE_IBUS,
52
 
                                                          Gio.BusNameWatcherFlags.NONE,
53
 
                                                          Lang.bind(this, this._onNameAppeared),
54
 
                                                          Lang.bind(this, this._clear));
 
54
        this._ibus = IBus.Bus.new_async();
 
55
        this._ibus.connect('connected', Lang.bind(this, this._onConnected));
 
56
        this._ibus.connect('disconnected', Lang.bind(this, this._clear));
 
57
        // Need to set this to get 'global-engine-changed' emitions
 
58
        this._ibus.set_watch_ibus_signal(true);
 
59
        this._ibus.connect('global-engine-changed', Lang.bind(this, this._engineChanged));
55
60
    },
56
61
 
57
62
    _clear: function() {
58
63
        if (this._panelService)
59
64
            this._panelService.destroy();
60
 
        if (this._ibus)
61
 
            this._ibus.destroy();
62
65
 
63
 
        this._ibus = null;
64
66
        this._panelService = null;
65
67
        this._candidatePopup.setPanelService(null);
66
68
        this._engines = {};
67
69
        this._ready = false;
68
70
        this._registerPropertiesId = 0;
69
 
    },
 
71
        this._currentEngineName = null;
70
72
 
71
 
    _onNameAppeared: function() {
72
 
        this._ibus = IBus.Bus.new_async();
73
 
        this._ibus.connect('connected', Lang.bind(this, this._onConnected));
 
73
        if (this._readyCallback)
 
74
            this._readyCallback(false);
74
75
    },
75
76
 
76
77
    _onConnected: function() {
79
80
                                      IBus.BusNameFlag.REPLACE_EXISTING,
80
81
                                      -1, null,
81
82
                                      Lang.bind(this, this._initPanelService));
82
 
        this._ibus.connect('disconnected', Lang.bind(this, this._clear));
83
83
    },
84
84
 
85
85
    _initEngines: function(ibus, result) {
89
89
                let name = enginesList[i].get_name();
90
90
                this._engines[name] = enginesList[i];
91
91
            }
 
92
            this._updateReadiness();
92
93
        } else {
93
94
            this._clear();
94
 
            return;
95
95
        }
96
 
 
97
 
        this._updateReadiness();
98
96
    },
99
97
 
100
98
    _initPanelService: function(ibus, result) {
103
101
            this._panelService = new IBus.PanelService({ connection: this._ibus.get_connection(),
104
102
                                                         object_path: IBus.PATH_PANEL });
105
103
            this._candidatePopup.setPanelService(this._panelService);
106
 
            // Need to set this to get 'global-engine-changed' emitions
107
 
            this._ibus.set_watch_ibus_signal(true);
108
 
            this._ibus.connect('global-engine-changed', Lang.bind(this, this._resetProperties));
109
104
            this._panelService.connect('update-property', Lang.bind(this, this._updateProperty));
110
 
            this._resetProperties();
 
105
            // If an engine is already active we need to get its properties
 
106
            this._ibus.get_global_engine_async(-1, null, Lang.bind(this, function(i, result) {
 
107
                let engine;
 
108
                try {
 
109
                    engine = this._ibus.get_global_engine_async_finish(result);
 
110
                    if (!engine)
 
111
                        return;
 
112
                } catch(e) {
 
113
                    return;
 
114
                }
 
115
                this._engineChanged(this._ibus, engine.get_name());
 
116
            }));
 
117
            this._updateReadiness();
111
118
        } else {
112
119
            this._clear();
113
 
            return;
114
120
        }
115
 
 
116
 
        this._updateReadiness();
117
121
    },
118
122
 
119
123
    _updateReadiness: function() {
120
124
        this._ready = (Object.keys(this._engines).length > 0 &&
121
125
                       this._panelService != null);
122
126
 
123
 
        if (this._ready && this._readyCallback)
124
 
            this._readyCallback();
 
127
        if (this._readyCallback)
 
128
            this._readyCallback(this._ready);
125
129
    },
126
130
 
127
 
    _resetProperties: function() {
128
 
        this.emit('properties-registered', null);
 
131
    _engineChanged: function(bus, engineName) {
 
132
        if (!this._ready)
 
133
            return;
 
134
 
 
135
        this._currentEngineName = engineName;
129
136
 
130
137
        if (this._registerPropertiesId != 0)
131
138
            return;
138
145
                this._panelService.disconnect(this._registerPropertiesId);
139
146
                this._registerPropertiesId = 0;
140
147
 
141
 
                this.emit('properties-registered', props);
 
148
                this.emit('properties-registered', this._currentEngineName, props);
142
149
            }));
143
150
    },
144
151
 
145
152
    _updateProperty: function(panel, prop) {
146
 
        this.emit('property-updated', prop);
 
153
        this.emit('property-updated', this._currentEngineName, prop);
147
154
    },
148
155
 
149
156
    activateProperty: function(key, state) {
150
157
        this._panelService.property_activate(key, state);
151
158
    },
152
159
 
153
 
    hasProperties: function(id) {
154
 
        if (id == 'anthy')
155
 
            return true;
156
 
 
157
 
        return false;
158
 
    },
159
 
 
160
160
    getEngineDesc: function(id) {
161
161
        if (!IBus || !this._ready)
162
162
            return null;
181
181
    }
182
182
});
183
183
 
 
184
const InputSource = new Lang.Class({
 
185
    Name: 'InputSource',
 
186
 
 
187
    _init: function(type, id, displayName, shortName, index) {
 
188
        this.type = type;
 
189
        this.id = id;
 
190
        this.displayName = displayName;
 
191
        this._shortName = shortName;
 
192
        this.index = index;
 
193
 
 
194
        this._menuItem = new LayoutMenuItem(this.displayName, this._shortName);
 
195
        this._menuItem.connect('activate', Lang.bind(this, this.activate));
 
196
        this._indicatorLabel = new St.Label({ text: this._shortName });
 
197
 
 
198
        this.properties = null;
 
199
    },
 
200
 
 
201
    destroy: function() {
 
202
        this._menuItem.destroy();
 
203
        this._indicatorLabel.destroy();
 
204
    },
 
205
 
 
206
    get shortName() {
 
207
        return this._shortName;
 
208
    },
 
209
 
 
210
    set shortName(v) {
 
211
        this._shortName = v;
 
212
        this._menuItem.indicator.set_text(v);
 
213
        this._indicatorLabel.set_text(v);
 
214
    },
 
215
 
 
216
    get menuItem() {
 
217
        return this._menuItem;
 
218
    },
 
219
 
 
220
    get indicatorLabel() {
 
221
        return this._indicatorLabel;
 
222
    },
 
223
 
 
224
    activate: function() {
 
225
        this.emit('activate');
 
226
    },
 
227
});
 
228
Signals.addSignalMethods(InputSource.prototype);
 
229
 
 
230
const InputSourcePopup = new Lang.Class({
 
231
    Name: 'InputSourcePopup',
 
232
    Extends: SwitcherPopup.SwitcherPopup,
 
233
 
 
234
    _init: function(items, action, actionBackward) {
 
235
        this.parent(items);
 
236
 
 
237
        this._action = action;
 
238
        this._actionBackward = actionBackward;
 
239
    },
 
240
 
 
241
    _createSwitcher: function() {
 
242
        this._switcherList = new InputSourceSwitcher(this._items);
 
243
        return true;
 
244
    },
 
245
 
 
246
    _initialSelection: function(backward, binding) {
 
247
        if (binding == 'switch-input-source') {
 
248
            if (backward)
 
249
                this._selectedIndex = this._items.length - 1;
 
250
        } else if (binding == 'switch-input-source-backward') {
 
251
            if (!backward)
 
252
                this._selectedIndex = this._items.length - 1;
 
253
        }
 
254
        this._select(this._selectedIndex);
 
255
    },
 
256
 
 
257
    _keyPressHandler: function(keysym, backwards, action) {
 
258
        if (action == this._action)
 
259
            this._select(backwards ? this._previous() : this._next());
 
260
        else if (action == this._actionBackward)
 
261
            this._select(backwards ? this._next() : this._previous());
 
262
        else if (keysym == Clutter.Left)
 
263
            this._select(this._previous());
 
264
        else if (keysym == Clutter.Right)
 
265
            this._select(this._next());
 
266
    },
 
267
 
 
268
    _finish : function() {
 
269
        this.parent();
 
270
 
 
271
        this._items[this._selectedIndex].activate();
 
272
    },
 
273
});
 
274
 
 
275
const InputSourceSwitcher = new Lang.Class({
 
276
    Name: 'InputSourceSwitcher',
 
277
    Extends: SwitcherPopup.SwitcherList,
 
278
 
 
279
    _init: function(items) {
 
280
        this.parent(true);
 
281
 
 
282
        for (let i = 0; i < items.length; i++)
 
283
            this._addIcon(items[i]);
 
284
    },
 
285
 
 
286
    _addIcon: function(item) {
 
287
        let box = new St.BoxLayout({ vertical: true });
 
288
 
 
289
        let bin = new St.Bin({ style_class: 'input-source-switcher-symbol' });
 
290
        let symbol = new St.Label({ text: item.shortName });
 
291
        bin.set_child(symbol);
 
292
        box.add(bin, { x_fill: false, y_fill: false } );
 
293
 
 
294
        let text = new St.Label({ text: item.displayName });
 
295
        box.add(text, { x_fill: false });
 
296
 
 
297
        this.addItem(box, text);
 
298
    }
 
299
});
 
300
 
184
301
const InputSourceIndicator = new Lang.Class({
185
302
    Name: 'InputSourceIndicator',
186
303
    Extends: PanelMenu.Button,
187
304
 
188
 
    _propertiesWhitelist: [
189
 
        'InputMode',
190
 
        'TypingMode',
191
 
        'DictMode'
192
 
    ],
193
 
 
194
305
    _init: function() {
195
306
        this.parent(0.0, _("Keyboard"));
196
307
 
201
312
        this.actor.add_actor(this._container);
202
313
        this.actor.add_style_class_name('panel-status-button');
203
314
 
204
 
        this._labelActors = {};
205
 
        this._layoutItems = {};
206
 
 
 
315
        // All valid input sources currently in the gsettings
 
316
        // KEY_INPUT_SOURCES list indexed by their index there
 
317
        this._inputSources = {};
 
318
        // All valid input sources currently in the gsettings
 
319
        // KEY_INPUT_SOURCES list of type INPUT_SOURCE_TYPE_IBUS
 
320
        // indexed by the IBus ID
 
321
        this._ibusSources = {};
 
322
 
 
323
        this._currentSource = null;
 
324
 
 
325
        // All valid input sources currently in the gsettings
 
326
        // KEY_INPUT_SOURCES list ordered by most recently used
 
327
        this._mruSources = [];
 
328
        this._keybindingAction =
 
329
            Main.wm.addKeybinding('switch-input-source',
 
330
                                  new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }),
 
331
                                  Meta.KeyBindingFlags.REVERSES,
 
332
                                  Shell.KeyBindingMode.ALL & ~Shell.KeyBindingMode.MESSAGE_TRAY,
 
333
                                  Lang.bind(this, this._switchInputSource));
 
334
        this._keybindingActionBackward =
 
335
            Main.wm.addKeybinding('switch-input-source-backward',
 
336
                                  new Gio.Settings({ schema: "org.gnome.desktop.wm.keybindings" }),
 
337
                                  Meta.KeyBindingFlags.REVERSES |
 
338
                                  Meta.KeyBindingFlags.REVERSED,
 
339
                                  Shell.KeyBindingMode.ALL & ~Shell.KeyBindingMode.MESSAGE_TRAY,
 
340
                                  Lang.bind(this, this._switchInputSource));
207
341
        this._settings = new Gio.Settings({ schema: DESKTOP_INPUT_SOURCES_SCHEMA });
208
342
        this._settings.connect('changed::' + KEY_CURRENT_INPUT_SOURCE, Lang.bind(this, this._currentInputSourceChanged));
209
343
        this._settings.connect('changed::' + KEY_INPUT_SOURCES, Lang.bind(this, this._inputSourcesChanged));
210
344
 
211
 
        this._currentSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
212
345
        this._xkbInfo = new GnomeDesktop.XkbInfo();
213
346
 
214
347
        this._propSeparator = new PopupMenu.PopupSeparatorMenuItem();
217
350
        this.menu.addMenuItem(this._propSection);
218
351
        this._propSection.actor.hide();
219
352
 
220
 
        this._properties = null;
221
 
 
222
 
        this._ibusManager = new IBusManager(Lang.bind(this, this._inputSourcesChanged));
 
353
        this._ibusReady = false;
 
354
        this._ibusManager = new IBusManager(Lang.bind(this, this._ibusReadyCallback));
223
355
        this._ibusManager.connect('properties-registered', Lang.bind(this, this._ibusPropertiesRegistered));
224
356
        this._ibusManager.connect('property-updated', Lang.bind(this, this._ibusPropertyUpdated));
225
357
        this._inputSourcesChanged();
230
362
        Main.sessionMode.connect('updated', Lang.bind(this, this._sessionUpdated));
231
363
        this._sessionUpdated();
232
364
 
233
 
        this.menu.addSettingsAction(_("Region and Language Settings"), 'gnome-region-panel.desktop');
 
365
        this.menu.addSettingsAction(_("Region & Language Settings"), 'gnome-region-panel.desktop');
 
366
 
 
367
        this._sourcesPerWindow = false;
 
368
        this._focusWindowNotifyId = 0;
 
369
        this._overviewShowingId = 0;
 
370
        this._overviewHiddenId = 0;
 
371
        this._settings.connect('changed::per-window', Lang.bind(this, this._sourcesPerWindowChanged));
 
372
        this._sourcesPerWindowChanged();
234
373
    },
235
374
 
236
375
    _sessionUpdated: function() {
241
380
        this._showLayoutItem.actor.visible = Main.sessionMode.allowSettings;
242
381
    },
243
382
 
 
383
    _ibusReadyCallback: function(ready) {
 
384
        if (this._ibusReady == ready)
 
385
            return;
 
386
 
 
387
        this._ibusReady = ready;
 
388
        this._mruSources = [];
 
389
        this._inputSourcesChanged();
 
390
    },
 
391
 
 
392
    _switchInputSource: function(display, screen, window, binding) {
 
393
        if (this._mruSources.length < 2)
 
394
            return;
 
395
 
 
396
        let popup = new InputSourcePopup(this._mruSources, this._keybindingAction, this._keybindingActionBackward);
 
397
        let modifiers = binding.get_modifiers();
 
398
        let backwards = modifiers & Meta.VirtualModifier.SHIFT_MASK;
 
399
        if (!popup.show(backwards, binding.get_name(), binding.get_mask()))
 
400
            popup.destroy();
 
401
    },
 
402
 
244
403
    _currentInputSourceChanged: function() {
245
 
        let nVisibleSources = Object.keys(this._layoutItems).length;
246
 
        let newCurrentSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
247
 
        let newLayoutItem = this._layoutItems[newCurrentSourceIndex];
248
 
        let hasProperties;
249
 
 
250
 
        if (newLayoutItem)
251
 
            hasProperties = this._ibusManager.hasProperties(newLayoutItem.ibusEngineId);
252
 
        else
253
 
            hasProperties = false;
254
 
 
255
 
        if (!newLayoutItem || (nVisibleSources < 2 && !hasProperties)) {
 
404
        let nVisibleSources = Object.keys(this._inputSources).length;
 
405
        let newSourceIndex = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
 
406
        let newSource = this._inputSources[newSourceIndex];
 
407
 
 
408
        let oldSource;
 
409
        [oldSource, this._currentSource] = [this._currentSource, newSource];
 
410
 
 
411
        if (oldSource) {
 
412
            oldSource.menuItem.setShowDot(false);
 
413
            oldSource.indicatorLabel.hide();
 
414
        }
 
415
 
 
416
        if (!newSource || (nVisibleSources < 2 && !newSource.properties)) {
256
417
            // This source index might be invalid if we weren't able
257
418
            // to build a menu item for it, so we hide ourselves since
258
419
            // we can't fix it here. *shrug*
266
427
 
267
428
        this.actor.show();
268
429
 
269
 
        if (this._layoutItems[this._currentSourceIndex]) {
270
 
            this._layoutItems[this._currentSourceIndex].setShowDot(false);
271
 
            this._container.set_skip_paint(this._labelActors[this._currentSourceIndex], true);
272
 
        }
273
 
 
274
 
        newLayoutItem.setShowDot(true);
275
 
 
276
 
        let newLabelActor = this._labelActors[newCurrentSourceIndex];
277
 
        this._container.set_skip_paint(newLabelActor, false);
278
 
 
279
 
        if (hasProperties)
280
 
            newLabelActor.set_text(newLayoutItem.indicator.get_text());
281
 
 
282
 
        this._currentSourceIndex = newCurrentSourceIndex;
 
430
        newSource.menuItem.setShowDot(true);
 
431
        newSource.indicatorLabel.show();
 
432
 
 
433
        this._buildPropSection(newSource.properties);
 
434
 
 
435
        for (let i = 1; i < this._mruSources.length; ++i)
 
436
            if (this._mruSources[i] == newSource) {
 
437
                let currentSource = this._mruSources.splice(i, 1);
 
438
                this._mruSources = currentSource.concat(this._mruSources);
 
439
                break;
 
440
            }
 
441
 
 
442
        this._changePerWindowSource();
283
443
    },
284
444
 
285
445
    _inputSourcesChanged: function() {
286
446
        let sources = this._settings.get_value(KEY_INPUT_SOURCES);
287
447
        let nSources = sources.n_children();
288
448
 
289
 
        for (let i in this._layoutItems)
290
 
            this._layoutItems[i].destroy();
291
 
 
292
 
        for (let i in this._labelActors)
293
 
            this._labelActors[i].destroy();
294
 
 
295
 
        this._layoutItems = {};
296
 
        this._labelActors = {};
297
 
 
298
 
        let infos = [];
299
 
        let infosByShortName = {};
 
449
        for (let i in this._inputSources)
 
450
            this._inputSources[i].destroy();
 
451
 
 
452
        this._inputSources = {};
 
453
        this._ibusSources = {};
 
454
 
 
455
        let inputSourcesByShortName = {};
300
456
 
301
457
        for (let i = 0; i < nSources; i++) {
302
 
            let info = { exists: false };
 
458
            let displayName;
 
459
            let shortName;
303
460
            let [type, id] = sources.get_child_value(i).deep_unpack();
 
461
            let exists = false;
304
462
 
305
463
            if (type == INPUT_SOURCE_TYPE_XKB) {
306
 
                [info.exists, info.displayName, info.shortName, , ] =
 
464
                [exists, displayName, shortName, , ] =
307
465
                    this._xkbInfo.get_layout_info(id);
308
466
            } else if (type == INPUT_SOURCE_TYPE_IBUS) {
309
467
                let engineDesc = this._ibusManager.getEngineDesc(id);
310
468
                if (engineDesc) {
311
469
                    let language = IBus.get_language_name(engineDesc.get_language());
312
 
 
313
 
                    info.exists = true;
314
 
                    info.displayName = language + ' (' + engineDesc.get_longname() + ')';
315
 
                    info.shortName = this._makeEngineShortName(engineDesc);
316
 
                    info.ibusEngineId = id;
 
470
                    exists = true;
 
471
                    displayName = language + ' (' + engineDesc.get_longname() + ')';
 
472
                    shortName = this._makeEngineShortName(engineDesc);
317
473
                }
318
474
            }
319
475
 
320
 
            if (!info.exists)
 
476
            if (!exists)
321
477
                continue;
322
478
 
323
 
            info.sourceIndex = i;
324
 
 
325
 
            if (!(info.shortName in infosByShortName))
326
 
                infosByShortName[info.shortName] = [];
327
 
            infosByShortName[info.shortName].push(info);
328
 
            infos.push(info);
329
 
        }
330
 
 
331
 
        for (let i = 0; i < infos.length; i++) {
332
 
            let info = infos[i];
333
 
            if (infosByShortName[info.shortName].length > 1) {
334
 
                let sub = infosByShortName[info.shortName].indexOf(info) + 1;
335
 
                info.shortName += String.fromCharCode(0x2080 + sub);
336
 
            }
337
 
 
338
 
            let item = new LayoutMenuItem(info.displayName, info.shortName);
339
 
            item.ibusEngineId = info.ibusEngineId;
340
 
            this._layoutItems[info.sourceIndex] = item;
341
 
            this.menu.addMenuItem(item, i);
342
 
            item.connect('activate', Lang.bind(this, function() {
 
479
            let is = new InputSource(type, id, displayName, shortName, i);
 
480
 
 
481
            is.connect('activate', Lang.bind(this, function() {
 
482
                if (this._currentSource && this._currentSource.index == is.index)
 
483
                    return;
343
484
                this._settings.set_value(KEY_CURRENT_INPUT_SOURCE,
344
 
                                         GLib.Variant.new_uint32(info.sourceIndex));
 
485
                                         GLib.Variant.new_uint32(is.index));
345
486
            }));
346
487
 
347
 
            let shortLabel = new St.Label({ text: info.shortName });
348
 
            this._labelActors[info.sourceIndex] = shortLabel;
349
 
            this._container.add_actor(shortLabel);
350
 
            this._container.set_skip_paint(shortLabel, true);
351
 
        }
 
488
            if (!(is.shortName in inputSourcesByShortName))
 
489
                inputSourcesByShortName[is.shortName] = [];
 
490
            inputSourcesByShortName[is.shortName].push(is);
 
491
 
 
492
            this._inputSources[is.index] = is;
 
493
 
 
494
            if (is.type == INPUT_SOURCE_TYPE_IBUS)
 
495
                this._ibusSources[is.id] = is;
 
496
        }
 
497
 
 
498
        let menuIndex = 0;
 
499
        for (let i in this._inputSources) {
 
500
            let is = this._inputSources[i];
 
501
            if (inputSourcesByShortName[is.shortName].length > 1) {
 
502
                let sub = inputSourcesByShortName[is.shortName].indexOf(is) + 1;
 
503
                is.shortName += String.fromCharCode(0x2080 + sub);
 
504
            }
 
505
 
 
506
            this.menu.addMenuItem(is.menuItem, menuIndex++);
 
507
 
 
508
            is.indicatorLabel.hide();
 
509
            this._container.add_actor(is.indicatorLabel);
 
510
        }
 
511
 
 
512
        let sourcesList = [];
 
513
        for (let i in this._inputSources)
 
514
            sourcesList.push(this._inputSources[i]);
 
515
 
 
516
        let mruSources = [];
 
517
        for (let i = 0; i < this._mruSources.length; i++) {
 
518
            for (let j = 0; j < sourcesList.length; j++)
 
519
                if (this._mruSources[i].type == sourcesList[j].type &&
 
520
                    this._mruSources[i].id == sourcesList[j].id) {
 
521
                    mruSources = mruSources.concat(sourcesList.splice(j, 1));
 
522
                    break;
 
523
                }
 
524
        }
 
525
        this._mruSources = mruSources.concat(sourcesList);
352
526
 
353
527
        this._currentInputSourceChanged();
354
528
    },
356
530
    _showLayout: function() {
357
531
        Main.overview.hide();
358
532
 
359
 
        let sources = this._settings.get_value(KEY_INPUT_SOURCES);
360
 
        let current = this._settings.get_uint(KEY_CURRENT_INPUT_SOURCE);
361
 
        let [type, id] = sources.get_child_value(current).deep_unpack();
 
533
        let source = this._currentSource;
362
534
        let xkbLayout = '';
363
535
        let xkbVariant = '';
364
536
 
365
 
        if (type == INPUT_SOURCE_TYPE_XKB) {
366
 
            [, , , xkbLayout, xkbVariant] = this._xkbInfo.get_layout_info(id);
367
 
        } else if (type == INPUT_SOURCE_TYPE_IBUS) {
368
 
            let engineDesc = this._ibusManager.getEngineDesc(id);
 
537
        if (source.type == INPUT_SOURCE_TYPE_XKB) {
 
538
            [, , , xkbLayout, xkbVariant] = this._xkbInfo.get_layout_info(source.id);
 
539
        } else if (source.type == INPUT_SOURCE_TYPE_IBUS) {
 
540
            let engineDesc = this._ibusManager.getEngineDesc(source.id);
369
541
            if (engineDesc) {
370
542
                xkbLayout = engineDesc.get_layout();
371
543
                xkbVariant = '';
394
566
        return String.fromCharCode(0x2328); // keyboard glyph
395
567
    },
396
568
 
397
 
    _propertyWhitelisted: function(prop) {
398
 
        for (let i = 0; i < this._propertiesWhitelist.length; ++i) {
399
 
            let key = prop.get_key();
400
 
            if (key.substr(0, this._propertiesWhitelist[i].length) == this._propertiesWhitelist[i])
401
 
                return true;
402
 
        }
403
 
        return false;
404
 
    },
405
 
 
406
 
    _ibusPropertiesRegistered: function(im, props) {
407
 
        this._properties = props;
408
 
        this._buildPropSection();
409
 
    },
410
 
 
411
 
    _ibusPropertyUpdated: function(im, prop) {
412
 
        if (!this._propertyWhitelisted(prop))
413
 
            return;
414
 
 
415
 
        if (this._updateSubProperty(this._properties, prop))
416
 
            this._buildPropSection();
 
569
    _ibusPropertiesRegistered: function(im, engineName, props) {
 
570
        let source = this._ibusSources[engineName];
 
571
        if (!source)
 
572
            return;
 
573
 
 
574
        source.properties = props;
 
575
 
 
576
        if (source == this._currentSource)
 
577
            this._currentInputSourceChanged();
 
578
    },
 
579
 
 
580
    _ibusPropertyUpdated: function(im, engineName, prop) {
 
581
        let source = this._ibusSources[engineName];
 
582
        if (!source)
 
583
            return;
 
584
 
 
585
        if (this._updateSubProperty(source.properties, prop) &&
 
586
            source == this._currentSource)
 
587
            this._currentInputSourceChanged();
417
588
    },
418
589
 
419
590
    _updateSubProperty: function(props, prop) {
433
604
        return false;
434
605
    },
435
606
 
436
 
    _updateIndicatorLabel: function(text) {
437
 
        let layoutItem = this._layoutItems[this._currentSourceIndex];
438
 
        let hasProperties;
439
 
 
440
 
        if (layoutItem)
441
 
            hasProperties = this._ibusManager.hasProperties(layoutItem.ibusEngineId);
442
 
        else
443
 
            hasProperties = false;
444
 
 
445
 
        if (hasProperties)
446
 
            this._labelActors[this._currentSourceIndex].set_text(text);
447
 
    },
448
 
 
449
 
    _buildPropSection: function() {
 
607
    _buildPropSection: function(properties) {
450
608
        this._propSeparator.actor.hide();
451
609
        this._propSection.actor.hide();
452
610
        this._propSection.removeAll();
453
611
 
454
 
        this._buildPropSubMenu(this._propSection, this._properties);
 
612
        this._buildPropSubMenu(this._propSection, properties);
455
613
 
456
614
        if (!this._propSection.isEmpty()) {
457
615
            this._propSection.actor.show();
468
626
        for (let i = 0; (p = props.get(i)) != null; ++i) {
469
627
            let prop = p;
470
628
 
471
 
            if (!this._propertyWhitelisted(prop) ||
472
 
                !prop.get_visible())
 
629
            if (!prop.get_visible())
473
630
                continue;
474
631
 
475
632
            if (prop.get_key() == 'InputMode') {
480
637
                    text = prop.get_label().get_text();
481
638
 
482
639
                if (text && text.length > 0 && text.length < 3)
483
 
                    this._updateIndicatorLabel(text);
 
640
                    this._currentSource.indicatorLabel.set_text(text);
484
641
            }
485
642
 
486
643
            let item;
487
 
            let type = prop.get_prop_type();
488
 
            if (type == IBus.PropType.MENU) {
 
644
            switch (prop.get_prop_type()) {
 
645
            case IBus.PropType.MENU:
489
646
                item = new PopupMenu.PopupSubMenuMenuItem(prop.get_label().get_text());
490
647
                this._buildPropSubMenu(item.menu, prop.get_sub_props());
491
 
            } else if (type == IBus.PropType.RADIO) {
 
648
                break;
 
649
 
 
650
            case IBus.PropType.RADIO:
492
651
                item = new PopupMenu.PopupMenuItem(prop.get_label().get_text());
493
652
                item.prop = prop;
494
653
                radioGroup.push(item);
513
672
                        }
514
673
                    }
515
674
                }));
516
 
            } else {
 
675
                break;
 
676
 
 
677
            case IBus.PropType.TOGGLE:
 
678
                item = new PopupMenu.PopupSwitchMenuItem(prop.get_label().get_text(), prop.get_state() == IBus.PropState.CHECKED);
 
679
                item.prop = prop;
 
680
                item.connect('toggled', Lang.bind(this, function() {
 
681
                    if (item.state) {
 
682
                        item.prop.set_state(IBus.PropState.CHECKED);
 
683
                        this._ibusManager.activateProperty(item.prop.get_key(),
 
684
                                                           IBus.PropState.CHECKED);
 
685
                    } else {
 
686
                        item.prop.set_state(IBus.PropState.UNCHECKED);
 
687
                        this._ibusManager.activateProperty(item.prop.get_key(),
 
688
                                                           IBus.PropState.UNCHECKED);
 
689
                    }
 
690
                }));
 
691
                break;
 
692
 
 
693
            case IBus.PropType.NORMAL:
 
694
                item = new PopupMenu.PopupMenuItem(prop.get_label().get_text());
 
695
                item.prop = prop;
 
696
                item.connect('activate', Lang.bind(this, function() {
 
697
                    this._ibusManager.activateProperty(item.prop.get_key(),
 
698
                                                       item.prop.get_state());
 
699
                }));
 
700
                break;
 
701
 
 
702
            case IBus.PropType.SEPARATOR:
 
703
                item = new PopupMenu.PopupSeparatorMenuItem();
 
704
                break;
 
705
 
 
706
            default:
 
707
                log ('IBus property %s has invalid type %d'.format(prop.get_key(), type));
517
708
                continue;
518
709
            }
519
710
 
522
713
        }
523
714
    },
524
715
 
 
716
    _getNewInputSource: function(current) {
 
717
        for (let i in this._inputSources) {
 
718
            let is = this._inputSources[i];
 
719
            if (is.type == current.type &&
 
720
                is.id == current.id)
 
721
                return is;
 
722
        }
 
723
        return this._currentSource;
 
724
    },
 
725
 
 
726
    _getCurrentWindow: function() {
 
727
        if (Main.overview.visible)
 
728
            return Main.overview;
 
729
        else
 
730
            return global.display.focus_window;
 
731
    },
 
732
 
 
733
    _setPerWindowInputSource: function() {
 
734
        let window = this._getCurrentWindow();
 
735
        if (!window)
 
736
            return;
 
737
 
 
738
        if (!window._inputSources) {
 
739
            window._inputSources = this._inputSources;
 
740
            window._currentSource = this._currentSource;
 
741
        } else if (window._inputSources == this._inputSources) {
 
742
            window._currentSource.activate();
 
743
        } else {
 
744
            window._inputSources = this._inputSources;
 
745
            window._currentSource = this._getNewInputSource(window._currentSource);
 
746
            window._currentSource.activate();
 
747
        }
 
748
    },
 
749
 
 
750
    _sourcesPerWindowChanged: function() {
 
751
        this._sourcesPerWindow = this._settings.get_boolean('per-window');
 
752
 
 
753
        if (this._sourcesPerWindow && this._focusWindowNotifyId == 0) {
 
754
            this._focusWindowNotifyId = global.display.connect('notify::focus-window',
 
755
                                                               Lang.bind(this, this._setPerWindowInputSource));
 
756
            this._overviewShowingId = Main.overview.connect('showing',
 
757
                                                            Lang.bind(this, this._setPerWindowInputSource));
 
758
            this._overviewHiddenId = Main.overview.connect('hidden',
 
759
                                                           Lang.bind(this, this._setPerWindowInputSource));
 
760
        } else if (!this._sourcesPerWindow && this._focusWindowNotifyId != 0) {
 
761
            global.display.disconnect(this._focusWindowNotifyId);
 
762
            this._focusWindowNotifyId = 0;
 
763
            Main.overview.disconnect(this._overviewShowingId);
 
764
            this._overviewShowingId = 0;
 
765
            Main.overview.disconnect(this._overviewHiddenId);
 
766
            this._overviewHiddenId = 0;
 
767
 
 
768
            let windows = global.get_window_actors().map(function(w) {
 
769
                return w.meta_window;
 
770
            });
 
771
            for (let i = 0; i < windows.length; ++i) {
 
772
                delete windows[i]._inputSources;
 
773
                delete windows[i]._currentSource;
 
774
            }
 
775
            delete Main.overview._inputSources;
 
776
            delete Main.overview._currentSource;
 
777
        }
 
778
    },
 
779
 
 
780
    _changePerWindowSource: function() {
 
781
        if (!this._sourcesPerWindow)
 
782
            return;
 
783
 
 
784
        let window = this._getCurrentWindow();
 
785
        if (!window)
 
786
            return;
 
787
 
 
788
        window._inputSources = this._inputSources;
 
789
        window._currentSource = this._currentSource;
 
790
    },
 
791
 
525
792
    _containerGetPreferredWidth: function(container, for_height, alloc) {
526
793
        // Here, and in _containerGetPreferredHeight, we need to query
527
794
        // for the height of all children, but we ignore the results
528
795
        // for those we don't actually display.
529
796
        let max_min_width = 0, max_natural_width = 0;
530
797
 
531
 
        for (let i in this._labelActors) {
532
 
            let [min_width, natural_width] = this._labelActors[i].get_preferred_width(for_height);
 
798
        for (let i in this._inputSources) {
 
799
            let is = this._inputSources[i];
 
800
            let [min_width, natural_width] = is.indicatorLabel.get_preferred_width(for_height);
533
801
            max_min_width = Math.max(max_min_width, min_width);
534
802
            max_natural_width = Math.max(max_natural_width, natural_width);
535
803
        }
541
809
    _containerGetPreferredHeight: function(container, for_width, alloc) {
542
810
        let max_min_height = 0, max_natural_height = 0;
543
811
 
544
 
        for (let i in this._labelActors) {
545
 
            let [min_height, natural_height] = this._labelActors[i].get_preferred_height(for_width);
 
812
        for (let i in this._inputSources) {
 
813
            let is = this._inputSources[i];
 
814
            let [min_height, natural_height] = is.indicatorLabel.get_preferred_height(for_width);
546
815
            max_min_height = Math.max(max_min_height, min_height);
547
816
            max_natural_height = Math.max(max_natural_height, natural_height);
548
817
        }
558
827
        box.y2 -= box.y1;
559
828
        box.y1 = 0;
560
829
 
561
 
        for (let i in this._labelActors)
562
 
            this._labelActors[i].allocate_align_fill(box, 0.5, 0, false, false, flags);
 
830
        for (let i in this._inputSources) {
 
831
            let is = this._inputSources[i];
 
832
            is.indicatorLabel.allocate_align_fill(box, 0.5, 0, false, false, flags);
 
833
        }
563
834
    }
564
835
});