B4J Question Detect laptop lid close and run b4j app

How to detect laptop (Windows/Mac OS) lid close event in b4j ? I want to run a b4j app on lid close instead of the default 'sleep'/shutdown options shown in laptop's power setting.
 

Magma

Expert
Licensed User
Longtime User
...I don't know what exactly you want to do - but you can read the events before and after...

because if a computer/windows go to sleep/shutdown... you can't do anything this time... you can;t run something if it is powered off...

with this command you can get the 10 latest events (/c:num /rd:true ->reverse to get latest /f:text (text without this /f:text you get it in xml)):
B4X:
wevtutil qe System /c:10 /rd:true /f:text
ps: so with jshell you can do more... i think
 
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
My response to this thread is a little late, but it took some time to figure this out.

I needed this same function for my laptop app.
I use my laptop as a console for my music collection app as well as other more complex tasks.
I like my lid closed for music, but the when I open it I would like the music to fade and my other work to show on the screen.

I found C# .Net code to do this and it worked, but to integrate it into B4J was a little more complicated.

https://stackoverflow.com/questions/3355606/detect-laptop-lid-closure-and-opening

The attached is how I did it. It won't work on MacOS of course.

The Log output of the attached example:
→Logging Started
→2022-05-27 11:22:20 AM: OPEN
→Last change: 2022-05-27 11:22:20 AM: OPEN
→2022-05-27 11:22:25 AM: CLOSED
→Logging Stopped

The LaptopLidLogger Class (in attached example) takes care of the precise sequence needed for this to work.

B4X:
'NOTES:
'in Tools set system->power & sleep->additional power settings->choose what to do when lid closes->Nothing

Sub Class_Globals
    Private Root As B4XView
    Private xui As XUI
    Private LLL As LaptopLidLogger
End Sub

Public Sub Initialize
'    B4XPages.GetManager.LogEvents = True
End Sub

Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("MainPage")
    LLL.Initialize(Me)
End Sub

Private Sub Button1_Click
    LLL.StartLogging
    Sleep(5000)
    Log("Last change: " & LLL.LastChange)
    Sleep(5000)
    LLL.StopLogging
End Sub

'NOTE: the LaptopLidlogger polls the status once per second (LLL.PollTimer(1000))
Public Sub Lid_Change(status As String)
    Log(status)
End Sub

You need Visual Studio set for WpfApplication and C# to modify the code below.
B4X:
//Modified C# Code From https://stackoverflow.com/questions/3355606/detect-laptop-lid-closure-and-opening

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.IO;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        [DllImport(@"User32", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
        private static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);
        private System.Windows.Threading.DispatcherTimer dispatcherTimer;
        internal struct POWERBROADCAST_SETTING
        {
            public Guid PowerSetting;
            public uint DataLength;
            public byte Data;
        }

        Guid GUID_LIDSWITCH_STATE_CHANGE = new Guid(0xBA3E0F4D, 0xB817, 0x4094, 0xA2, 0xD1, 0xD5, 0x63, 0x79, 0xE6, 0xA0, 0xF3);
        const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
        const int WM_POWERBROADCAST = 0x0218;
        const int PBT_POWERSETTINGCHANGE = 0x8013;

        private bool? _previousLidState = null;

        public MainWindow()
        {
            if (!File.Exists("LidLogger.txt"))
            {
                Debug.WriteLine("{0}: File does not exist 1!", DateTime.Now);
                this.Close();
            }

            InitializeComponent();
            this.SourceInitialized += MainWindow_SourceInitialized;
       
            dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
            dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
            dispatcherTimer.Interval = new TimeSpan(0, 0, 2);
            dispatcherTimer.Start();
        }

        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            if (!File.Exists("LidLogger.txt"))
            {
                dispatcherTimer.Stop();
                Debug.WriteLine("{0}: File does not exist 3!", DateTime.Now);
                this.Close();
            }
        }

        void MainWindow_SourceInitialized(object sender, EventArgs e)
        {
            RegisterForPowerNotifications();
            IntPtr hwnd = new WindowInteropHelper(this).Handle;
            HwndSource.FromHwnd(hwnd).AddHook(new HwndSourceHook(WndProc));
        }

        private void RegisterForPowerNotifications()
        {
            IntPtr handle = new WindowInteropHelper(this).Handle;
            IntPtr hLIDSWITCHSTATECHANGE = RegisterPowerSettingNotification(handle, ref GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE);
        }

        IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
                case WM_POWERBROADCAST:
                    OnPowerBroadcast(wParam, lParam);
                    break;
                default:
                    break;
            }
            return IntPtr.Zero;
        }

        private void OnPowerBroadcast(IntPtr wParam, IntPtr lParam)
        {
            if (!File.Exists("LidLogger.txt"))
            {
                Debug.WriteLine("{0}: File does not exist 2", DateTime.Now);
                this.Close();
            }
            if ((int)wParam == PBT_POWERSETTINGCHANGE)
            {
                POWERBROADCAST_SETTING ps = (POWERBROADCAST_SETTING)Marshal.PtrToStructure(lParam, typeof(POWERBROADCAST_SETTING));
                IntPtr pData = (IntPtr)((long)lParam + Marshal.SizeOf(ps));
                Int32 iData = (Int32)Marshal.PtrToStructure(pData, typeof(Int32));
                if (ps.PowerSetting == GUID_LIDSWITCH_STATE_CHANGE)
                {
                    bool isLidOpen = ps.Data != 0;

                    if (!isLidOpen == _previousLidState)
                    {
                        LidStatusChanged(isLidOpen);
                    }
                    _previousLidState = isLidOpen;
                }
            }
        }

        private void LidStatusChanged(bool isLidOpen)
        {
            if (isLidOpen)
            {
                File.AppendAllText("LidLogger.txt", $"{DateTime.Now}: OPEN\n");
            }
            else
            {
                File.AppendAllText("LidLogger.txt", $"{DateTime.Now}: CLOSED\n");
            }
        }
    }
}
 

