1
/* -*- mode: js2; js2-basic-offset: 4; indent-tabs-mode: nil -*- */
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;
10
const Main = imports.ui.main;
11
const Params = imports.misc.params;
13
function _navigateActor(actor) {
18
if (actor instanceof St.Widget)
19
needsGrab = !actor.navigate_focus(null, Gtk.DirectionType.TAB_FORWARD, false);
21
actor.grab_key_focus();
25
// @owner: the actor that owns the GrabHelper
27
// Creates a new GrabHelper object, for dealing with keyboard and pointer grabs
28
// associated with a set of actors.
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
34
const GrabHelper = new Lang.Class({
37
_init: function(owner) {
43
this._capturedEventId = 0;
45
this._keyFocusNotifyId = 0;
46
this._focusWindowChangedId = 0;
47
this._ignoreRelease = false;
50
this._grabFocusCount = 0;
56
// Adds @actor to the set of actors that are allowed to process events
58
addActor: function(actor) {
59
actor.__grabHelperDestroyId = actor.connect('destroy', Lang.bind(this, function() { this.removeActor(actor); }));
60
this._actors.push(actor);
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);
71
this._actors.splice(index, 1);
72
if (actor.__grabHelperDestroyId) {
73
actor.disconnect(actor.__grabHelperDestroyId);
74
delete actor.__grabHelperDestroyId;
78
_isWithinGrabbedActor: function(actor) {
80
if (this._actors.indexOf(actor) != -1)
82
actor = actor.get_parent();
88
return this._grabStack[this._grabStack.length - 1] || {};
91
_findStackIndex: function(actor) {
95
for (let i = 0; i < this._grabStack.length; i++) {
96
if (this._grabStack[i].actor === actor)
102
isActorGrabbed: function(actor) {
103
return this._findStackIndex(actor) >= 0;
107
// @params: A bunch of parameters, see below
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.
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
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().
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.
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,
143
let focus = global.stage.key_focus;
144
let hadFocus = focus && this._isWithinGrabbedActor(focus);
145
let newFocus = params.actor;
147
if (this.isActorGrabbed(params.actor))
150
params.savedFocus = focus;
152
if (params.modal && !this._takeModalGrab())
155
if (params.grabFocus && !this._takeFocusGrab(hadFocus))
158
if (hadFocus || params.grabFocus)
159
_navigateActor(newFocus);
161
this._grabStack.push(params);
165
_takeModalGrab: function() {
166
let firstGrab = (this._modalCount == 0);
168
if (!Main.pushModal(this._owner))
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));
179
_releaseModalGrab: function() {
181
if (this._modalCount > 0)
184
if (this._capturedEventId > 0) {
185
global.stage.disconnect(this._capturedEventId);
186
this._capturedEventId = 0;
189
if (this._eventId > 0) {
190
global.stage.disconnect(this._eventId);
194
Main.popModal(this._owner);
195
global.sync_pointer();
198
_takeFocusGrab: function(hadFocus) {
199
let firstGrab = (this._grabFocusCount == 0);
201
let metaDisplay = global.screen.get_display();
203
this._grabbedFromKeynav = hadFocus;
204
this._preGrabInputMode = global.stage_input_mode;
205
this._prevFocusedWindow = metaDisplay.focus_window;
207
if (this._preGrabInputMode == Shell.StageInputMode.NONREACTIVE ||
208
this._preGrabInputMode == Shell.StageInputMode.NORMAL) {
209
global.set_stage_input_mode(Shell.StageInputMode.FOCUSED);
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));
216
this._grabFocusCount++;
220
_releaseFocusGrab: function() {
221
this._grabFocusCount--;
222
if (this._grabFocusCount > 0)
225
if (this._keyFocusNotifyId > 0) {
226
global.stage.disconnect(this._keyFocusNotifyId);
227
this._keyFocusNotifyId = 0;
230
if (!this._focusWindowChanged > 0) {
231
let metaDisplay = global.screen.get_display();
232
metaDisplay.disconnect(this._focusWindowChangedId);
233
this._focusWindowChangedId = 0;
236
let prePopInputMode = global.stage_input_mode;
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);
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());
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;
264
// @params: The parameters for the grab; see below.
266
// Pops an actor from the grab stack, potentially dropping the grab.
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 });
273
let grabStackIndex = this._findStackIndex(params.actor);
274
if (grabStackIndex < 0)
277
let focus = global.stage.key_focus;
278
let hadFocus = focus && this._isWithinGrabbedActor(focus);
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;
285
for (let i = poppedGrabs.length - 1; i >= 0; i--) {
286
let poppedGrab = poppedGrabs[i];
288
if (poppedGrab.onUngrab)
289
poppedGrab.onUngrab();
291
if (poppedGrab.modal)
292
this._releaseModalGrab();
294
if (poppedGrab.grabFocus)
295
this._releaseFocusGrab();
299
let poppedGrab = poppedGrabs[0];
300
_navigateActor(poppedGrab.savedFocus);
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;
310
if (release && this._ignoreRelease) {
311
this._ignoreRelease = false;
315
if (!button && this._modalCount == 0)
318
if (this._isWithinGrabbedActor(event.get_source()))
321
if (Main.keyboard.shouldTakeEvent(event))
325
// If we have a press event, ignore the next event,
326
// which should be a release event.
328
this._ignoreRelease = true;
329
this.ungrab({ actor: this._grabStack[0].actor });
332
return this._modalCount > 0;
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) {
347
_onKeyFocusChanged: function() {
348
let focus = global.stage.key_focus;
349
if (!focus || !this._isWithinGrabbedActor(focus))
353
_focusWindowChanged: function() {
354
let metaDisplay = global.screen.get_display();
355
if (metaDisplay.focus_window != null)