Closed ndarilek closed 3 years ago
@ndarilek Hi there. Can you share some sample code so we can understand the context you're working in? Then we'll have a better idea of what's available to you. (e.g. Is this a pure win32 desktop app? uwp? etc.)
You can use the cast method to query for an interface.
I know implementing COM interfaces isn't supported yet
Support for implementing COM interfaces is now available.
This is a Bevy game, running in a window presumably created by WGPU.
I have this code which seems to correctly detect if my game is a trial or not. Ignore the bit about the demo feature--that is used to enable demo functionality outside the Microsoft Store, which gets the full non-demo version and should upgrade via IAP.
pub fn is_demo() -> bool {
if cfg!(feature = "demo") {
return true;
}
#[cfg(windows)]
{
if let Ok(context) = StoreContext::GetDefault() {
if let Ok(license) = context.GetAppLicenseAsync() {
if let Ok(license) = license.get() {
if let Ok(trial) = license.IsTrial() {
trial
} else {
false
}
} else {
false
}
} else {
false
}
} else {
false
}
}
#[cfg(not(windows))]
false
}
Yeah, can certainly be optimized, but I kind of expected it not to work so wrote it verbosely. :)
Then this code makes the purchase when a button is clicked, but doesn't work:
pub fn purchase_upgrade() {
#[cfg(windows)]
{
if let Ok(context) = StoreContext::GetDefault() {
if let Err(e) = context.RequestPurchaseAsync(MICROSOFT_STORE_ID) {
eprintln!("Error purchasing product: {:?}", e);
}
}
}
}
But that code fails to run. Unfortunately I'm having a tough time debugging this because I essentially have to round-trip through approval and certification to test this in a store context, my last version used error!
, and I'm speculating that perhaps error logging isn't set up in release builds. I'll see if this eprintln triggers instead. But for the time being I'm speculating that perhaps the window handle needs to be registered. I'm new enough to the win32 APIs to not be sure whether or not this failure case is relevant to me.
As an aside, I can apparently link locally built apps to their store installations and, after installing the store version once, debug issues like this in local builds. What should I google to figure out how to do that outside of a Visual Studio context? Is there a command line incantation to link a local appxmanifest/executable with the same version of that app installed through the store? I've searched, but don't even know what keywords to try and nothing has sent me anywhere useful.
Thanks.
Neat! I have been meaning to play with the Bevy game engine.
I don't have any experience working with the store - that seems hard to debug. 😨 Hopefully @riverar has some suggestions.
@ndarilek I just learned of Bevy and its Entity Component System (ECS) a few hours ago so this may be a little contrived and/or not idiomatic Bevy, but I think this will work for you. I strongly recommend opening an issue on Bevy asking them to expose their Window HWNDs for platform interoperability scenarios like this one. (They have access to it via winit
.)
The example below
MainWindowHandle
struct and adds it to the ECS Resource bagregister_main_window_handle
startup system that digs out the last event in the WindowCreated
event bag, assumes it's the primary app window, and uses Win32 GetActiveWindow
naively assuming there's an active window on the threadsimple
system that creates a dialog with a button that, when clicked, gets an instance of StoreContext
, goes through a QueryInterface dance via cast()
to get access to IInitializeWithWindow
methodscargo.toml
...
[dependencies]
bevy = "0.5"
bevy_egui = "0.7"
windows = "0.21.1"
[build-dependencies]
windows = "0.21.1"
build.rs
fn main() {
windows::build!({
Windows::Services::Store::StoreContext,
Windows::Win32::UI::Shell::IInitializeWithWindow,
Windows::Win32::UI::KeyboardAndMouseInput::GetActiveWindow
})
}
main.rs
#![windows_subsystem = "console"]
mod bindings {
windows::include_bindings!();
}
use bindings::Windows::Services::Store::*;
use bindings::Windows::Win32::Foundation::HWND;
use bindings::Windows::Win32::UI::KeyboardAndMouseInput::GetActiveWindow;
use bindings::Windows::Win32::UI::Shell::IInitializeWithWindow;
use bevy::{
prelude::*,
window::WindowCreated
};
use bevy_egui::{
egui::{self, Id},
EguiContext, EguiPlugin,
};
use windows::Interface;
pub struct MainWindowHandle(HWND);
fn main() {
App::build()
.add_plugins(DefaultPlugins)
.add_plugin(EguiPlugin)
.insert_resource(MainWindowHandle(HWND::default()))
.add_system(simple.system())
.add_startup_system(register_main_window_handle.system())
.run();
}
fn register_main_window_handle(
mut events: EventReader<WindowCreated>,
mut main_window_handle: ResMut<MainWindowHandle>,
) {
if let Some(event) = events.iter().last() {
main_window_handle.0 = unsafe { GetActiveWindow() };
}
}
fn simple(egui_context: ResMut<EguiContext>, main_window_handle: Res<MainWindowHandle>) {
egui::Window::new("Limited time offer!")
.id(Id::new("iap_window"))
.title_bar(true)
.show(egui_context.ctx(), |ui| {
if ui.button("Purchase now! (0.00 Ferris)").clicked() {
let store_context = StoreContext::GetDefault().unwrap();
let interop: IInitializeWithWindow = store_context.cast().unwrap();
if unsafe { interop.Initialize(main_window_handle.0) }.is_ok() {
// Do something with store_context here
// e.g. GetStoreProductForCurrentAppAsync()
}
}
});
}
I'll send an email internally to ask about the developer story for Windows.Services.Store.* APIs. Maybe there are some test hooks we can leverage? Pushing to the Store and waiting is frankly ridiculous.
Wow, thanks, that's way more than I expected. I'll see about packaging this into a Bevy plugin and publishing it so folks will have an easier time. I appreciate you looking into whether testing without round-tripping through the store is possible. Adding it to the existing documentation would also be immensely helpful. I've found command line oriented docs for other steps in this flow except for this one.
While I have your attention, can I hold onto the StoreContext
and stash that in a resource as well, so systems aren't having to wait on a future every time they need it? I guess I'm wondering if methods like GetAppLicenseAsync
get license state as it was when the context was initialized, or if they pick up changes? I know I can register an event handler, and I may do that too, but that's a bit more complicated than I need if these methods will pick up context changes as they're made. Maybe adding the license as a resource would work too.
Thanks again.
can I hold onto the StoreContext and stash that in a resource as well, so systems aren't having to wait on a future every time they need it?
Ah the initialize dance in the simple
system, yep stashing that should be fine and probably ideal.
Right, so you'd populate the resource in the startup system, and future
systems could directly get a Res<StoreContext>
or Res<License>
without needing to go through this dance each time. I just wasn't sure
if the retrieved context/license represented a single point in time, or
if it updated state based on purchase status and such.
Thanks, this helps a lot. Traveling today but I'll give it a shot on Monday and see what happens. Hopefully we can figure out a way to test things out without having to wait on certification round trips for each test. :)
Here's a facility (WindowsStoreProxy.xml) to work offline https://docs.microsoft.com/en-us/windows/uwp/monetize/in-app-purchases-and-trials-using-the-windows-applicationmodel-store-namespace#get-started-with-the-currentapp-and-currentappsimulator-classes
Haven't tried it yet but looks promising.
Going to close this now as there's no work for the windows
crate, but feel free to continue discussions. Just administrative overhead.
Thanks, I'm not sure that link is relevant. From the first section on that page:
Important The Windows.ApplicationModel.Store namespace is no longer being updated with new features. If your project targets Windows 10 Anniversary Edition (10.0; Build 14393) or a later release in Visual Studio (that is, you are targeting Windows 10, version 1607, or later), we recommend that you use the Windows.Services.Store namespace instead. For more information, see In-app purchases and trials. The Windows.ApplicationModel.Store namespace is not supported in Windows desktop applications that use the Desktop Bridge or in apps or games that use a development sandbox in Partner Center (for example, this is the case for any game that integrates with Xbox Live). These products must use the Windows.Services.Store namespace to implement in-app purchases and trials.
I'll see if I can get a build through certification tomorrow and test out your code. Thanks again for all the help!
@ndarilek Noticed that too, sent a few emails to get updated guidance. Will follow up if I hear back.
One more snag. I finally got my purchase flow to give me an error:
Error purchasing product: Error { code: 0x80070578, message: "This function must be called from a UI thread", win32_erro r: 1400 }
How do I run a closure or equivalent on the UI thread using these bindings? How does that work when there isn't an actual UI?
Thanks again.
Hitting a snag with 0.23.0:
[dependencies.windows]
version = "0.23"
features = [
"Foundation",
"Services_Store",
"Win32_UI",
"Win32_UI_KeyboardAndMouseInput",
"Win32_UI_Shell",
]
error[E0432]: unresolved import windows::Win32::UI::KeyboardAndMouseInput::GetActiveWindow
--> src\iap.rs:18:17
|
18 | Win32::UI::{KeyboardAndMouseInput::GetActiveWindow, Shell::IInitializeWithWindow},
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ no GetActiveWindow
in Win32::UI::KeyboardAndMouseInput
error[E0599]: no method named Initialize
found for struct IInitializeWithWindow
in the current scope
--> src\iap.rs:116:53
|
116 | ... if unsafe { interop.Initialize(GetActiveWindow()) }.is_ok() {
| ^^^^^^^^^^ method not found in IInitializeWithWindow
What features am I missing? Would be great if the docs included the needed feature for a given module/name. If they do then I'm missing it--at least in search results.
Thanks!
@ndarilek What you need for GetActiveWindow
is ["Win32_Foundation", "Win32_UI_Input_KeyboardAndMouse" ]
. The general workflow is:
The pre-generated code lives at https://github.com/microsoft/windows-rs/tree/master/src/Windows
Bubbling up all the required features via docs is something def on the radar and being worked on as you read this.
I have a Rust game I'm trying to add to the Microsoft Store. Currently it seems to correctly determine when it is running as a free trial, but I'm having trouble figuring out how to implement the purchase flow.
The solution seems to be documented here: https://docs.microsoft.com/en-us/windows/uwp/monetize/in-app-purchases-and-trials#desktop Specifically:
IInitializeWithWindow
in my app, but I'm not sure a) how to castStoreContext
to that and set the main window handle, or even if I can.MainWindowHandle
as per this line in the sample code:initWindow.Initialize(System.Diagnostics.Process.GetCurrentProcess().MainWindowHandle);
I know implementing COM interfaces isn't supported yet, but I'm not sure if that's what I need to pull this off. I'd really appreciate knowing whether or not this is even possible. :)
Thanks.