Attachments

  • LLLogger.zip
    171.8 KB · Views: 79
Last edited:
Upvote 0

William Lancee

Well-Known Member
Licensed User
Longtime User
@Magma

If the Choice is to keep the Laptop working while closed...
[Additional Power Settings->Choose what happens when lid closes->Nothing]

Opening and closing the lid is not added to the power report, so that simpler approach doesn't work.
After closing and opening the lid, the report is unchanged.
 
Upvote 0

Magma

Expert
Licensed User
Longtime User
My response to this thread is a little late, but it took some time to figure this out.

I needed this same function for my laptop app.
I use my laptop as a console for my music collection app as well as other more complex tasks.
I like my lid closed for music, but the when I open it I would like the music to fade and my other work to show on the screen.

I found C# .Net code to do this and it worked, but to integrate it into B4J was a little more complicated.

https://stackoverflow.com/questions/3355606/detect-laptop-lid-closure-and-opening

The attached is how I did it. It won't work on MacOS of course.

The Log output of the attached example:
→Logging Started
→2022-05-27 11:22:20 AM: OPEN
→Last change: 2022-05-27 11:22:20 AM: OPEN
→2022-05-27 11:22:25 AM: CLOSED
→Logging Stopped

The LaptopLidLogger Class (in attached example) takes care of the precise sequence needed for this to work.

B4X:
'NOTES:
'in Tools set system->power & sleep->additional power settings->choose what to do when lid closes->Nothing

Sub Class_Globals
    Private Root As B4XView
    Private xui As XUI
    Private LLL As LaptopLidLogger
End Sub

Public Sub Initialize
'    B4XPages.GetManager.LogEvents = True
End Sub

Private Sub B4XPage_Created (Root1 As B4XView)
    Root = Root1
    Root.LoadLayout("MainPage")
    LLL.Initialize(Me)
End Sub

Private Sub Button1_Click
    LLL.StartLogging
    Sleep(5000)
    Log("Last change: " & LLL.LastChange)
    Sleep(5000)
    LLL.StopLogging
End Sub

'NOTE: the LaptopLidlogger polls the status once per second (LLL.PollTimer(1000))
Public Sub Lid_Change(status As String)
    Log(status)
End Sub

You need Visual Studio set for WpfApplication and C# to modify the code below.
B4X:
//Modified C# Code From https://stackoverflow.com/questions/3355606/detect-laptop-lid-closure-and-opening

using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using System.Windows;
using System.Windows.Interop;
using System.IO;

