Skip to content

Commit

Permalink
Add uwptool.exe (flutter#26038)
Browse files Browse the repository at this point in the history
uwptool is a helper tool used by the flutter command-line tool. It can
be thought of as analogous to adb for Android or libimobiledevice for
iOS.

Its core functions are:
* List installed apps. (included in this patch)
* Launch an app with a set of launch arguments. (included in this patch)
* Install an app (included in a later patch).
* Uninstall an app (included in a later patch).

Part of flutter/flutter#81756
  • Loading branch information
cbracken authored May 11, 2021
1 parent a5da502 commit 807460a
Show file tree
Hide file tree
Showing 7 changed files with 282 additions and 2 deletions.
4 changes: 4 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1605,6 +1605,10 @@ FILE: ../../../flutter/shell/platform/windows/text_input_plugin.cc
FILE: ../../../flutter/shell/platform/windows/text_input_plugin.h
FILE: ../../../flutter/shell/platform/windows/text_input_plugin_delegate.h
FILE: ../../../flutter/shell/platform/windows/text_input_plugin_unittest.cc
FILE: ../../../flutter/shell/platform/windows/uwptool_main.cc
FILE: ../../../flutter/shell/platform/windows/uwptool_utils.cc
FILE: ../../../flutter/shell/platform/windows/uwptool_utils.h
FILE: ../../../flutter/shell/platform/windows/uwptool_utils_unittests.cc
FILE: ../../../flutter/shell/platform/windows/window_binding_handler.h
FILE: ../../../flutter/shell/platform/windows/window_binding_handler_delegate.h
FILE: ../../../flutter/shell/platform/windows/window_proc_delegate_manager_win32.cc
Expand Down
42 changes: 42 additions & 0 deletions shell/platform/windows/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -241,6 +241,7 @@ executable("flutter_windows_unittests") {

# Target-specific sources.
if (target_os == "winuwp") {
sources += [ "uwptool_utils_unittests.cc" ]
# TODO(clarkezone) add UWP tests
# https://github.com/flutter/flutter/issues/70197
} else {
Expand Down Expand Up @@ -279,6 +280,10 @@ executable("flutter_windows_unittests") {
"//flutter/testing",
"//third_party/rapidjson",
]

if (target_os == "winuwp") {
deps += [ ":uwptool_utils" ]
}
}

shared_library("flutter_windows_glfw") {
Expand All @@ -301,6 +306,7 @@ group("windows") {
deps = [
":flutter_windows_winuwp",
":publish_headers_windows",
":uwptool",
"//flutter/shell/platform/windows/client_wrapper:publish_wrapper_windows",
]
} else {
Expand All @@ -315,3 +321,39 @@ group("windows") {
deps += [ ":windows_glfw" ]
}
}

# uwptool utilities.
source_set("uwptool_utils") {
if (target_os == "winuwp") {
configs += [ ":cppwinrt_defs" ]
cflags = [ "/EHsc" ]
}

sources = [
"uwptool_utils.cc",
"uwptool_utils.h",
]

deps = [ ":registry" ]
}

# Command-line tool used by the flutter tool that serves a similar purpose to
# adb for Android or libimobiledevice for iOS apps. Supports installing,
# uninstalling, launching apps.
executable("uwptool") {
if (target_os == "winuwp") {
configs += [ ":cppwinrt_defs" ]
cflags = [ "/EHsc" ]
libs = [
"windowsapp.lib",
"user32.lib",
]
}

sources = [ "uwptool_main.cc" ]
deps = [
":string_conversion",
":uwptool_utils",
"//flutter/fml:command_line",
]
}
4 changes: 2 additions & 2 deletions shell/platform/windows/registry.cc
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,8 @@ std::vector<std::wstring> RegistryKey::GetSubKeyNames() const {
for (int i = 0; i < subkey_count; ++i) {
DWORD key_buf_size = max_key_buf_size;
auto key_buf = std::make_unique<wchar_t[]>(max_key_buf_size);
result = ::RegEnumKeyEx(key_, i, key_buf.get(), &key_buf_size, nullptr,
nullptr, nullptr, nullptr);
result = ::RegEnumKeyExW(key_, i, key_buf.get(), &key_buf_size, nullptr,
nullptr, nullptr, nullptr);
if (result == ERROR_SUCCESS) {
subkey_names.emplace_back(key_buf.get());
}
Expand Down
92 changes: 92 additions & 0 deletions shell/platform/windows/uwptool_main.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include <Windows.h>
#include <winrt/base.h>

#include <algorithm>
#include <iostream>
#include <sstream>
#include <string>
#include <vector>

#include "flutter/fml/command_line.h"
#include "flutter/shell/platform/windows/string_conversion.h"
#include "flutter/shell/platform/windows/uwptool_utils.h"

namespace {

// Prints a list of installed UWP apps to stdout.
void PrintInstalledApps() {
flutter::ApplicationStore app_store;
for (const flutter::Application& app : app_store.GetInstalledApplications()) {
std::wcout << app.GetPackageId() << std::endl;
}
}

// Launches the app installed on the system whose Application User Model ID is
// prefixed with app_id, with the specified arguments list.
//
// Returns -1 if no matching app, or multiple matching apps are found, or if
// the app fails to launch. Otherwise, the process ID of the launched app is
// returned.
int LaunchApp(const std::wstring_view app_id, const std::wstring_view args) {
flutter::Application app(app_id);
DWORD process_id = app.Launch(args);
if (process_id == -1) {
std::wcerr << L"Failed to launch app " << app.GetPackageId() << std::endl;
return 1;
}
return 0;
}

// Prints the command usage to stderr.
void PrintUsage() {
std::cerr << "usage: uwptool COMMAND [APP_ID]" << std::endl;
std::cerr << "commands:" << std::endl;
std::cerr << " listapps list installed applications" << std::endl;
std::cerr << " launch launch an application" << std::endl;
}

} // namespace

int main(int argc, char** argv) {
winrt::init_apartment();

auto command_line = fml::CommandLineFromArgcArgv(argc, argv);
if (command_line.positional_args().size() < 1) {
PrintUsage();
return 1;
}

const std::vector<std::string>& args = command_line.positional_args();
std::string command = args[0];
if (command == "listapps") {
PrintInstalledApps();
return 0;
} else if (command == "launch") {
if (command_line.positional_args().size() < 1) {
PrintUsage();
return 1;
}

// Get the package ID.
std::string package_id = args[1];

// Concatenate the remaining args, space-separated.
std::ostringstream app_args;
for (int i = 2; i < args.size(); ++i) {
app_args << args[i];
if (i < args.size() - 1) {
app_args << " ";
}
}
return LaunchApp(flutter::Utf16FromUtf8(package_id),
flutter::Utf16FromUtf8(app_args.str()));
}

std::cerr << "Unknown command: " << command << std::endl;
PrintUsage();
return 1;
}
60 changes: 60 additions & 0 deletions shell/platform/windows/uwptool_utils.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/platform/windows/uwptool_utils.h"

#include <Windows.h>
#include <Winreg.h>
#include <shobjidl_core.h>
#include <winrt/base.h>

#include <string>
#include <unordered_set>
#include <vector>

namespace flutter {

Application::Application(const std::wstring_view package_id)
: package_id_(package_id) {}

int Application::Launch(const std::wstring_view args) {
// Create the ApplicationActivationManager.
winrt::com_ptr<IApplicationActivationManager> activation_manager;
HRESULT hresult = ::CoCreateInstance(
CLSID_ApplicationActivationManager, nullptr, CLSCTX_INPROC_SERVER,
IID_IApplicationActivationManager, activation_manager.put_void());
if (FAILED(hresult)) {
return -1;
}

// Launch the application.
DWORD process_id;
ACTIVATEOPTIONS options = AO_NONE;
std::wstring app_user_model_id = package_id_ + L"!App";
hresult = activation_manager->ActivateApplication(
app_user_model_id.data(), args.data(), options, &process_id);
if (FAILED(hresult)) {
return -1;
}
return process_id;
}

std::vector<Application> ApplicationStore::GetInstalledApplications() {
constexpr wchar_t kMappingsKey[] =
L"\\Local Settings\\Software\\Microsoft\\Windows\\CurrentVersion"
L"\\AppModel\\Repository\\Families";
RegistryKey mappings_key(HKEY_CLASSES_ROOT, kMappingsKey, KEY_READ);
if (!mappings_key.IsValid()) {
return {};
}

std::unordered_set<std::wstring> package_ids;
for (const std::wstring& subkey_name : mappings_key.GetSubKeyNames()) {
package_ids.emplace(subkey_name);
}
std::vector<Application> apps(package_ids.begin(), package_ids.end());
return apps;
}

} // namespace flutter
47 changes: 47 additions & 0 deletions shell/platform/windows/uwptool_utils.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#ifndef FLUTTER_SHELL_PLATFORM_WINDOWS_UWPTOOL_UTILS_H_
#define FLUTTER_SHELL_PLATFORM_WINDOWS_UWPTOOL_UTILS_H_

#include <string>
#include <vector>

#include "flutter/shell/platform/windows/registry.h"

namespace flutter {

// A UWP application.
class Application {
public:
explicit Application(const std::wstring_view package_id);
Application(const Application& other) = default;
Application& operator=(const Application& other) = default;

// Returns the application user model ID.
std::wstring GetPackageId() const { return package_id_; }

// Launches the application with the specified list of launch arguments.
int Launch(const std::wstring_view args);

private:
std::wstring package_id_;
};

// The machine-local store of installed applications.
class ApplicationStore {
public:
ApplicationStore() = default;

// Prevent copying.
ApplicationStore(const ApplicationStore& other) = delete;
ApplicationStore& operator=(const ApplicationStore& other) = delete;

// Returns a list of all installed application user model IDs.
std::vector<Application> GetInstalledApplications();
};

} // namespace flutter

#endif // FLUTTER_SHELL_PLATFORM_WINDOWS_UWPTOOL_UTILS_H_
35 changes: 35 additions & 0 deletions shell/platform/windows/uwptool_utils_unittests.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "flutter/shell/platform/windows/uwptool_utils.h"

#include <algorithm>
#include <iostream>
#include <string>
#include <vector>

#include "gtest/gtest.h"

namespace flutter {
namespace testing {

// TODO(cbracken): write registry values to be tested, then cleanup, refactor
// to support a mock registry.
// https://github.com/flutter/flutter/issues/82095

// Verify that at least one Microsoft app (e.g. Microsoft.WindowsCalculator) is
// installed and can be found.
TEST(ApplicationStore, GetInstalledApplications) {
ApplicationStore app_store;
std::vector<Application> apps = app_store.GetInstalledApplications();
EXPECT_FALSE(apps.empty());

auto ms_pos = std::find_if(apps.begin(), apps.end(), [](const auto& app) {
return app.GetPackageId().rfind(L"Microsoft.", 0);
});
EXPECT_TRUE(ms_pos != apps.end());
}

} // namespace testing
} // namespace flutter

0 comments on commit 807460a

Please sign in to comment.