~darkxst/ubuntu/quantal/gnome-shell/lp1128804

« back to all changes in this revision

Viewing changes to js/ui/grabHelper.js

  • Committer: Package Import Robot
  • Author(s): Tim Lunn
  • Date: 2012-10-09 20:42:33 UTC
  • mfrom: (57.1.7 quantal)
  • Revision ID: package-import@ubuntu.com-20121009204233-chcl8989muuzfpws
Tags: 3.6.0-0ubuntu3
* debian/patches/ubuntu-lightdm-user-switching.patch
  - Fix user switching when running lightdm.  LP: #1064269
 

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
 
2
 
 
3
const Clutter = imports.gi.Clutter;
 
4
const Gtk = imports.gi.Gtk;
 
5
const Lang = imports.lang;
 
6
const Meta = imports.gi.Meta;
 
7
const Shell = imports.gi.Shell;
 
8
const St = imports.gi.St;
 
9
 
 
10
const Main = imports.ui.main;
 
11
const Params = imports.misc.params;
 
12
 
 
13
function _navigateActor(actor) {
 
14
    if (!actor)
 
15
        return;
 
16
 
 
17
    let needsGrab = true;
 
18
    if (actor instanceof St.Widget)
 
19
        needsGrab = !actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
 
20
    if (needsGrab)
 
21
        actor.grab_key_focus();
 
22
}
 
23
 
 
24
// GrabHelper:
 
25
// @owner: the actor that owns the GrabHelper
 
26
//
 
27
// Creates a new GrabHelper object, for dealing with keyboard and pointer grabs
 
28
// associated with a set of actors.
 
29
//
 
30
// Note that the grab can be automatically dropped at any time by the user, and
 
31
// your code just needs to deal with it; you shouldn't adjust behavior directly
 
32
// after you call ungrab(), but instead pass an 'onUngrab' callback when you
 
33
// call grab().
 
