Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

OSD AoT library #1349

Draft
wants to merge 25 commits into
base: master
Choose a base branch
from

Conversation

Ace-Radom
Copy link
Contributor

@Ace-Radom Ace-Radom commented Jun 26, 2024

Add possibility for users to show an OSD always on top.

Description

This feature is (or should be) based on a C++/CLI library, using GDI+ for on-screen bitmap drawing. The library creates a pop-up window (with window style WS_POPUP), renders the bitmap and puts it in the window.

Currently it uses a external picture (eject.png) for testing.

This PR adds a new project LenovoLegionToolkit.Lib.AoTOSD into the solution. It contains the AoT OSD library. The C# interface of this library is NotificationWindowAoT under namespace LenovoLegionToolkit.Lib.AoTOSD.

There's only some things need to know:

  1. Unlike the NotificationWindow at the moment, NotificationWindowAoT should only be inited once in each run. This is because the AoT OSD window is registered with the constructor, and unregistered with the destructor of the class. Theoretically when we are sure that the class has already been destructed before it is constructed again, we're able to init NotificationWindowAoT class for more than once, but I don't recommend it. In my debug codes I just put it into the IoC container, and it worked well.

  2. Showing an new AoT OSD window is really simple: just create a managed bitmap (System.Drawing.Bitmap) and pass it to NotificationWindowAoT.Show(). The position and visible duration should also be passed at the same time. And then...there's actually no other thing to worry about. The library convert automatically the managed bitmap to a native C++ bitmap and is responsible for the memory management there.

  3. The new library requires LenovoLegionToolkit.Lib for the global logger.

TODO

  • basic window-base class & layered window
  • a native C++ interface for AoT OSD window
  • animation
  • display on multi-virtual-desktop
  • a C++/CLI interface which is easier for C# to use
  • a wrapper for global logger in C++/CLI
  • converting C# bitmap to native C++ bitmap
  • error handling
  • code refactoring
  • A trigger for user to choose the OSD should be shown whether in classic form / AoT
  • use make.bat to compile

Confirmed Issues

  • Currently the library is functionaly-correct only when it is called in a command-line program. When I test it within the CLI, the AoT OSD works pretty nice, but once it was added into WPF, something just went wrong. The OSD shows little bit smaller than it should be, and it's not AoT any more: I mean, when there's already one window gain the TopMost state, it doesn't work any more.

    I don't know if I'm able to handle this issue, since the project I referenced itself (3RVX) is also a command-line based software. With the creation of the pop-up window an instance handle should be passed to RegisterClassEx & CreateWindowEx API, and this handle is got from GetModuleHandle. Due to something I don't know, it seems that this handle must be a command-line-executable handle. If I cannot fix this bug in current architecture, maybe I'll try another way / a workaround. Nah it is not... I just forgot sth in the notification manager code.

    2024/6/27 update: issue solved.

  • Converting .Net bitmap (System.Drawing.Bitmap) to C++ native bitmap (Gdiplus::Bitmap) losts alpha channal.

    2024/6/28 update: issue solved.

@Ace-Radom
Copy link
Contributor Author

Status update: the bug has been solved, I'll begin code refactoring

@Ace-Radom Ace-Radom force-pushed the feat/osd_always_on_top branch from 9345fe5 to 005bedd Compare June 28, 2024 23:25
@Ace-Radom
Copy link
Contributor Author

Ace-Radom commented Jun 29, 2024

@BartoszCichecki Would you please help me with the compilation with make.bat? In IDE it works fine but I cannot compile it with the make script (on Github Action either, so I removed the proj ref for now). Other OSD AoT functions have been done.

@Ace-Radom Ace-Radom marked this pull request as ready for review June 29, 2024 01:13
@Ace-Radom
Copy link
Contributor Author

Okay after some research seems that the msbuild provided by .net doesn't support compiling C++/CLI project cuz lots of the files are missing. It needs the msbuild provided directly by VS to compile. I'm considering if I should directly rewrite this function in C#, or I should find another way to compile the library.

@BartoszCichecki
Copy link
Owner

I looked at the code, and it isn't much I think that will be problematic to migrate over to P/Invoke, so if building is a challenge, I can take this over (next week I will most likely have some time). It's up to you. No rush.

@Ace-Radom
Copy link
Contributor Author

I looked at the code, and it isn't much I think that will be problematic to migrate over to P/Invoke, so if building is a challenge, I can take this over (next week I will most likely have some time). It's up to you. No rush.

