Merge lp:~unity-team/unity8/physical-key-mapper into lp:unity8

Proposed by Michał Sawicz on 2015-02-16
Status: Merged
Approved by: Albert Astals Cid on 2015-02-17
Approved revision: 1519
Merged at revision: 1644
Proposed branch: lp:~unity-team/unity8/physical-key-mapper
Merge into: lp:unity8
Diff against target: 437 lines (+248/-125)
6 files modified
qml/Components/Dialogs.qml (+4/-24)
qml/Components/PhysicalKeysMapper.qml (+113/-0)
qml/Components/VolumeKeyFilter.qml (+0/-63)
qml/Shell.qml (+11/-38)
tests/qmltests/CMakeLists.txt (+1/-0)
tests/qmltests/Components/tst_PhysicalKeysMapper.qml (+119/-0)
To merge this branch: bzr merge lp:~unity-team/unity8/physical-key-mapper
Reviewer Review Type Date Requested Status
PS Jenkins bot continuous-integration 2015-02-16 Needs Fixing on 2015-02-17
Albert Astals Cid (community) 2015-02-16 Approve on 2015-02-17
Daniel d'Andrada codewise 2015-02-16 Pending
kevin gunn testing 2015-02-16 Pending
Michał Sawicz ux Pending
Review via email: mp+249890@code.launchpad.net

This proposal supersedes a proposal from 2014-12-10.

Commit Message

Create a PhysicalKeyMapper to handle all physical/hardware keys

Description of the Change

* Are there any related MPs required for this MP to build/function as expected? Please list.
No

 * Did you perform an exploratory manual test run of your code change and any related functionality?

Yes

 * Did you make sure that your branch does not contain spurious tags?

Yes

 * If you changed the packaging (debian), did you subscribe the ubuntu-unity team to this MP?

N/A

 * If you changed the UI, has there been a design review?

No, should there be?

Create a PhysicalKeyFilter to handle all physical/hardware keys on a mobile device. In addition, change the key combination for taking a screenshot from (Volume Up + Volume Down) to (Power + Volume Down).

--fixes lp:1390393

To post a comment you must log in.
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

