diff mbox series

adapter: Implement PowerState property

Message ID db39101f70c945026e76d7b49ee358d9a2330358.camel@hadess.net
State New
Headers show
Series adapter: Implement PowerState property | expand

Commit Message

Bastien Nocera Aug. 25, 2022, 1:26 p.m. UTC
This property should allow any program to show the transitional state,
not just the one that requested the change, and will also show
transitional states that were the results of other system changes, like
rfkill changes.
---

Downstream bug in gnome-bluetooth:
https://gitlab.gnome.org/GNOME/gnome-bluetooth/-/issues/121

Note that this probably doesn't handle multiple, conflicting requests
for power on, or power off. Is there a good way to protect against
that?

 client/main.c       |  1 +
 doc/adapter-api.txt | 14 ++++++++++
 src/adapter.c       | 66 +++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 81 insertions(+)

Comments

bluez.test.bot@gmail.com Aug. 25, 2022, 1:50 p.m. UTC | #1
This is an automated email and please do not reply to this email.

Dear Submitter,

Thank you for submitting the patches to the linux bluetooth mailing list.
While preparing the CI tests, the patches you submitted couldn't be applied to the current HEAD of the repository.

----- Output -----
error: corrupt patch at line 20
hint: Use 'git am --show-current-patch' to see the failed patch


Please resolve the issue and submit the patches again.


---
Regards,
Linux Bluetooth
Bastien Nocera Aug. 25, 2022, 3:30 p.m. UTC | #2
On Thu, 2022-08-25 at 15:26 +0200, Bastien Nocera wrote:
> This property should allow any program to show the transitional
> state,
> not just the one that requested the change, and will also show
> transitional states that were the results of other system changes,
> like
> rfkill changes.

Looks like the bot doesn't like where I put those comments.

If anyone can comment on the API I used, and I'll iterate the actual
implementation. I'd like the API to be settled by the time GNOME 43
ships, so we can rely on it there.

Cheers
Luiz Augusto von Dentz Aug. 25, 2022, 11:10 p.m. UTC | #3
Hi Archie and Bastien,

On Thu, Aug 25, 2022 at 4:06 PM Luiz Augusto von Dentz
<luiz.dentz@gmail.com> wrote:
>
> Hi Bastien,
>
> On Thu, Aug 25, 2022 at 8:32 AM Bastien Nocera <hadess@hadess.net> wrote:
> >
> > On Thu, 2022-08-25 at 15:26 +0200, Bastien Nocera wrote:
> > > This property should allow any program to show the transitional
> > > state,
> > > not just the one that requested the change, and will also show
> > > transitional states that were the results of other system changes,
> > > like
> > > rfkill changes.
> >
> > Looks like the bot doesn't like where I put those comments.
> >
> > If anyone can comment on the API I used, and I'll iterate the actual
> > implementation. I'd like the API to be settled by the time GNOME 43
> > ships, so we can rely on it there.
>
> I wonder what are you actually after with these changes, in most cases
> I'd say the changes shall just be queued, anyway perhaps the problem
> was something related to:
>
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit/?id=ede7b915980fbc80eff80aa189c35ca016956c61

Btw, I think this shows that perhaps it would be best practice to
create a github issue when there are bugs for Gnome/Chrome OS so we
can properly use the Fixes: tag to close them which makes it easier
for downstream to find out if a similar issue was fixed and attempt to
reuse the same fix.
Bastien Nocera Aug. 26, 2022, 9:25 a.m. UTC | #4
On Thu, 2022-08-25 at 16:06 -0700, Luiz Augusto von Dentz wrote:
> Hi Bastien,
> 
> On Thu, Aug 25, 2022 at 8:32 AM Bastien Nocera <hadess@hadess.net>
> wrote:
> > 
> > On Thu, 2022-08-25 at 15:26 +0200, Bastien Nocera wrote:
> > > This property should allow any program to show the transitional
> > > state,
> > > not just the one that requested the change, and will also show
> > > transitional states that were the results of other system
> > > changes,
> > > like
> > > rfkill changes.
> > 
> > Looks like the bot doesn't like where I put those comments.
> > 
> > If anyone can comment on the API I used, and I'll iterate the
> > actual
> > implementation. I'd like the API to be settled by the time GNOME 43
> > ships, so we can rely on it there.
> 
> I wonder what are you actually after with these changes, in most
> cases
> I'd say the changes shall just be queued, anyway perhaps the problem
> was something related to:
> 
> https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit/?id=ede7b915980fbc80eff80aa189c35ca016956c61
> 