Yeah I actually have another idea, maybe we can use a WinForm window as the OSD container. In this way all codes would be written in C# and there should be no compile problems any more. I'll also give a try on P/Invoke again. (compilation problem...yeah classic C++ stuff)

@Ace-Radom
Copy link
Contributor Author

Ace-Radom commented Jul 4, 2024

After some tests, creating a WinForm window with the following CreateParams:

protected override CreateParams CreateParams
{
    get
    {
        CreateParams cp = base.CreateParams;
        unchecked
        {
            cp.Style |= (int)WS_POPUP;
        }
        cp.ExStyle |= WS_EX_TOOLWINDOW | WS_EX_NOACTIVATE | WS_EX_TOPMOST;
        return cp;
    }
}

does almost the same thing. But the problem is, as noticed in #1053, current OSD cannot show on multiple virtual desktops, and the WinForm OSD Container suffers most likely the same limitation (at least I don't know a simple way to move windows from one vdesktop to another).

With a WinForm OSD Container we don't need (I think, haven't tested yet) to make a bitmap view from the classic OSD window anymore, since WinForm already provides an ElementHost to integrate. But in this way, #1053 won't be solved and would still be a problem. Therefore I'll try with P/Invoke for now, if it is really hard to migrate (mostly because the class pointer required by WndProc & the callback function pointer required by RegisterClassEx, and also I'm not sure if the HBITMAP got from C# can work with native WINAPI calls), the WinForm OSD Container would be a second choice I think.

@Ace-Radom
Copy link
Contributor Author

@BartoszCichecki yeah...maybe you can take it over in your spare time, it's expected that I won't have enough time working on this in the next couple of weeks.

@Ace-Radom
Copy link
Contributor Author

I'm going to pick up this feature probably in the next few days, but in a different branch since the code and commits in this pr is really a mess. I'll let this pr open but will open a new one once I'm finished with this stuff.

@Ace-Radom
Copy link
Contributor Author

Ace-Radom commented Oct 1, 2024

Status update: still got stucked on migrating codes from C++ to C#. The migration of the very low-level StaticWndProc function is the biggest issue I cannot handle.

LRESULT CALLBACK Window::BasicWindow::StaticWndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) {
    BasicWindow* window;

    if (message == WM_CREATE)
    {
        window = (BasicWindow*)((LPCREATESTRUCT)lParam)->lpCreateParams;
        SetWindowLongPtr(hWnd, GWLP_USERDATA, (LONG_PTR)window);
    }
    else
    {
        window = (BasicWindow*)GetWindowLongPtr(hWnd, GWLP_USERDATA);
        if (!window)
        {
            return DefWindowProc(hWnd, message, wParam, lParam);
        }
    }
    return window->WndProc(hWnd, message, wParam, lParam);
}

When calling CreateWindowEx function, a this pointer of the current BasicWindow class (and same, its subclass) was transfered as the lParam. This is the standard way I know to process window events, but such thing just messed up when I tried to reproduce these codes with pure-C#. I'm not a C# specialist and I didn't find a clear way to go around.

But I found sth interesting that may help compiling the C++/CLI codes on remote. This is a bit complex, we may need to download & use VS2022 build tools on remote. This is just an idea I didn't prove, but it seems to be the only way I know for know to solve this problem.

Status update: seems to be a good choice.

@Ace-Radom Ace-Radom marked this pull request as draft October 1, 2024 19:59
@Ace-Radom Ace-Radom marked this pull request as ready for review October 2, 2024 20:06
@Ace-Radom Ace-Radom force-pushed the feat/osd_always_on_top branch 2 times, most recently from 22a131b to ac19c24 Compare October 2, 2024 23:29
@Ace-Radom
Copy link
Contributor Author

@BartoszCichecki FYI: the AoTOSD lib itself should be finished and compiling it on GitHub Actions isn't a problem any more. The thing is I almost rewrite the building batch file since dotnet publish doesn't work for C++/CLI, now using make.bat needs to provide an absolute path of the msbuild which works for C++/CLI. I hope it won't be an issue on your local dev env.

I'll still do some checks and see if I can still do some simplify on the codes and also, if you have few times recently I can also start with the WPF view part, which is the next step.

@Ace-Radom Ace-Radom marked this pull request as draft November 18, 2024 09:13
@Ace-Radom Ace-Radom mentioned this pull request Nov 18, 2024
1 task
@Ace-Radom Ace-Radom force-pushed the feat/osd_always_on_top branch from f49a62e to ac19c24 Compare November 28, 2024 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants