Is it possible to simulate mouse clicks on a non-visible form running inside a Windows service?

0

We are working on a Windows service in .NET in which we would like to use behavior of a COM control that can only be initiated by a mouse click.

To this extend, we have created a service that creates a non-visible form and starts a message pump to allow interacting with the form. We are able to communicate with the form and its controls by Windows message (e.g. BM_CLICK works just fine), but we have not been able to simulate a mouse click using messages WM_LBUTTONDOWN or WM_LBUTTONUP.

The code below outputs Clicked on (100,100) when started as a normal process, but the click is not detected when the code is started as a service. Does anybody have an idea about what is wrong with our code or is it simply a limitation of code running inside a service context?

Please note that we are not trying to access controls outside of our own Windows context.

    static class Program
    {
        private static void Main()
        {
            var service = new Service();

            if (Environment.UserInteractive)
            {
                Logger.Info("Starting as interactive.");

                service.Start();

                Console.WriteLine("Press a key to stop.");
                Console.ReadKey(true);

                service.Shutdown();
            }
            else
            {
                Logger.Info("Starting as service.");

                ServiceBase.Run(new ServiceBase[] {service});
            }
        }
    }

    public partial class Service : ServiceBase
    {
        public Service()
        {
            InitializeComponent();
        }

        [DllImport("User32.dll")]
        public static extern Int32 SendMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("user32.dll")]
        public static extern bool PostMessage(IntPtr hWnd, int Msg, int wParam, IntPtr lParam);

        private const int WM_LBUTTONDOWN = 0x201;
        private const int WM_LBUTTONUP = 0x202;

        protected override void OnStart(string[] args)
        {
            base.OnStart(args);

            Start();
        }

        private static void Log(string s)
        {
            var path = Path.Combine(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), "output.txt");
            File.AppendAllLines(path, new[] {s});
        }

        private static IntPtr CreateLParam(int x, int y)
        {
            return (IntPtr)((y << 16) | (x & 0xffff));
        }

        public void Start()
        {
            Log("Start");

            try
            {
                var handle = IntPtr.Zero;

                var thread = new Thread(o =>
                {
                    var form = new Form {Left = 0, Top = 0, Width = 1000, Height = 1000};
                    handle = form.Handle;
                    form.MouseClick += (sender, eventArgs) => Log($"Clicked on ({eventArgs.X},{eventArgs.Y})");
                    Application.Run(form);
                });
                thread.SetApartmentState(ApartmentState.STA);
                thread.IsBackground = true;
                thread.Start();

                Thread.Sleep(2000); // sleep 2 seconds

                var lParam = CreateLParam(100, 100);

                SendMessage(handle, WM_LBUTTONDOWN, 0x00000000, lParam);
                SendMessage(handle, WM_LBUTTONUP, 0x00000000, lParam);

                PostMessage(handle, WM_LBUTTONDOWN, 0x00000000, lParam);
                PostMessage(handle, WM_LBUTTONUP, 0x00000000, lParam);
            }
            catch (Exception ex)
            {
                Log("Exception: " + ex.Message);
            }
        }

        public void Shutdown()
        {
        }
    }
c#
.net
winapi
service
asked on Stack Overflow Sep 28, 2020 by user987456

1 Answer

0

The answers to this question is that it depends on what is considered a simulation of a mouse click.

If simulating a mouse click means sending a BM_CLICK to a button, the answer is 'Yes'. This will trigger the OnClick event of the button and the associated logic.

If simulating a mouse click means sending WM_LBUTTONDOWN or WM_LBUTTONUP messages to controls created inside the service context and then having the control respond to those messages, then the answer is also 'Yes'. There doesn't seem to be an inherent restriction in how Windows deals with services that prevents these messages from being sent or received.

How a control processes these received messages is a different question. The comment from @SimonMourier showed that sometimes additional logic is needed to obtain the same behavior as when the code was executing in a normal non-service context.

In our use case, we are trying to trigger mouse click behavior on an RDP client and we haven't been able to do that yet. It seems that this control uses IME to capture user input and I will create a separate post for this question.

answered on Stack Overflow Sep 29, 2020 by user987456

User contributions licensed under CC BY-SA 3.0