That helps with one of the problems I ran into (resetting the
transitional state on failure), thanks.

What I'm after is a transitional state when the Bluetooth adapter is
being powered on, as it can take more than "an instant" (aka 200msec)
to turn on, and sometimes much longer.

Users in that transition period can wonder why the adapter is already
on but not usable, or still off. This is even worse if the enablement
doesn't work, as it could show that the device was on/available for a
time before stopping being available, depending on which option the UI
developer took, which is obviously not true.

I updated the patch for the latest git and completed the commit
message, let me know if that helps.
Bastien Nocera Aug. 26, 2022, 9:26 a.m. UTC | #5
On Thu, 2022-08-25 at 16:10 -0700, Luiz Augusto von Dentz wrote:
> Hi Archie and Bastien,
> 
> On Thu, Aug 25, 2022 at 4:06 PM Luiz Augusto von Dentz
> <luiz.dentz@gmail.com> wrote:
> > 
> > Hi Bastien,
> > 
> > On Thu, Aug 25, 2022 at 8:32 AM Bastien Nocera <hadess@hadess.net>
> > wrote:
> > > 
> > > On Thu, 2022-08-25 at 15:26 +0200, Bastien Nocera wrote:
> > > > This property should allow any program to show the transitional
> > > > state,
> > > > not just the one that requested the change, and will also show
> > > > transitional states that were the results of other system
> > > > changes,
> > > > like
> > > > rfkill changes.
> > > 
> > > Looks like the bot doesn't like where I put those comments.
> > > 
> > > If anyone can comment on the API I used, and I'll iterate the
> > > actual
> > > implementation. I'd like the API to be settled by the time GNOME
> > > 43
> > > ships, so we can rely on it there.
> > 
> > I wonder what are you actually after with these changes, in most
> > cases
> > I'd say the changes shall just be queued, anyway perhaps the
> > problem
> > was something related to:
> > 
> > https://git.kernel.org/pub/scm/bluetooth/bluez.git/commit/?id=ede7b915980fbc80eff80aa189c35ca016956c61
> 
> Btw, I think this shows that perhaps it would be best practice to
> create a github issue when there are bugs for Gnome/Chrome OS so we
> can properly use the Fixes: tag to close them which makes it easier
> for downstream to find out if a similar issue was fixed and attempt
> to
> reuse the same fix.

That's not the fix we need in this particular case, but I'm fine with
filing GitHub bugs if you prefer.
Luiz Augusto von Dentz Aug. 26, 2022, 6:50 p.m. UTC | #6
Hi Bastien,

On Thu, Aug 25, 2022 at 6:40 AM Bastien Nocera <hadess@hadess.net> wrote:
>
> This property should allow any program to show the transitional state,
> not just the one that requested the change, and will also show
> transitional states that were the results of other system changes, like
> rfkill changes.
> ---
>
> Downstream bug in gnome-bluetooth:
> https://gitlab.gnome.org/GNOME/gnome-bluetooth/-/issues/121
>
> Note that this probably doesn't handle multiple, conflicting requests
> for power on, or power off. Is there a good way to protect against
> that?
>
>  client/main.c       |  1 +
>  doc/adapter-api.txt | 14 ++++++++++
>  src/adapter.c       | 66 +++++++++++++++++++++++++++++++++++++++++++++
>  3 files changed, 81 insertions(+)
>
> diff --git a/client/main.c b/client/main.c
> index 19139d15b..ddd97c23c 100644
> --- a/client/main.c
> +++ b/client/main.c
> @@ -981,6 +981,7 @@ static void cmd_show(int argc, char *argv[])
>         print_property(adapter->proxy, "Alias");
>         print_property(adapter->proxy, "Class");
>         print_property(adapter->proxy, "Powered");
> +       print_property(adapter->proxy, "PowerState");
>         print_property(adapter->proxy, "Discoverable");
>         print_property(adapter->proxy, "DiscoverableTimeout");
>         print_property(adapter->proxy, "Pairable");
> diff --git a/doc/adapter-api.txt b/doc/adapter-api.txt
> index 48466ab75..5bdb9c34e 100644
> --- a/doc/adapter-api.txt
> +++ b/doc/adapter-api.txt
> @@ -269,6 +269,20 @@ Properties string Address [readonly]
>                         restart or unplugging of the adapter it will reset
>                         back to false.
>
> +               string PowerState [readonly]
> +
> +                       The power state of an adapter.
> +
> +                       The power state will show whether the adapter is
> +                       turning off, or turning on, as well as being on
> +                       or off.
> +
> +                       Possible values:
> +                               "on" - powered on
> +                               "off" - powered off
> +                               "turning-on" - transitioning from "off" to "on"
> +                               "turning-off" - transitioning from "on" to "off"