namespace WpfApplication1
{
    public partial class MainWindow : Window
    {
        [DllImport(@"User32", SetLastError = true, EntryPoint = "RegisterPowerSettingNotification", CallingConvention = CallingConvention.StdCall)]
        private static extern IntPtr RegisterPowerSettingNotification(IntPtr hRecipient, ref Guid PowerSettingGuid, Int32 Flags);
        private System.Windows.Threading.DispatcherTimer dispatcherTimer;
        internal struct POWERBROADCAST_SETTING
        {
            public Guid PowerSetting;
            public uint DataLength;
            public byte Data;
        }

        Guid GUID_LIDSWITCH_STATE_CHANGE = new Guid(0xBA3E0F4D, 0xB817, 0x4094, 0xA2, 0xD1, 0xD5, 0x63, 0x79, 0xE6, 0xA0, 0xF3);
        const int DEVICE_NOTIFY_WINDOW_HANDLE = 0x00000000;
        const int WM_POWERBROADCAST = 0x0218;
        const int PBT_POWERSETTINGCHANGE = 0x8013;

        private bool? _previousLidState = null;

        public MainWindow()
        {
            if (!File.Exists("LidLogger.txt"))
            {
                Debug.WriteLine("{0}: File does not exist 1!", DateTime.Now);
                this.Close();
            }

            InitializeComponent();
            this.SourceInitialized += MainWindow_SourceInitialized;
       
            dispatcherTimer = new System.Windows.Threading.DispatcherTimer();
            dispatcherTimer.Tick += new EventHandler(dispatcherTimer_Tick);
            dispatcherTimer.Interval = new TimeSpan(0, 0, 2);
            dispatcherTimer.Start();
        }

        private void dispatcherTimer_Tick(object sender, EventArgs e)
        {
            if (!File.Exists("LidLogger.txt"))
            {
                dispatcherTimer.Stop();
                Debug.WriteLine("{0}: File does not exist 3!", DateTime.Now);
                this.Close();
            }
        }

        void MainWindow_SourceInitialized(object sender, EventArgs e)
        {
            RegisterForPowerNotifications();
            IntPtr hwnd = new WindowInteropHelper(this).Handle;
            HwndSource.FromHwnd(hwnd).AddHook(new HwndSourceHook(WndProc));
        }

        private void RegisterForPowerNotifications()
        {
            IntPtr handle = new WindowInteropHelper(this).Handle;
            IntPtr hLIDSWITCHSTATECHANGE = RegisterPowerSettingNotification(handle, ref GUID_LIDSWITCH_STATE_CHANGE, DEVICE_NOTIFY_WINDOW_HANDLE);
        }

        IntPtr WndProc(IntPtr hwnd, int msg, IntPtr wParam, IntPtr lParam, ref bool handled)
        {
            switch (msg)
            {
                case WM_POWERBROADCAST:
                    OnPowerBroadcast(wParam, lParam);
                    break;
                default:
                    break;
            }
            return IntPtr.Zero;
        }

        private void OnPowerBroadcast(IntPtr wParam, IntPtr lParam)
        {
            if (!File.Exists("LidLogger.txt"))
            {
                Debug.WriteLine("{0}: File does not exist 2", DateTime.Now);
                this.Close();
            }
            if ((int)wParam == PBT_POWERSETTINGCHANGE)
            {
                POWERBROADCAST_SETTING ps = (POWERBROADCAST_SETTING)Marshal.PtrToStructure(lParam, typeof(POWERBROADCAST_SETTING));
                IntPtr pData = (IntPtr)((long)lParam + Marshal.SizeOf(ps));
                Int32 iData = (Int32)Marshal.PtrToStructure(pData, typeof(Int32));
                if (ps.PowerSetting == GUID_LIDSWITCH_STATE_CHANGE)
                {
                    bool isLidOpen = ps.Data != 0;

                    if (!isLidOpen == _previousLidState)
                    {
                        LidStatusChanged(isLidOpen);
                    }
                    _previousLidState = isLidOpen;
                }
            }
        }

        private void LidStatusChanged(bool isLidOpen)
        {
            if (isLidOpen)
            {
                File.AppendAllText("LidLogger.txt", $"{DateTime.Now}: OPEN\n");
            }
            else
            {
                File.AppendAllText("LidLogger.txt", $"{DateTime.Now}: CLOSED\n");
            }
        }
    }
}
Brilliant 👏... I didn't see you found a solution...
 
Upvote 0
Top