The else ifs need to be written in our style

} else if (volumeDownKeyPressed) {

instead of

}
else if (volumeDownKeyPressed) {

review: Needs Fixing
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Something is wrong with the power button, after i press it to bring the phone back from sleep i always end up getting the shutdown dialog.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

8 + * Authors:
9 + * Josh Arenson <email address hidden>

Better leave that out as this info gets quickly outdated overtime, as the code gets modified. bzr/git history does a better job at that.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Please follow the MP submission checklist regarding the commit message:
https://wiki.ubuntu.com/Process/Merges/Checklists/Unity8

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

> Please follow the MP submission checklist regarding the commit message:
> https://wiki.ubuntu.com/Process/Merges/Checklists/Unity8

Also the "--fixes lp:1390393" line is not necessary as CI will automatically add it when merging the branch.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

If you're really going to make PhysicalKeysFilter also handle higher level things such as key mappings as you're doing for the screenshot, you should also absorb the code from Dialogs.qml that handles long power key press detection.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

> If you're really going to make PhysicalKeysFilter also handle higher level
> things such as key mappings as you're doing for the screenshot, you should
> also absorb the code from Dialogs.qml that handles long power key press
> detection.

In that tone, I might be better to call it a key mapper instead of a key filter.

Josh Arenson (josharenson) wrote : Posted in a previous version of this proposal

> Something is wrong with the power button, after i press it to bring the phone
> back from sleep i always end up getting the shutdown dialog.

I will look into this, but it sounds like lp:1383277

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In qml/Shell.qml

"""
    Dialogs {
        ...
        Connections {
            target: physicalKeysMapper
            onPowerKeyLongPress: dialogs.showPowerDialog();
        }
        ...
    }
"""

Why not put the onPowerKeyLongPress line directly into PhysicalKeysMapper, like it's done for all other connections?

-----------------

In qml/Components/Dialogs.qml

"""
    property bool dialogShown: false
"""

That should still be inside a QtObject { id: d } wrapper as it's still a private property (ie, not meant to be seem or used outside Dialogs.qml)

--------------------

In qml/Components/PhysicalKeysMapper.qml

"""
    property bool eventAccepted: false

    ...

    function onKeyPressed(key) {
        ...
            eventAccepted = true;
        ...
            eventAccepted = true;
        ...
            eventAccepted = true;
        ...
        return eventAccepted;
    }

    function onKeyReleased(key) {
        ...
            eventAccepted = true;
        ...
            eventAccepted = true;
        ...
            eventAccepted = true;
        ...
        return inadvertently;
    }

"""

You init eventAccepted to false but once it's been set to true it will never come back to false again.
Meaning that you're inadvertently telling WindowKeysFilter to filter out *all* key events, so no application will ever get a physical key event again. This is a bug.

eventAccepted should not be a property but a internal variable of those funtioncs, like this:

"""
    function onKeyPressed(key) {
        var eventAccepted = false;
        ...
            eventAccepted = true;
        ...
            eventAccepted = true;
        ...
            eventAccepted = true;
        ...
        return eventAccepted;
    }
"""

--------------------

In qml/Components/PhysicalKeysMapper.qml

"""
        } else if (key == Qt.Key_VolumeDown) {
            eventAccepted = true;
            volumeDownKeyPressed = true;
        } else if (key == Qt.Key_VolumeUp) {
            eventAccepted = true;
            volumeUpKeyPressed = true;
        }
"""

Volume keys should *not* be accepted. Check the original code in Shell.qml.
If you accept them you're telling WindowKeysFilter to filter them out, so they won't get dispatched to the focused item in the qml scene (eg: the MirSurfaceItem of the focused app), meaning that apps won't get volume key events anymore. Check the documentation in plugins/Utils/windowkeysfilter.h

Volume keys are special in which we want both unity8 and the focused app to process them, unlike the power key, which should be processed only by unity8.

--------------------

In qml/Components/PhysicalKeysMapper.qml

"""
    property bool aPowerKeyWasReleased: true
    property bool powerKeyPressed: false

    property bool aVolumeKeyWasReleased: true
    property bool volumeDownKeyPressed: false
    property bool volumeUpKeyPressed: false
"""

Could you please wrap those properties in a "property QtObject d: QtObject { id: d }" as they're private?

review: Needs Fixing
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Daniel's handling the review

review: Abstain
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

In qml/Components/PhysicalKeysMapper.qml

"""
    signal bothVolumeKeysPressed();
"""

This signal is dead code as it's not being used in Shell.qml (since the screeshot is now power+volumeDown as has its own "virtual" key). Please remove it.

By the way, what's the rationale for changing the key combination of the screenshot? Which is all the more curious given that comment: https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1390393/comments/5

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Again on this eventAccepted thing: I was thinking we could remove it entirely from PhysicalKeysMapper and keep the accept/reject decision making in the WindowKeysFilter functions in Shell.qml, as they currently are. That way you clearly separate the mapping from the filtering logic and keep the filtering logic close to the item that actually does the filtering, which is WindowKeysFilter.

key mapping and event filtering are clearly two separate, independent tasks.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

The PhysicalKeysMapper test requires no GUI, so it should belong to qmlunittests, not qmluitests.

Tests that require no GUI are run by "make test" and are also run when building the debian packages.

This diff shows how to make it a GUI-less test:
http://paste.ubuntu.com/9474472/

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

The convention we've been using for test names is test_fooBar, not test_foo_bar.

Josh Arenson (josharenson) wrote : Posted in a previous version of this proposal

> In qml/Components/PhysicalKeysMapper.qml
>
> """
> signal bothVolumeKeysPressed();
> """
>
> This signal is dead code as it's not being used in Shell.qml (since the
> screeshot is now power+volumeDown as has its own "virtual" key). Please remove
> it.
>
> By the way, what's the rationale for changing the key combination of the
> screenshot? Which is all the more curious given that comment:
> https://bugs.launchpad.net/ubuntu/+source/unity8/+bug/1390393/comments/5