This changes need to be split in its own patch, also not long ago I
was discussing with Marcel about MGMT power states vs rfkill, they
don't seem to be inline with each other, while rfkill does reflect on
the MGMT interface powered states doesn't which means the driver are
not aware of it since afaik the MGMT states are not communicated back
to the driver, so perhaps we should reflect this distinction on
PowerState with a dedicated state for rfkill or we start using rfkill
ourselves directly via daemon using a dedicated property.

>                 boolean Discoverable [readwrite]
>
>                         Switch an adapter to discoverable or non-discoverable
> diff --git a/src/adapter.c b/src/adapter.c
> index ec26aab1a..3b0237708 100644
> --- a/src/adapter.c
> +++ b/src/adapter.c
> @@ -239,6 +239,12 @@ struct btd_adapter_pin_cb_iter {
>         /* When the iterator reaches the end, it is NULL and attempt is 0 */
>  };
>
> +enum {
> +       ADAPTER_POWER_STATE_TARGET_NONE = 0,
> +       ADAPTER_POWER_STATE_TARGET_OFF,
> +       ADAPTER_POWER_STATE_TARGET_ON
> +};
> +
>  struct btd_adapter {
>         int ref_count;
>
> @@ -252,6 +258,7 @@ struct btd_adapter {
>         char *short_name;               /* controller short name */
>         uint32_t supported_settings;    /* controller supported settings */
>         uint32_t pending_settings;      /* pending controller settings */
> +       uint32_t power_state_target;    /* the target power state */
>         uint32_t current_settings;      /* current controller settings */
>
>         char *path;                     /* adapter object path */
> @@ -579,6 +586,8 @@ static void settings_changed(struct btd_adapter *adapter, uint32_t settings)
>         if (changed_mask & MGMT_SETTING_POWERED) {
>                 g_dbus_emit_property_changed(dbus_conn, adapter->path,
>                                         ADAPTER_INTERFACE, "Powered");
> +               g_dbus_emit_property_changed(dbus_conn, adapter->path,
> +                                       ADAPTER_INTERFACE, "PowerState");
>
>                 if (adapter->current_settings & MGMT_SETTING_POWERED) {
>                         adapter_start(adapter);
> @@ -635,6 +644,11 @@ static void new_settings_callback(uint16_t index, uint16_t length,
>         if (settings == adapter->current_settings)
>                 return;
>
> +       if ((adapter->current_settings & MGMT_SETTING_POWERED) !=
> +           (settings & MGMT_SETTING_POWERED)) {
> +               adapter->power_state_target = ADAPTER_POWER_STATE_TARGET_NONE;
> +       }
> +
>         DBG("Settings: 0x%08x", settings);
>
>         settings_changed(adapter, settings);
> @@ -684,6 +698,11 @@ static bool set_mode(struct btd_adapter *adapter, uint16_t opcode,
>         switch (opcode) {
>         case MGMT_OP_SET_POWERED:
>                 setting = MGMT_SETTING_POWERED;
> +               adapter->power_state_target = mode ?
> +                       ADAPTER_POWER_STATE_TARGET_ON :
> +                       ADAPTER_POWER_STATE_TARGET_OFF;
> +               g_dbus_emit_property_changed(dbus_conn, adapter->path,
> +                                               ADAPTER_INTERFACE, "PowerState");
>                 break;
>         case MGMT_OP_SET_CONNECTABLE:
>                 setting = MGMT_SETTING_CONNECTABLE;
> @@ -708,6 +727,12 @@ static bool set_mode(struct btd_adapter *adapter, uint16_t opcode,
>                                 set_mode_complete, adapter, NULL) > 0)
>                 return true;
>
> +       if (setting == MGMT_SETTING_POWERED) {
> +               /* cancel the earlier setting */
> +               adapter->power_state_target = ADAPTER_POWER_STATE_TARGET_NONE;
> +               g_dbus_emit_property_changed(dbus_conn, adapter->path,
> +                                               ADAPTER_INTERFACE, "PowerState");
> +       }
>         btd_error(adapter->dev_id, "Failed to set mode for index %u",
>                                                         adapter->dev_id);
>
> @@ -2878,6 +2903,7 @@ static gboolean property_get_mode(struct btd_adapter *adapter,
>  struct property_set_data {
>         struct btd_adapter *adapter;
>         GDBusPendingPropertySet id;
> +       uint32_t setting;
>  };
>
>  static void property_set_mode_complete(uint8_t status, uint16_t length,
> @@ -2888,6 +2914,9 @@ static void property_set_mode_complete(uint8_t status, uint16_t length,
>
>         DBG("%s (0x%02x)", mgmt_errstr(status), status);
>
> +       if (data->setting == MGMT_SETTING_POWERED)
> +               adapter->power_state_target = ADAPTER_POWER_STATE_TARGET_NONE;
> +
>         if (status != MGMT_STATUS_SUCCESS) {
>                 const char *dbus_err;
>
> @@ -3025,6 +3054,15 @@ static void property_set_mode(struct btd_adapter *adapter, uint32_t setting,
>
>         data->adapter = adapter;
>         data->id = id;
> +       data->setting = setting;
> +
> +       if (setting == MGMT_SETTING_POWERED) {
> +               adapter->power_state_target = mode ?
> +                       ADAPTER_POWER_STATE_TARGET_ON :
> +                       ADAPTER_POWER_STATE_TARGET_OFF;
> +               g_dbus_emit_property_changed(dbus_conn, adapter->path,
> +                                               ADAPTER_INTERFACE, "PowerState");
> +       }
>
>         if (mgmt_send(adapter->mgmt, opcode, adapter->dev_id, len, param,
>                         property_set_mode_complete, data, g_free) > 0)
> @@ -3062,6 +3100,29 @@ static void property_set_powered(const GDBusPropertyTable *property,
>         property_set_mode(adapter, MGMT_SETTING_POWERED, iter, id);
>  }
>
> +static gboolean property_get_power_state(const GDBusPropertyTable *property,
> +                                       DBusMessageIter *iter, void *user_data)
> +{
> +       struct btd_adapter *adapter = user_data;
> +       const char *str;
> +
> +       if (adapter->power_state_target == ADAPTER_POWER_STATE_TARGET_NONE) {
> +               if (adapter->current_settings & MGMT_SETTING_POWERED)
> +                       str = "on";
> +               else
> +                       str = "off";
> +       } else {
> +               if (adapter->power_state_target == ADAPTER_POWER_STATE_TARGET_ON)
> +                       str = "turning-on";
> +               else
> +                       str = "turning-off";
> +       }
> +
> +       dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str);
> +
> +       return TRUE;
> +}
> +
>  static gboolean property_get_discoverable(const GDBusPropertyTable *property,
>                                         DBusMessageIter *iter, void *user_data)
>  {
> @@ -3700,6 +3761,7 @@ static const GDBusPropertyTable adapter_properties[] = {
>         { "Alias", "s", property_get_alias, property_set_alias },
>         { "Class", "u", property_get_class },
>         { "Powered", "b", property_get_powered, property_set_powered },
> +       { "PowerState", "s", property_get_power_state },
>         { "Discoverable", "b", property_get_discoverable,
>                                         property_set_discoverable },
>         { "DiscoverableTimeout", "u", property_get_discoverable_timeout,
> @@ -5506,6 +5568,8 @@ static void adapter_start(struct btd_adapter *adapter)
>  {
>         g_dbus_emit_property_changed(dbus_conn, adapter->path,
>                                                 ADAPTER_INTERFACE, "Powered");
> +       g_dbus_emit_property_changed(dbus_conn, adapter->path,
> +                                               ADAPTER_INTERFACE, "PowerState");
>
>         DBG("adapter %s has been enabled", adapter->path);
>
> @@ -7249,6 +7313,8 @@ static void adapter_stop(struct btd_adapter *adapter)
>
>         g_dbus_emit_property_changed(dbus_conn, adapter->path,
>                                                 ADAPTER_INTERFACE, "Powered");
> +       g_dbus_emit_property_changed(dbus_conn, adapter->path,
> +                                               ADAPTER_INTERFACE, "PowerState");
>
>         DBG("adapter %s has been disabled", adapter->path);
>  }
> --
> 2.37.2
>
Bastien Nocera Aug. 29, 2022, 9:47 a.m. UTC | #7
On Fri, 2022-08-26 at 11:50 -0700, Luiz Augusto von Dentz wrote:
> Hi Bastien,
> 
> On Thu, Aug 25, 2022 at 6:40 AM Bastien Nocera <hadess@hadess.net>
> wrote:
> > 
> > This property should allow any program to show the transitional
> > state,
> > not just the one that requested the change, and will also show
> > transitional states that were the results of other system changes,
> > like
> > rfkill changes.
> > ---
> > 
> > Downstream bug in gnome-bluetooth:
> > https://gitlab.gnome.org/GNOME/gnome-bluetooth/-/issues/121
> > 
> > Note that this probably doesn't handle multiple, conflicting
> > requests
> > for power on, or power off. Is there a good way to protect against
> > that?
> > 
> >  client/main.c       |  1 +
> >  doc/adapter-api.txt | 14 ++++++++++
> >  src/adapter.c       | 66
> > +++++++++++++++++++++++++++++++++++++++++++++
> >  3 files changed, 81 insertions(+)
> > 
> > diff --git a/client/main.c b/client/main.c
> > index 19139d15b..ddd97c23c 100644
> > --- a/client/main.c
> > +++ b/client/main.c
> > @@ -981,6 +981,7 @@ static void cmd_show(int argc, char *argv[])
> >         print_property(adapter->proxy, "Alias");
> >         print_property(adapter->proxy, "Class");
> >         print_property(adapter->proxy, "Powered");
> > +       print_property(adapter->proxy, "PowerState");
> >         print_property(adapter->proxy, "Discoverable");
> >         print_property(adapter->proxy, "DiscoverableTimeout");
> >         print_property(adapter->proxy, "Pairable");
> > diff --git a/doc/adapter-api.txt b/doc/adapter-api.txt
> > index 48466ab75..5bdb9c34e 100644
> > --- a/doc/adapter-api.txt
> > +++ b/doc/adapter-api.txt
> > @@ -269,6 +269,20 @@ Properties string Address [readonly]
> >                         restart or unplugging of the adapter it
> > will reset
> >                         back to false.
> > 
> > +               string PowerState [readonly]
> > +
> > +                       The power state of an adapter.
> > +
> > +                       The power state will show whether the
> > adapter is
> > +                       turning off, or turning on, as well as
> > being on
> > +                       or off.
> > +
> > +                       Possible values:
> > +                               "on" - powered on
> > +                               "off" - powered off
> > +                               "turning-on" - transitioning from
> > "off" to "on"
> > +                               "turning-off" - transitioning from
> > "on" to "off"
> 
> This changes need to be split in its own patch,

You want the docs changes to be a separate patch? I can do that, but I
thought that having documentation changes be added along with the
property itself would have been desirable.

>  also not long ago I
> was discussing with Marcel about MGMT power states vs rfkill, they
> don't seem to be inline with each other, while rfkill does reflect on
> the MGMT interface powered states doesn't which means the driver are
> not aware of it since afaik the MGMT states are not communicated back
> to the driver, so perhaps we should reflect this distinction on
> PowerState with a dedicated state for rfkill or we start using rfkill
> ourselves directly via daemon using a dedicated property.

The rfkill calls made by GNOME are usually sent to all the rfkill
devices, rather than targeted at blocking a single adapter. So the
adapter state isn't super interesting in this case, as all the devices
will be either blocked (computer with no platform rfkill), or gone from
the USB bus (computer with a platform rfkill).


The good thing about using a string property here is that we can extend
it. One thing we can do though, to make extensibility easier, is take a
leaf out of the icon naming specification, and use prefixes to encode
the expected state in case the software isn't new enough to know about
a property.

For example:
- "on"
- "off"
- "on-disabling" (transitioning from on to off)
- "off-enabling" (transitioning from off to on)

So we could easily add:
- "off-rfkill" (off and blocked)

Let me know what you think.

Cheers
Bastien Nocera Aug. 30, 2022, 4:59 p.m. UTC | #8
On Mon, 2022-08-29 at 11:47 +0200, Bastien Nocera wrote:
> The rfkill calls made by GNOME are usually sent to all the rfkill
> devices, rather than targeted at blocking a single adapter. So the
> adapter state isn't super interesting in this case, as all the
> devices
> will be either blocked (computer with no platform rfkill), or gone
> from
> the USB bus (computer with a platform rfkill).
> 
> 
> The good thing about using a string property here is that we can
> extend
> it. One thing we can do though, to make extensibility easier, is take
> a
> leaf out of the icon naming specification, and use prefixes to encode
> the expected state in case the software isn't new enough to know
> about
> a property.
> 
> For example:
> - "on"
> - "off"
> - "on-disabling" (transitioning from on to off)
> - "off-enabling" (transitioning from off to on)
> 
> So we could easily add:
> - "off-rfkill" (off and blocked)
> 
> Let me know what you think.

Poking again, as I really need to land this soon (and by soon I mean
last week ;)
Luiz Augusto von Dentz Aug. 30, 2022, 5:35 p.m. UTC | #9
Hi Bastien,

On Tue, Aug 30, 2022 at 9:59 AM Bastien Nocera <hadess@hadess.net> wrote:
>
> On Mon, 2022-08-29 at 11:47 +0200, Bastien Nocera wrote:
> > The rfkill calls made by GNOME are usually sent to all the rfkill
> > devices, rather than targeted at blocking a single adapter. So the
> > adapter state isn't super interesting in this case, as all the
> > devices
> > will be either blocked (computer with no platform rfkill), or gone
> > from
> > the USB bus (computer with a platform rfkill).
> >
> >
> > The good thing about using a string property here is that we can
> > extend
> > it. One thing we can do though, to make extensibility easier, is take
> > a
> > leaf out of the icon naming specification, and use prefixes to encode
> > the expected state in case the software isn't new enough to know
> > about
> > a property.
> >
> > For example:
> > - "on"
> > - "off"
> > - "on-disabling" (transitioning from on to off)
> > - "off-enabling" (transitioning from off to on)
> >
> > So we could easily add:
> > - "off-rfkill" (off and blocked)
> >
> > Let me know what you think.
>
> Poking again, as I really need to land this soon (and by soon I mean
> last week ;)

off-rfkill sounds fine to me, btw introduce it as experimental so we
can still make changes if need to.
diff mbox series

Patch

diff --git a/client/main.c b/client/main.c
index 19139d15b..ddd97c23c 100644
--- a/client/main.c
+++ b/client/main.c
@@ -981,6 +981,7 @@  static void cmd_show(int argc, char *argv[])
        print_property(adapter->proxy, "Alias");
        print_property(adapter->proxy, "Class");
        print_property(adapter->proxy, "Powered");
+       print_property(adapter->proxy, "PowerState");
        print_property(adapter->proxy, "Discoverable");
        print_property(adapter->proxy, "DiscoverableTimeout");
        print_property(adapter->proxy, "Pairable");
diff --git a/doc/adapter-api.txt b/doc/adapter-api.txt
index 48466ab75..5bdb9c34e 100644
--- a/doc/adapter-api.txt
+++ b/doc/adapter-api.txt
@@ -269,6 +269,20 @@  Properties string Address [readonly]
                        restart or unplugging of the adapter it will reset
                        back to false.
 
+               string PowerState [readonly]
+
+                       The power state of an adapter.
+
+                       The power state will show whether the adapter is
+                       turning off, or turning on, as well as being on
+                       or off.
+
+                       Possible values:
+                               "on" - powered on
+                               "off" - powered off
+                               "turning-on" - transitioning from "off" to "on"
+                               "turning-off" - transitioning from "on" to "off"
+
                boolean Discoverable [readwrite]
 
                        Switch an adapter to discoverable or non-discoverable
diff --git a/src/adapter.c b/src/adapter.c
index ec26aab1a..3b0237708 100644
--- a/src/adapter.c
+++ b/src/adapter.c
@@ -239,6 +239,12 @@  struct btd_adapter_pin_cb_iter {
        /* When the iterator reaches the end, it is NULL and attempt is 0 */
 };
 
+enum {
+       ADAPTER_POWER_STATE_TARGET_NONE = 0,
+       ADAPTER_POWER_STATE_TARGET_OFF,
+       ADAPTER_POWER_STATE_TARGET_ON
+};
+
 struct btd_adapter {
        int ref_count;
 
@@ -252,6 +258,7 @@  struct btd_adapter {
        char *short_name;               /* controller short name */
        uint32_t supported_settings;    /* controller supported settings */
        uint32_t pending_settings;      /* pending controller settings */
+       uint32_t power_state_target;    /* the target power state */
        uint32_t current_settings;      /* current controller settings */
 
        char *path;                     /* adapter object path */
@@ -579,6 +586,8 @@  static void settings_changed(struct btd_adapter *adapter, uint32_t settings)
        if (changed_mask & MGMT_SETTING_POWERED) {
                g_dbus_emit_property_changed(dbus_conn, adapter->path,
                                        ADAPTER_INTERFACE, "Powered");
+               g_dbus_emit_property_changed(dbus_conn, adapter->path,
+                                       ADAPTER_INTERFACE, "PowerState");
 
                if (adapter->current_settings & MGMT_SETTING_POWERED) {
                        adapter_start(adapter);
@@ -635,6 +644,11 @@  static void new_settings_callback(uint16_t index, uint16_t length,
        if (settings == adapter->current_settings)
                return;
 
+       if ((adapter->current_settings & MGMT_SETTING_POWERED) !=
+           (settings & MGMT_SETTING_POWERED)) {
+               adapter->power_state_target = ADAPTER_POWER_STATE_TARGET_NONE;
+       }
+
        DBG("Settings: 0x%08x", settings);
 
        settings_changed(adapter, settings);
@@ -684,6 +698,11 @@  static bool set_mode(struct btd_adapter *adapter, uint16_t opcode,
        switch (opcode) {
        case MGMT_OP_SET_POWERED:
                setting = MGMT_SETTING_POWERED;
+               adapter->power_state_target = mode ?
+                       ADAPTER_POWER_STATE_TARGET_ON :
+                       ADAPTER_POWER_STATE_TARGET_OFF;
+               g_dbus_emit_property_changed(dbus_conn, adapter->path,
+                                               ADAPTER_INTERFACE, "PowerState");
                break;
        case MGMT_OP_SET_CONNECTABLE:
                setting = MGMT_SETTING_CONNECTABLE;
@@ -708,6 +727,12 @@  static bool set_mode(struct btd_adapter *adapter, uint16_t opcode,
                                set_mode_complete, adapter, NULL) > 0)
                return true;
 
+       if (setting == MGMT_SETTING_POWERED) {
+               /* cancel the earlier setting */
+               adapter->power_state_target = ADAPTER_POWER_STATE_TARGET_NONE;
+               g_dbus_emit_property_changed(dbus_conn, adapter->path,
+                                               ADAPTER_INTERFACE, "PowerState");
+       }
        btd_error(adapter->dev_id, "Failed to set mode for index %u",
                                                        adapter->dev_id);
 
@@ -2878,6 +2903,7 @@  static gboolean property_get_mode(struct btd_adapter *adapter,
 struct property_set_data {
        struct btd_adapter *adapter;
        GDBusPendingPropertySet id;
+       uint32_t setting;
 };
 
 static void property_set_mode_complete(uint8_t status, uint16_t length,
@@ -2888,6 +2914,9 @@  static void property_set_mode_complete(uint8_t status, uint16_t length,
 
        DBG("%s (0x%02x)", mgmt_errstr(status), status);
 
+       if (data->setting == MGMT_SETTING_POWERED)
+               adapter->power_state_target = ADAPTER_POWER_STATE_TARGET_NONE;
+
        if (status != MGMT_STATUS_SUCCESS) {
                const char *dbus_err;
 
@@ -3025,6 +3054,15 @@  static void property_set_mode(struct btd_adapter *adapter, uint32_t setting,
 
        data->adapter = adapter;
        data->id = id;
+       data->setting = setting;
+
+       if (setting == MGMT_SETTING_POWERED) {
+               adapter->power_state_target = mode ?
+                       ADAPTER_POWER_STATE_TARGET_ON :
+                       ADAPTER_POWER_STATE_TARGET_OFF;
+               g_dbus_emit_property_changed(dbus_conn, adapter->path,
+                                               ADAPTER_INTERFACE, "PowerState");
+       }
 
        if (mgmt_send(adapter->mgmt, opcode, adapter->dev_id, len, param,
                        property_set_mode_complete, data, g_free) > 0)
@@ -3062,6 +3100,29 @@  static void property_set_powered(const GDBusPropertyTable *property,
        property_set_mode(adapter, MGMT_SETTING_POWERED, iter, id);
 }
 
+static gboolean property_get_power_state(const GDBusPropertyTable *property,
+                                       DBusMessageIter *iter, void *user_data)
+{
+       struct btd_adapter *adapter = user_data;
+       const char *str;
+
+       if (adapter->power_state_target == ADAPTER_POWER_STATE_TARGET_NONE) {
+               if (adapter->current_settings & MGMT_SETTING_POWERED)
+                       str = "on";
+               else
+                       str = "off";
+       } else {
+               if (adapter->power_state_target == ADAPTER_POWER_STATE_TARGET_ON)
+                       str = "turning-on";
+               else
+                       str = "turning-off";
+       }
+
+       dbus_message_iter_append_basic(iter, DBUS_TYPE_STRING, &str);
+
+       return TRUE;
+}
+
 static gboolean property_get_discoverable(const GDBusPropertyTable *property,
                                        DBusMessageIter *iter, void *user_data)
 {
@@ -3700,6 +3761,7 @@  static const GDBusPropertyTable adapter_properties[] = {
        { "Alias", "s", property_get_alias, property_set_alias },
        { "Class", "u", property_get_class },
        { "Powered", "b", property_get_powered, property_set_powered },
+       { "PowerState", "s", property_get_power_state },
        { "Discoverable", "b", property_get_discoverable,
                                        property_set_discoverable },
        { "DiscoverableTimeout", "u", property_get_discoverable_timeout,
@@ -5506,6 +5568,8 @@  static void adapter_start(struct btd_adapter *adapter)
 {
        g_dbus_emit_property_changed(dbus_conn, adapter->path,
                                                ADAPTER_INTERFACE, "Powered");
+       g_dbus_emit_property_changed(dbus_conn, adapter->path,
+                                               ADAPTER_INTERFACE, "PowerState");
 
        DBG("adapter %s has been enabled", adapter->path);
 
@@ -7249,6 +7313,8 @@  static void adapter_stop(struct btd_adapter *adapter)
 
        g_dbus_emit_property_changed(dbus_conn, adapter->path,
                                                ADAPTER_INTERFACE, "Powered");
+       g_dbus_emit_property_changed(dbus_conn, adapter->path,
+                                               ADAPTER_INTERFACE, "PowerState");
 
        DBG("adapter %s has been disabled", adapter->path);
 }