34
const GrabHelper = new Lang.Class({
 
35
    Name: 'GrabHelper',
 
36
 
 
37
    _init: function(owner) {
 
38
        this._owner = owner;
 
39
 
 
40
        this._grabStack = [];
 
41
 
 
42
        this._actors = [];
 
43
        this._capturedEventId = 0;
 
44
        this._eventId = 0;
 
45
        this._keyFocusNotifyId = 0;
 
46
        this._focusWindowChangedId = 0;
 
47
        this._ignoreRelease = false;
 
48
 
 
49
        this._modalCount = 0;
 
50
        this._grabFocusCount = 0;
 
51
    },
 
52
 
 
53
    // addActor:
 
54
    // @actor: an actor
 
55
    //
 
56
    // Adds @actor to the set of actors that are allowed to process events
 
57
    // during a grab.
 
58
    addActor: function(actor) {
 
59
        actor.__grabHelperDestroyId = actor.connect('destroy', Lang.bind(this, function() { this.removeActor(actor); }));
 
60
        this._actors.push(actor);
 
61
    },
 
62
 
 
63
    // removeActor:
 
64
    // @actor: an actor
 
65
    //
 
66
    // Removes @actor from the set of actors that are allowed to
 
67
    // process events during a grab.
 
68
    removeActor: function(actor) {
 
69
        let index = this._actors.indexOf(actor);
 
70
        if (index != -1)
 
71
            this._actors.splice(index, 1);
 
72
        if (actor.__grabHelperDestroyId) {
 
73
            actor.disconnect(actor.__grabHelperDestroyId);
 
74
            delete actor.__grabHelperDestroyId;
 
75
        }
 
76
    },
 
77
 
 
78
    _isWithinGrabbedActor: function(actor) {
 
79
        while (actor) {
 
80
            if (this._actors.indexOf(actor) != -1)
 
81
                return true;
 
82
            actor = actor.get_parent();
 
83
        }
 
84
        return false;
 
85
    },
 
86
 
 
87
    get currentGrab() {
 
88
        return this._grabStack[this._grabStack.length - 1] || {};
 
89
    },
 
90
 
 
91
    _findStackIndex: function(actor) {
 
92
        if (!actor)
 
93
            return -1;
 
94
 
 
95
        for (let i = 0; i < this._grabStack.length; i++) {
 
96
            if (this._grabStack[i].actor === actor)
 
97
                return i;
 
98
        }
 
99
        return -1;
 
100
    },
 
101
 
 
102
    isActorGrabbed: function(actor) {
 
103
        return this._findStackIndex(actor) >= 0;
 
104
    },
 
105
 
 
106
    // grab:
 
107
    // @params: A bunch of parameters, see below
 
108
    //
 
109
    // Grabs the mouse and keyboard, according to the GrabHelper's
 
110
    // parameters. If @newFocus is not %null, then the keyboard focus
 
111
    // is moved to the first #StWidget:can-focus widget inside it.
 
112
    //
 
113
    // The grab will automatically be dropped if:
 
114
    //   - The user clicks outside the grabbed actors
 
115
    //   - The user types Escape
 
116
    //   - The keyboard focus is moved outside the grabbed actors
 
117
    //   - A window is focused
 
118
    //
 
119
    // If @params.actor is not null, then it will be focused as the
 
120
    // new actor. If you attempt to grab an already focused actor, the
 
121
    // request to be focused will be ignored. The actor will not be
 
122
    // added to the grab stack, so do not call a paired ungrab().
 
123
    //
 
124
    // If @params contains { modal: true }, then grab() will push a modal
 
125
    // on the owner of the GrabHelper. As long as there is at least one
 
126
    // { modal: true } actor on the grab stack, the grab will be kept.
 
127
    // When the last { modal: true } actor is ungrabbed, then the modal
 
128
    // will be dropped. A modal grab can fail if there is already a grab
 
129
    // in effect from aother application; in this case the function returns
 
130
    // false and nothing happens. Non-modal grabs can never fail.
 
131
    //
 
132
    // If @params contains { grabFocus: true }, then if you call grab()
 
133
    // while the shell is outside the overview, it will set the stage
 
134
    // input mode to %Shell.StageInputMode.FOCUSED, and ungrab() will
 
135
    // revert it back, and re-focus the previously-focused window (if
 
136
    // another window hasn't been explicitly focused before then).
 
137
    grab: function(params) {
 
138
        params = Params.parse(params, { actor: null,
 
139
                                        modal: false,
 
140
                                        grabFocus: false,
 
141
                                        onUngrab: null });
 
142
 
 
143
        let focus = global.stage.key_focus;
 
144
        let hadFocus = focus && this._isWithinGrabbedActor(focus);
 
145
        let newFocus = params.actor;
 
146
 
 
147
        if (this.isActorGrabbed(params.actor))
 
148
            return true;
 
149
 
 
150
        params.savedFocus = focus;
 
151
 
 
152
        if (params.modal && !this._takeModalGrab())
 
153
            return false;
 
154
 
 
155
        if (params.grabFocus && !this._takeFocusGrab(hadFocus))
 
156
            return false;
 
157
 
 
158
        if (hadFocus || params.grabFocus)
 
159
            _navigateActor(newFocus);
 
160
 
 
161
        this._grabStack.push(params);
 
162
        return true;
 
163
    },
 
164
 
 
165
    _takeModalGrab: function() {
 
166
        let firstGrab = (this._modalCount == 0);
 
167
        if (firstGrab) {
 
168
            if (!Main.pushModal(this._owner))
 
169
                return false;
 
170
 
 
171
            this._capturedEventId = global.stage.connect('captured-event', Lang.bind(this, this._onCapturedEvent));
 
172
            this._eventId = global.stage.connect('event', Lang.bind(this, this._onEvent));
 
173
        }
 
174
 
 
175
        this._modalCount++;
 
176
        return true;
 
177
    },
 
178
 
 
179
    _releaseModalGrab: function() {
 
180
        this._modalCount--;
 
181
        if (this._modalCount > 0)
 
182
            return;
 
183
 
 
184
        if (this._capturedEventId > 0) {
 
185
            global.stage.disconnect(this._capturedEventId);
 
186
            this._capturedEventId = 0;
 
187
        }
 
188
 
 
189
        if (this._eventId > 0) {
 
190
            global.stage.disconnect(this._eventId);
 
191
            this._eventId = 0;
 
192
        }
 
193
 
 
194
        Main.popModal(this._owner);
 
195
        global.sync_pointer();
 
196
    },
 
197
 
 
198
    _takeFocusGrab: function(hadFocus) {
 
199
        let firstGrab = (this._grabFocusCount == 0);
 
200
        if (firstGrab) {
 
201
            let metaDisplay = global.screen.get_display();
 
202
 
 
203
            this._grabbedFromKeynav = hadFocus;
 
204
            this._preGrabInputMode = global.stage_input_mode;
 
205
            this._prevFocusedWindow = metaDisplay.focus_window;
 
206
 
 
207
            if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
 
208
                this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
 
209
                global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
 
210
            }
 
211
 
 
212
            this._keyFocusNotifyId = global.stage.connect('notify::key-focus', Lang.bind(this, this._onKeyFocusChanged));
 
213
            this._focusWindowChangedId = metaDisplay.connect('notify::focus-window', Lang.bind(this, this._focusWindowChanged));
 
214
        }
 
215
 
 
216
        this._grabFocusCount++;
 
217
        return true;
 
218
    },
 
219
 
 
220
    _releaseFocusGrab: function() {
 
221
        this._grabFocusCount--;
 
222
        if (this._grabFocusCount > 0)
 
223
            return;
 
224
 
 
225
        if (this._keyFocusNotifyId > 0) {
 
226
            global.stage.disconnect(this._keyFocusNotifyId);
 
227
            this._keyFocusNotifyId = 0;
 
228
        }
 
229
 
 
230
        if (!this._focusWindowChanged > 0) {
 
231
            let metaDisplay = global.screen.get_display();
 
232
            metaDisplay.disconnect(this._focusWindowChangedId);
 
233
            this._focusWindowChangedId = 0;
 
234
        }
 
235
 
 
236
        let prePopInputMode = global.stage_input_mode;
 
237
 
 
238
        if (this._grabbedFromKeynav) {
 
239
            if (this._preGrabInputMode == Shell.StageInputMode.FOCUSED &&
 
240
                prePopInputMode != Shell.StageInputMode.FULLSCREEN)
 
241
                global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
 
242
        }
 
243
 
 
244
        if (this._prevFocusedWindow) {
 
245
            let metaDisplay = global.screen.get_display();
 
246
            if (!metaDisplay.focus_window) {
 
247
                metaDisplay.set_input_focus_window(this._prevFocusedWindow,
 
248
                                                   false, global.get_current_time());
 
249
            }
 
250
        }
 
251
    },
 
252
 
 
253
    // ignoreRelease:
 
254
    //
 
255
    // Make sure that the next button release event evaluated by the
 
256
    // capture event handler returns false. This is designed for things
 
257
    // like the ComboBoxMenu that go away on press, but need to eat
 
258
    // the next release event.
 
259
    ignoreRelease: function() {
 
260
        this._ignoreRelease = true;
 
261
    },
 
262
 
 
263
    // ungrab:
 
264
    // @params: The parameters for the grab; see below.
 
265
    //
 
266
    // Pops an actor from the grab stack, potentially dropping the grab.
 
267
    //
 
268
    // If the actor that was popped from the grab stack was not the actor
 
269
    // That was passed in, this call is ignored.
 
270
    ungrab: function(params) {
 
271
        params = Params.parse(params, { actor: this.currentGrab.actor });
 
272
 
 
273
        let grabStackIndex = this._findStackIndex(params.actor);
 
274
        if (grabStackIndex < 0)
 
275
            return;
 
276
 
 
277
        let focus = global.stage.key_focus;
 
278
        let hadFocus = focus && this._isWithinGrabbedActor(focus);
 
279
 
 
280
        let poppedGrabs = this._grabStack.slice(grabStackIndex);
 
281
        // "Pop" all newly ungrabbed actors off the grab stack
 
282
        // by truncating the array.
 
283
        this._grabStack.length = grabStackIndex;
 
284
 
 
285
        for (let i = poppedGrabs.length - 1; i >= 0; i--) {
 
286
            let poppedGrab = poppedGrabs[i];
 
287
 
 
288
            if (poppedGrab.onUngrab)
 
289
                poppedGrab.onUngrab();
 
290
 
 
291
            if (poppedGrab.modal)
 
292
                this._releaseModalGrab();
 
293
 
 
294
            if (poppedGrab.grabFocus)
 
295
                this._releaseFocusGrab();
 
296
        }
 
297
 
 
298
        if (hadFocus) {
 
299
            let poppedGrab = poppedGrabs[0];
 
300
            _navigateActor(poppedGrab.savedFocus);
 
301
        }
 
302
    },
 
303
 
 
304
    _onCapturedEvent: function(actor, event) {
 
305
        let type = event.type();
 
306
        let press = type == Clutter.EventType.BUTTON_PRESS;
 
307
        let release = type == Clutter.EventType.BUTTON_RELEASE;
 
308
        let button = press || release;
 
309
 
 
310
        if (release && this._ignoreRelease) {
 
311
            this._ignoreRelease = false;
 
312
            return false;
 
313
        }
 
314
 
 
315
        if (!button && this._modalCount == 0)
 
316
            return false;
 
317
 
 
318
        if (this._isWithinGrabbedActor(event.get_source()))
 
319
            return false;
 
320
 
 
321
        if (Main.keyboard.shouldTakeEvent(event))
 
322
            return false;
 
323
 
 
324
        if (button) {
 
325
            // If we have a press event, ignore the next event,
 
326
            // which should be a release event.
 
327
            if (press)
 
328
                this._ignoreRelease = true;
 
329
            this.ungrab({ actor: this._grabStack[0].actor });
 
330
        }
 
331
 
 
332
        return this._modalCount > 0;
 
333
    },
 
334
 
 
335
    // We catch 'event' rather than 'key-press-event' so that we get
 
336
    // a chance to run before the overview's own Escape check
 
337
    _onEvent: function(actor, event) {
 
338
        if (event.type() == Clutter.EventType.KEY_PRESS &&
 
339
            event.get_key_symbol() == Clutter.KEY_Escape) {
 
340
            this.ungrab();
 
341
            return true;
 
342
        }
 
343
 
 
344
        return false;
 
345
    },
 
346
 
 
347
    _onKeyFocusChanged: function() {
 
348
        let focus = global.stage.key_focus;
 
349
        if (!focus || !this._isWithinGrabbedActor(focus))
 
350
            this.ungrab();
 
351
    },
 
352
 
 
353
    _focusWindowChanged: function() {
 
354
        let metaDisplay = global.screen.get_display();
 
355
        if (metaDisplay.focus_window != null)
 
356
            this.ungrab();
 
357
    }
 
358
});