Had a lengthy discussion w/ mzanetti and AlbertA (and a bit w/ saviq) about this. Decided that this was the way to go. It _does_ resolve the bug, and I see no real architectural issues.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

PhysicalKeysMapper is still among the tests that require a GUI. The position where you put it in tests/qmltests/CMakeLists.txt matters. It should be right below this line:

"""
add_qml_test(utils/Unity/Test UnityTest)
"""

As it will get covered by this line:
"""
set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
"""
which makes it use the minimal QPA plugin, which does not have display output.

-------------------

You still didn't make (or said anything about) the Dialog changes I asked here:
https://code.launchpad.net/~josharenson/unity8/physical_keys_filter/+merge/244244/comments/602555

review: Needs Fixing
Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Text conflict in qml/Components/Dialogs.qml
1 conflicts encountered.

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Approving as the last couple of issues have been addressed.

review: Approve
Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

This is not enough, now I'm just getting screenshots of the greeter unless I hold the power key long enough for the screenshot to be taken.

You'll need to suppress the suspend code in unity-system-compositor if volume down was pressed while power was pressed.

review: Needs Fixing (ux)
Josh Arenson (josharenson) wrote : Posted in a previous version of this proposal

I think this introduces a circular dependency.

Case 1 - power key is pressed first:
USC suspends phone

Case 2 - volume key is pressed first:
Unity displays volume dialog which shows up in all screenshots

There is no way to satisfy everyone without introducing a delay

Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

Sure there is, USC needs to suppress suspending the phone if voldown is pressed while power is pressed.

Michał Sawicz (saviq) wrote : Posted in a previous version of this proposal

Don't get me wrong, the split in handling those keys between unity8 and usc is something we need to fix, but before we get there, USC needs to gain some knowledge on what unity8 does (as it already does have the knowledge for power long-press - it doesn't suspend, it wakes if suspended, even).

Josh Arenson (josharenson) wrote : Posted in a previous version of this proposal

> Don't get me wrong, the split in handling those keys between unity8 and usc is
> something we need to fix, but before we get there, USC needs to gain some
> knowledge on what unity8 does (as it already does have the knowledge for power
> long-press - it doesn't suspend, it wakes if suspended, even).

Working on a USC change that handles the following state
if (power_down && volume_down)
   disable_suspend()
if (power_up)
   enable_suspend()

I suppose I was thinking that suspend happens on power key pressed, but it should happen on power key released.

Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Text conflict in qml/Shell.qml
1 conflicts encountered.

kevin gunn (kgunn72) wrote : Posted in a previous version of this proposal
kevin gunn (kgunn72) wrote : Posted in a previous version of this proposal

meant to set "needs fixing"

review: Needs Fixing
kevin gunn (kgunn72) wrote : Posted in a previous version of this proposal

forgot to mark this as approved per testing

review: Approve (testing)
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

"""
8 + function onKeyPressed(key) {
119 + var eventAccepted = true;
120 +
121 + /* Determine what key was pressed */
122 + if (key == Qt.Key_PowerDown || key == Qt.Key_PowerOff) {
123 +
124 + // FIXME: We only consider power key presses if the screen is
125 + // on because of bugs 1410830/1409003. The theory is that when
126 + // those bugs are encountered, there is a >2s delay between the
127 + // power press event and the power release event, which causes
128 + // the shutdown dialog to appear on resume. So to avoid that
129 + // symptom while we investigate the root cause, we simply won't
130 + // initiate any dialogs when the screen is off.
131 + // This also prevents taking screenshots when the screen is off.
132 + if (Powerd.status === Powerd.On) {
133 + if (!powerKeyLongPressTimer.running) {
134 + powerKeyLongPressTimer.restart();
135 + }
136 + d.powerKeyPressed = true;
137 + }
138 + } else if (key == Qt.Key_MediaTogglePlayPause || key == Qt.Key_MediaPlay) {
139 + eventAccepted = callManager.handleMediaKey(false);
140 + } else if (key == Qt.Key_VolumeDown) {
141 + d.volumeDownKeyPressed = true;
142 + } else if (key == Qt.Key_VolumeUp) {
143 + d.volumeUpKeyPressed = true;
144 + } else {
145 + eventAccepted = false;
146 + }
"""

As I told before, volume keys should not be filtered out.

In this code, you're filtering them out (eventAccepted will be true for them).

You might consider renaming eventAccepted to something like filterOutEvent to make more explicit what the return value actually does.

review: Needs Fixing
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Having both aPowerKeyWasReleased and powerKeyPressed smelled fishy to me. So I had a go at making the code more declarative. It does seem to simplify and clarify PhysicalKeysMapper logic:

http://paste.ubuntu.com/10176638/

What do you think? It also has the benefit of making PhysicalKeysMapper interface more QMLish.
Eg: MouseArea has pressed property, not a pressed() signal.

Josh Arenson (josharenson) wrote : Posted in a previous version of this proposal

Addressed issues that were discussed on IRC as per #1507 and #1508

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

Looks good.

Non-blockers:

In test_LongPressPowerButton(), could you please check that powerKeyLongPress() only gets emited *after* the timeout?

like this:

"""
compare(onPowerKeyLongPressCount, 0);
wait(physicalKeysMapper.powerKeyLongPressTimeMs * 2);
compare(onPowerKeyLongPressCount, 1);
"""

review: Approve
Daniel d'Andrada (dandrader) : Posted in a previous version of this proposal
review: Approve (codewise)
Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

* Did you perform an exploratory manual test run of the code change and any related functionality?
No, but kgunn did.

* Did CI run pass? If not, please explain why.
Yes! All green!

* Did you make sure that the branch does not contain spurious tags?
Yes.

Albert Astals Cid (aacid) wrote : Posted in a previous version of this proposal

Relaying Saviq's comment over mail

*****
Ugh, I just realized that we might have needlessly changed the screenshot shortcut and made
lives hard for ourselves.

We need not have involved u-s-c if we kept the screenshot combination to
be vol+-, and since we've switched to reacting onReleased instead of
onPressed, we could have done the same with volume buttons...
The other thing that came to mind is that we need to verify that
long-press behaviour is still fine. Albert just checked and long-press
is broken.

Josh, when you're around, could you have a look at going back to vol+-
as the screenshot shortcut, and fixing the above. Have a look at
.isAutoRepeat of a key event to enable that.
*****

review: Needs Fixing
1514. By Michał Sawicz on 2015-02-16

Update copyright dates

1515. By Michał Sawicz on 2015-02-16

doc++

1516. By Michał Sawicz on 2015-02-16

doc--

1517. By Michał Sawicz on 2015-02-16

Accept power events regardless of power state, don't trigger screenshots if screen off

1518. By Michał Sawicz on 2015-02-16

Rename signals properly.

1519. By Michał Sawicz on 2015-02-16

Fix signal more

Albert Astals Cid (aacid) wrote :

 * Did you perform an exploratory manual test run of the code change and any related functionality?
Yes

 * Did CI run pass?
No, Qt 5.4 brought some failures, investigating

 * Did you make sure that the branch does not contain spurious tags?
Yes

review: Approve
Michael Zanetti (mzanetti) wrote :

IMO it feels quite odd that we only change the volume onReleased now...

Josh Arenson (josharenson) wrote : Posted in a previous version of this proposal

Can you please clarify what actions should trigger a screenshot, and which actions should trigger a volume event?

This is the order that I would assume

1. PressDecrease + PressIncrease = ScreenShot
2. PressDecrease + Release Decrease = Volume Down
3. PressIncrease + Release Increase = Volume Up
4. PressAndHoldIncrease (exclusive) = VolumeUp @ KeyRepeat Interval
5. PressAndHoldDecrease (exclusive) = VolumeDown @ KeyRepeat Interval

I have an issue with 2 & 3 (not receiving instant feedback on a volume button press). Also, should there be a delay for step 1, or should the screenshot happen instantly?

Daniel d'Andrada (dandrader) wrote : Posted in a previous version of this proposal

For what it's worth, my Android phone only reacts (ie, show the volume bar etc) when I release the volume key, not on press.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'qml/Components/Dialogs.qml'
2--- qml/Components/Dialogs.qml 2014-12-08 13:39:01 +0000
3+++ qml/Components/Dialogs.qml 2015-02-17 08:14:50 +0000
4@@ -35,21 +35,12 @@
5 }
6 }
7
8- function onPowerKeyPressed() {
9- // FIXME: event.isAutoRepeat is always false on Nexus 4.
10- // So we use powerKeyTimer.running to avoid the PowerOff key repeat
11- // https://launchpad.net/bugs/1349416
12- if (!powerKeyTimer.running) {
13- powerKeyTimer.restart();
14- }
15- }
16-
17- function onPowerKeyReleased() {
18- powerKeyTimer.stop();
19- }
20-
21 signal powerOffClicked();
22
23+ function showPowerDialog() {
24+ d.showPowerDialog();
25+ }
26+
27 QtObject {
28 id: d // private stuff
29 objectName: "dialogsPrivate"
30@@ -68,17 +59,6 @@
31 active: false
32 }
33
34- Timer {
35- id: powerKeyTimer
36- interval: 2000
37- repeat: false
38- triggeredOnStart: false
39-
40- onTriggered: {
41- d.showPowerDialog();
42- }
43- }
44-
45 Component {
46 id: logoutDialogComponent
47 ShellDialog {
48
49=== added file 'qml/Components/PhysicalKeysMapper.qml'
50--- qml/Components/PhysicalKeysMapper.qml 1970-01-01 00:00:00 +0000
51+++ qml/Components/PhysicalKeysMapper.qml 2015-02-17 08:14:50 +0000
52@@ -0,0 +1,113 @@
53+/*
54+ * Copyright (C) 2014-2015 Canonical, Ltd.
55+ *
56+ * This program is free software; you can redistribute it and/or modify
57+ * it under the terms of the GNU General Public License as published by
58+ * the Free Software Foundation; version 3.
59+ *
60+ * This program is distributed in the hope that it will be useful,
61+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
62+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
63+ * GNU General Public License for more details.
64+ *
65+ * You should have received a copy of the GNU General Public License
66+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
67+ */
68+
69+import QtQuick 2.0
70+import Powerd 0.1
71+
72+/*!
73+ \brief A mapper for the physical keys on the device
74+
75+ A mapper to handle events triggered by pressing physical keys on a device.
76+ Keys included are
77+ * Volume Decrease
78+ * Volume Increase
79+ * Power
80+
81+ This allows for handling the following events
82+ * Power dialog
83+ * Volume Decreases/Increases
84+ * Screenshots
85+
86+*/
87+
88+Item {
89+ id: root
90+
91+ signal powerKeyLongPressed;
92+ signal volumeDownTriggered;
93+ signal volumeUpTriggered;
94+ signal screenshotTriggered;
95+
96+ QtObject {
97+ id: d
98+
99+ property bool volumeDownKeyPressed: false
100+ property bool volumeUpKeyPressed: false
101+ property bool ignoreVolumeEvents: false
102+ }
103+
104+ Timer {
105+ id: powerKeyLongPressTimer
106+
107+ interval: 2000
108+ onTriggered: root.powerKeyLongPressed();
109+ }
110+
111+
112+ function onKeyPressed(event) {
113+ if ((event.key == Qt.Key_PowerDown || event.key == Qt.Key_PowerOff)
114+ && !event.isAutoRepeat) {
115+
116+ // FIXME: We only consider power key presses if the screen is
117+ // on because of bugs 1410830/1409003. The theory is that when
118+ // those bugs are encountered, there is a >2s delay between the
119+ // power press event and the power release event, which causes
120+ // the shutdown dialog to appear on resume. So to avoid that
121+ // symptom while we investigate the root cause, we simply won't
122+ // initiate any dialogs when the screen is off.
123+ if (Powerd.status === Powerd.On) {
124+ powerKeyLongPressTimer.restart();
125+ }
126+ event.accepted = true;
127+ } else if ((event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay)
128+ && !event.isAutoRepeat) {
129+ event.accepted = callManager.handleMediaKey(false);
130+ } else if (event.key == Qt.Key_VolumeDown) {
131+ if (event.isAutoRepeat && !d.ignoreVolumeEvents) root.volumeDownTriggered();
132+ else if (!event.isAutoRepeat) {
133+ if (d.volumeUpKeyPressed) {
134+ if (Powerd.status === Powerd.On) root.screenshotTriggered();
135+ d.ignoreVolumeEvents = true;
136+ }
137+ d.volumeDownKeyPressed = true;
138+ }
139+ } else if (event.key == Qt.Key_VolumeUp) {
140+ if (event.isAutoRepeat && !d.ignoreVolumeEvents) root.volumeUpTriggered();
141+ else if (!event.isAutoRepeat) {
142+ if (d.volumeDownKeyPressed) {
143+ if (Powerd.status === Powerd.On) root.screenshotTriggered();
144+ d.ignoreVolumeEvents = true;
145+ }
146+ d.volumeUpKeyPressed = true;
147+ }
148+ }
149+ }
150+
151+ function onKeyReleased(event) {
152+ if (event.key == Qt.Key_PowerDown || event.key == Qt.Key_PowerOff) {
153+ powerKeyLongPressTimer.stop();
154+ event.accepted = true;
155+ } else if (event.key == Qt.Key_VolumeDown) {
156+ if (!d.ignoreVolumeEvents) root.volumeDownTriggered();
157+ d.volumeDownKeyPressed = false;
158+ if (!d.volumeUpKeyPressed) d.ignoreVolumeEvents = false;
159+ } else if (event.key == Qt.Key_VolumeUp) {
160+ if (!d.ignoreVolumeEvents) root.volumeUpTriggered();
161+ d.volumeUpKeyPressed = false;
162+ if (!d.volumeDownKeyPressed) d.ignoreVolumeEvents = false;
163+ }
164+ }
165+}
166
167=== removed file 'qml/Components/VolumeKeyFilter.qml'
168--- qml/Components/VolumeKeyFilter.qml 2014-10-30 02:39:38 +0000
169+++ qml/Components/VolumeKeyFilter.qml 1970-01-01 00:00:00 +0000
170@@ -1,63 +0,0 @@
171-/*
172- * Copyright (C) 2014 Canonical, Ltd.
173- *
174- * This program is free software; you can redistribute it and/or modify
175- * it under the terms of the GNU General Public License as published by
176- * the Free Software Foundation; version 3.
177- *
178- * This program is distributed in the hope that it will be useful,
179- * but WITHOUT ANY WARRANTY; without even the implied warranty of
180- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
181- * GNU General Public License for more details.
182- *
183- * You should have received a copy of the GNU General Public License
184- * along with this program. If not, see <http://www.gnu.org/licenses/>.
185- */
186-
187-import QtQuick 2.0
188-/*!
189- \brief A filter for volume keys
190-
191-A filter which treats volume keys as single tri-state key with the states:
192-VolumeUp Pressed, VolumeDown Pressed or Volume Up+Down pressed
193-*/
194-QtObject {
195- id: root
196-
197- signal volumeUpPressed()
198- signal volumeDownPressed()
199- signal bothVolumeKeysPressed()
200-
201- property bool volumeUpKeyPressed: false
202- property bool volumeDownKeyPressed: false
203- property bool aVolumeKeyWasReleased: true
204-
205- function onKeyPressed(key) {
206- if (key == Qt.Key_VolumeUp)
207- volumeUpKeyPressed = true;
208- else if (key == Qt.Key_VolumeDown)
209- volumeDownKeyPressed = true;
210-
211- if (volumeDownKeyPressed && volumeUpKeyPressed) {
212- //avoids sending a signal repeatedly if both keys are held
213- //instead one of the keys must have been previously released
214- if (aVolumeKeyWasReleased)
215- bothVolumeKeysPressed();
216- aVolumeKeyWasReleased = false;
217- } else if (volumeDownKeyPressed) {
218- volumeDownPressed();
219- } else if (volumeUpKeyPressed) {
220- volumeUpPressed();
221- }
222- }
223-
224- function onKeyReleased(key) {
225- if (key == Qt.Key_VolumeUp) {
226- volumeUpKeyPressed = false;
227- aVolumeKeyWasReleased = true;
228- } else if (key == Qt.Key_VolumeDown) {
229- volumeDownKeyPressed = false;
230- aVolumeKeyWasReleased = true;
231- }
232- }
233-}
234
235=== modified file 'qml/Shell.qml'
236--- qml/Shell.qml 2015-02-11 17:12:49 +0000
237+++ qml/Shell.qml 2015-02-17 08:14:50 +0000
238@@ -160,6 +160,15 @@
239 objectName: "dashCommunicator"
240 }
241
242+ PhysicalKeysMapper {
243+ id: physicalKeysMapper
244+
245+ onPowerKeyLongPressed: dialogs.showPowerDialog()
246+ onVolumeDownTriggered: volumeControl.volumeDown();
247+ onVolumeUpTriggered: volumeControl.volumeUp();
248+ onScreenshotTriggered: screenGrabber.capture();
249+ }
250+
251 ScreenGrabber {
252 id: screenGrabber
253 z: dialogs.z + 10
254@@ -172,45 +181,9 @@
255 value: launcher.shown || launcher.dashSwipe
256 }
257
258- VolumeKeyFilter {
259- id: volumeKeyFilter
260- onVolumeDownPressed: volumeControl.volumeDown()
261- onVolumeUpPressed: volumeControl.volumeUp()
262- onBothVolumeKeysPressed: screenGrabber.capture()
263- }
264-
265 WindowKeysFilter {
266- Keys.onPressed: {
267- // Nokia earpieces give TogglePlayPause, while the iPhone's earpiece gives Play
268- if (event.key == Qt.Key_MediaTogglePlayPause || event.key == Qt.Key_MediaPlay) {
269- event.accepted = callManager.handleMediaKey(false);
270- } else if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
271- // FIXME: We only consider power key presses if the screen is
272- // on because of bugs 1410830/1409003. The theory is that when
273- // those bugs are encountered, there is a >2s delay between the
274- // power press event and the power release event, which causes
275- // the shutdown dialog to appear on resume. So to avoid that
276- // symptom while we investigate the root cause, we simply won't
277- // initiate any dialogs when the screen is off.
278- if (Powerd.status === Powerd.On) {
279- dialogs.onPowerKeyPressed();
280- }
281- event.accepted = true;
282- } else {
283- volumeKeyFilter.onKeyPressed(event.key);
284- event.accepted = false;
285- }
286- }
287-
288- Keys.onReleased: {
289- if (event.key == Qt.Key_PowerOff || event.key == Qt.Key_PowerDown) {
290- dialogs.onPowerKeyReleased();
291- event.accepted = true;
292- } else {
293- volumeKeyFilter.onKeyReleased(event.key);
294- event.accepted = false;
295- }
296- }
297+ Keys.onPressed: physicalKeysMapper.onKeyPressed(event);
298+ Keys.onReleased: physicalKeysMapper.onKeyReleased(event);
299 }
300
301 Item {
302
303=== modified file 'tests/qmltests/CMakeLists.txt'
304--- tests/qmltests/CMakeLists.txt 2015-02-11 17:11:41 +0000
305+++ tests/qmltests/CMakeLists.txt 2015-02-17 08:14:50 +0000
306@@ -13,6 +13,7 @@
307 set(qmltest_DEFAULT_NO_ADD_TEST FALSE)
308 set(qmltest_DEFAULT_PROPERTIES ENVIRONMENT "QT_QPA_PLATFORM=minimal")
309 add_qml_test(utils/Unity/Test UnityTest)
310+add_qml_test(Components PhysicalKeysMapper)
311
312 set(qmltest_DEFAULT_TARGETS qmluitests)
313 set(qmltest_DEFAULT_NO_ADD_TEST TRUE)
314
315=== added file 'tests/qmltests/Components/tst_PhysicalKeysMapper.qml'
316--- tests/qmltests/Components/tst_PhysicalKeysMapper.qml 1970-01-01 00:00:00 +0000
317+++ tests/qmltests/Components/tst_PhysicalKeysMapper.qml 2015-02-17 08:14:50 +0000
318@@ -0,0 +1,119 @@
319+/*
320+ * Copyright (C) 2014-2015 Canonical, Ltd.
321+ *
322+ * This program is free software; you can redistribute it and/or modify
323+ * it under the terms of the GNU General Public License as published by
324+ * the Free Software Foundation; version 3.
325+ *
326+ * This program is distributed in the hope that it will be useful,
327+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
328+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
329+ * GNU General Public License for more details.
330+ *
331+ * You should have received a copy of the GNU General Public License
332+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
333+ */
334+
335+import QtQuick 2.0
336+import QtTest 1.0
337+import "../../../qml/Components"
338+
339+TestCase {
340+ name: "PhysicalKeysMapper"
341+
342+ Loader {
343+ // Using a Loader here to make sure mapper state is coherent
344+ // regardless of unmatched KeyPress and KeyRelease events
345+ id: loader
346+ active: false
347+ sourceComponent: PhysicalKeysMapper { }
348+ }
349+
350+ SignalSpy {
351+ id: powerSpy
352+ target: loader.item
353+ signalName: "powerKeyLongPressed"
354+ }
355+
356+ SignalSpy {
357+ id: volumeDownSpy
358+ target: loader.item
359+ signalName: "volumeDownTriggered"
360+ }
361+
362+ SignalSpy {
363+ id: volumeUpSpy
364+ target: loader.item
365+ signalName: "volumeUpTriggered"
366+ }
367+
368+ SignalSpy {
369+ id: screenshotSpy
370+ target: loader.item
371+ signalName: "screenshotTriggered"
372+ }
373+
374+ function init() {
375+ loader.active = true;
376+ tryCompare(loader.status == Loader.Ready);
377+ }
378+
379+ function cleanup() {
380+ loader.active = false;
381+ powerSpy.clear();
382+ volumeDownSpy.clear();
383+ volumeUpSpy.clear();
384+ screenshotSpy.clear();
385+ }
386+
387+ function test_LongPressPowerButton() {
388+ loader.item.onKeyPressed({ key: Qt.Key_PowerDown });
389+
390+ expectFailContinue("", "Power signal should not be emitted within a second");
391+ powerSpy.wait(1000);
392+ powerSpy.clear();
393+
394+ loader.item.onKeyPressed({ key: Qt.Key_PowerDown, isAutoRepeat: true });
395+ powerSpy.wait(1500);
396+ }
397+
398+ function test_ScreenshotButtons_data() {
399+ return [
400+ { tag: "UpFirst", first: Qt.Key_VolumeUp, second: Qt.Key_VolumeDown },
401+ { tag: "DownFirst", first: Qt.Key_VolumeDown, second: Qt.Key_VolumeUp },
402+ ];
403+ }
404+
405+ function test_ScreenshotButtons(data) {
406+ loader.item.onKeyPressed({ key: data.first });
407+ loader.item.onKeyPressed({ key: data.second });
408+ screenshotSpy.wait();
409+ loader.item.onKeyReleased({ key: data.first });
410+ loader.item.onKeyReleased({ key: data.second });
411+ expectFailContinue("", "VolumeUp signal should not fire");
412+ volumeUpSpy.wait(100);
413+ expectFailContinue("", "VolumeDown signal should not fire");
414+ volumeDownSpy.wait(100);
415+ }
416+
417+ function test_VolumeButton_data() {
418+ return [
419+ { tag: "Down", key: Qt.Key_VolumeDown, spy: volumeDownSpy },
420+ { tag: "Up", key: Qt.Key_VolumeUp, spy: volumeUpSpy },
421+ ];
422+ }
423+
424+ function test_VolumeButton(data) {
425+ loader.item.onKeyPressed({ key: data.key });
426+ expectFailContinue("", "Signal should not fire on press");
427+ data.spy.wait(100);
428+ data.spy.clear();
429+
430+ loader.item.onKeyReleased({ key: data.key });
431+ data.spy.wait();
432+
433+ loader.item.onKeyPressed({ key: data.key });
434+ loader.item.onKeyPressed({ key: data.key, isAutoRepeat: true });
435+ data.spy.wait();
436+ }
437+}

Subscribers

People subscribed via source and target branches