Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

import("//flutter/common/config.gni")
import("//flutter/shell/platform/config.gni")
import("//flutter/shell/platform/glfw/config.gni")
import("//flutter/testing/testing.gni")

# Whether to build the dartdevc sdk, libraries, and source files
Expand Down Expand Up @@ -140,6 +141,10 @@ group("flutter") {
if (is_linux) {
public_deps +=
[ "//flutter/shell/platform/linux:flutter_linux_unittests" ]
if (build_glfw_shell) {
public_deps +=
[ "//flutter/shell/platform/glfw:flutter_glfw_unittests" ]
}
}

if (is_mac) {
Expand Down
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -1284,6 +1284,8 @@ FILE: ../../../flutter/shell/platform/glfw/client_wrapper/plugin_registrar_glfw_
FILE: ../../../flutter/shell/platform/glfw/event_loop.cc
FILE: ../../../flutter/shell/platform/glfw/event_loop.h
FILE: ../../../flutter/shell/platform/glfw/flutter_glfw.cc
FILE: ../../../flutter/shell/platform/glfw/flutter_glfw_private.h
FILE: ../../../flutter/shell/platform/glfw/flutter_glfw_test.cc
FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.cc
FILE: ../../../flutter/shell/platform/glfw/glfw_event_loop.h
FILE: ../../../flutter/shell/platform/glfw/headless_event_loop.cc
Expand Down
19 changes: 19 additions & 0 deletions shell/platform/glfw/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//flutter/testing/testing.gni")

_public_headers = [ "public/flutter_glfw.h" ]

# Any files that are built by clients (client_wrapper code, library headers for
Expand Down Expand Up @@ -32,6 +34,7 @@ source_set("flutter_glfw") {
"event_loop.cc",
"event_loop.h",
"flutter_glfw.cc",
"flutter_glfw_private.h",
Comment thread
rokob marked this conversation as resolved.
Outdated
"glfw_event_loop.cc",
"glfw_event_loop.h",
"headless_event_loop.cc",
Expand Down Expand Up @@ -71,6 +74,22 @@ source_set("flutter_glfw") {
}
}

test_fixtures("flutter_glfw_fixtures") {
fixtures = []
}

executable("flutter_glfw_unittests") {
testonly = true
deps = [
":flutter_glfw_fixtures",
":flutter_glfw_headers",
":flutter_glfw",
"//flutter/shell/platform/embedder:embedder_headers",
"//flutter/testing",
]
sources = ["flutter_glfw_test.cc"]
Comment thread
rokob marked this conversation as resolved.
Outdated
}

copy("publish_headers_glfw") {
sources = _public_headers
outputs = [ "$root_out_dir/{{source_file_part}}" ]
Expand Down
122 changes: 122 additions & 0 deletions shell/platform/glfw/flutter_glfw.cc
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,14 @@
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <list>
#include <string>

#include "flutter/shell/platform/common/cpp/client_wrapper/include/flutter/plugin_registrar.h"
#include "flutter/shell/platform/common/cpp/incoming_message_dispatcher.h"
#include "flutter/shell/platform/common/cpp/path_utils.h"
#include "flutter/shell/platform/embedder/embedder.h"
#include "flutter/shell/platform/glfw/flutter_glfw_private.h"
#include "flutter/shell/platform/glfw/glfw_event_loop.h"
#include "flutter/shell/platform/glfw/headless_event_loop.h"
#include "flutter/shell/platform/glfw/key_event_handler.h"
Expand Down Expand Up @@ -690,6 +693,123 @@ static bool RunFlutterEngine(
return true;
}

const char* GetLocaleStringFromEnvironmentVariables(const char* language,
Comment thread
rokob marked this conversation as resolved.
Outdated
const char* lc_all,
const char* lc_messages,
const char* lang) {
const char* retval;
retval = language;
if ((retval != NULL) && (retval[0] != '\0')) {
return retval;
}
retval = lc_all;
if ((retval != NULL) && (retval[0] != '\0')) {
return retval;
}
retval = lc_messages;
if ((retval != NULL) && (retval[0] != '\0')) {
return retval;
}
retval = lang;
if ((retval != NULL) && (retval[0] != '\0')) {
return retval;
}

return NULL;
}

const char* GetLocaleStringFromEnvironment() {
Comment thread
rokob marked this conversation as resolved.
Outdated
return GetLocaleStringFromEnvironmentVariables(
getenv("LANGUAGE"), getenv("LC_ALL"), getenv("LC_MESSAGES"),
getenv("LANG"));
}

// Parse a locale into its components.
Comment thread
rokob marked this conversation as resolved.
Outdated
void ParseLocale(const std::string& locale,
std::string* language,
std::string* territory,
std::string* codeset,
std::string* modifier) {
Comment thread
rokob marked this conversation as resolved.
Outdated
// Locales are in the form "language[_territory][.codeset][@modifier]"
std::string::size_type end = locale.size();
std::string::size_type modifier_pos = locale.rfind('@');
if (modifier_pos != std::string::npos) {
*modifier = locale.substr(modifier_pos + 1, end - modifier_pos - 1);
end = modifier_pos;
}

std::string::size_type codeset_pos = locale.rfind('.');
if (codeset_pos != std::string::npos) {
*codeset = locale.substr(codeset_pos + 1, end - codeset_pos - 1);
end = codeset_pos;
}

std::string::size_type territory_pos = locale.rfind('_');
if (territory_pos != std::string::npos) {
*territory = locale.substr(territory_pos + 1, end - territory_pos - 1);
end = territory_pos;
}

*language = locale.substr(0, end);
}

std::vector<std::unique_ptr<FlutterLocale>> GetLocales(
const char* locale_string,
std::list<std::string>& locale_storage) {
if (!locale_string || locale_string[0] == '\0') {
locale_string = "C";

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why "C"?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I found somewhere in some posix docs about locales that "ISO C says that all programs start by default in the standard ‘C’ locale." And looking around at other things that parse these strings, they use "C" as the fallback.

}
std::istringstream locales_stream(locale_string);
std::vector<std::unique_ptr<FlutterLocale>> locales;
std::string s;
while (getline(locales_stream, s, ':')) {
locales.push_back(std::make_unique<FlutterLocale>());
std::string language, territory, codeset, modifier;
ParseLocale(s, &language, &territory, &codeset, &modifier);
FlutterLocale* locale = locales.back().get();
locale->struct_size = sizeof(FlutterLocale);
if (!language.empty()) {
locale_storage.push_back(language);
locale->language_code = locale_storage.back().c_str();
}
if (!territory.empty()) {
locale_storage.push_back(territory);
locale->country_code = locale_storage.back().c_str();
}
if (!codeset.empty()) {
locale_storage.push_back(codeset);
locale->script_code = locale_storage.back().c_str();
}
if (!modifier.empty()) {
locale_storage.push_back(modifier);
locale->variant_code = locale_storage.back().c_str();
}
}
return locales;
}

// Returns parsed locales fetched from the environment.
std::vector<std::unique_ptr<FlutterLocale>> GetLocalesFromEnvironment(
Comment thread
rokob marked this conversation as resolved.
Outdated
std::list<std::string>& locale_storage) {
return GetLocales(GetLocaleStringFromEnvironment(), locale_storage);
}

// Passes locale information to the Flutter engine.
static void SetUpLocales(FlutterDesktopEngineState* state) {
// Helper to extend lifetime of the strings passed to Flutter.
std::list<std::string> locale_storage;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This construction seems more confusing than just inlining the conversion to C strings (as we do on other platforms IIRC).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You need something to extend the lifetimes based on the calling convention, and this is basically how the other linux shell does it. I didn't think to look at the windows code, that is a much better way to handle it, I will mirror that over here.

std::vector<std::unique_ptr<FlutterLocale>> locales =
GetLocalesFromEnvironment(locale_storage);
FlutterLocale** locales_array =
reinterpret_cast<FlutterLocale**>(&locales[0]);
FlutterEngineResult result = FlutterEngineUpdateLocales(
state->flutter_engine, const_cast<const FlutterLocale**>(locales_array),
locales.size());
if (result != kSuccess) {
std::cerr << "Failed to set up Flutter locales." << std::endl;
}
}

// Populates |state|'s helper object fields that are common to normal and
// headless mode.
//
Expand All @@ -713,6 +833,8 @@ static void SetUpCommonEngineState(FlutterDesktopEngineState* state,
// System channel handler.
state->platform_handler = std::make_unique<flutter::PlatformHandler>(
state->internal_plugin_registrar->messenger(), window);

SetUpLocales(state);
}

bool FlutterDesktopInit() {
Expand Down
30 changes: 30 additions & 0 deletions shell/platform/glfw/flutter_glfw_private.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
// 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_GLFW_FLUTTER_GLFW_PRIVATE_H_
#define FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_

#include <list>
#include <memory>
#include <string>
#include <vector>

#include "flutter/shell/platform/embedder/embedder.h"

const char* GetLocaleStringFromEnvironmentVariables(const char* language,
const char* lc_all,
const char* lc_messages,
const char* lang);

void ParseLocale(const std::string& locale,
std::string* language,
std::string* territory,
std::string* codeset,
std::string* modifier);

std::vector<std::unique_ptr<FlutterLocale>> GetLocales(
const char* locale_string,
std::list<std::string>& locale_storage);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The reason there are multiple exposed helpers on Windows is that we can't easily mock out what the OS returns; that's not the case here, since you can easily set environment variables in tests, so I would expect there to only be one visible function for getting locales even if it has helper functions internally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was trying to not set environment variables in tests and instead make more pure functions for testability, but that is more noise than its worth here so I will collapse things.

#endif // FLUTTER_SHELL_PLATFORM_GLFW_FLUTTER_GLFW_PRIVATE_H_
138 changes: 138 additions & 0 deletions shell/platform/glfw/flutter_glfw_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
// 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/glfw/flutter_glfw_private.h"

#include "gtest/gtest.h"

TEST(FlutterGlfwTest, GetLocaleStringFromEnvironment) {
EXPECT_EQ(
GetLocaleStringFromEnvironmentVariables("sv:de", NULL, NULL, "sv_SE"),
"sv:de");
EXPECT_EQ(
GetLocaleStringFromEnvironmentVariables(NULL, "en_EN", NULL, "sv_SE"),
"en_EN");
EXPECT_EQ(
GetLocaleStringFromEnvironmentVariables(NULL, NULL, "de_DE", "sv_SE"),
"de_DE");
EXPECT_EQ(GetLocaleStringFromEnvironmentVariables(NULL, NULL, NULL, "sv_SE"),
"sv_SE");
EXPECT_EQ(GetLocaleStringFromEnvironmentVariables(NULL, NULL, NULL, NULL),
nullptr);
}

TEST(FlutterGlfwTest, ParseLocaleSimple) {
std::string s = "en";
std::string language, territory, codeset, modifier;
ParseLocale(s, &language, &territory, &codeset, &modifier);
EXPECT_EQ(language, "en");
EXPECT_EQ(territory, "");
EXPECT_EQ(codeset, "");
EXPECT_EQ(modifier, "");
}

TEST(FlutterGlfwTest, ParseLocaleWithTerritory) {
std::string s = "en_GB";
std::string language, territory, codeset, modifier;
ParseLocale(s, &language, &territory, &codeset, &modifier);
EXPECT_EQ(language, "en");
EXPECT_EQ(territory, "GB");
EXPECT_EQ(codeset, "");
EXPECT_EQ(modifier, "");
}

TEST(FlutterGlfwTest, ParseLocaleWithCodeset) {
std::string s = "zh_CN.UTF-8";
std::string language, territory, codeset, modifier;
ParseLocale(s, &language, &territory, &codeset, &modifier);
EXPECT_EQ(language, "zh");
EXPECT_EQ(territory, "CN");
EXPECT_EQ(codeset, "UTF-8");
EXPECT_EQ(modifier, "");
}

TEST(FlutterGlfwTest, ParseLocaleFull) {
std::string s = "en_GB.ISO-8859-1@euro";
std::string language, territory, codeset, modifier;
ParseLocale(s, &language, &territory, &codeset, &modifier);
EXPECT_EQ(language, "en");
EXPECT_EQ(territory, "GB");
EXPECT_EQ(codeset, "ISO-8859-1");
EXPECT_EQ(modifier, "euro");
}

TEST(FlutterGlfwTest, GetLocalesSimple) {
const char* locale_string = "en_US";
std::list<std::string> locale_storage;

std::vector<std::unique_ptr<FlutterLocale>> locales =
GetLocales(locale_string, locale_storage);

EXPECT_EQ(locales.size(), 1UL);

EXPECT_STREQ(locales[0]->language_code, "en");
EXPECT_STREQ(locales[0]->country_code, "US");
EXPECT_STREQ(locales[0]->script_code, nullptr);
EXPECT_STREQ(locales[0]->variant_code, nullptr);
}

TEST(FlutterGlfwTest, GetLocalesFull) {
const char* locale_string = "en_GB.ISO-8859-1@euro:en_US:sv:zh_CN.UTF-8";
std::list<std::string> locale_storage;

std::vector<std::unique_ptr<FlutterLocale>> locales =
GetLocales(locale_string, locale_storage);

EXPECT_EQ(locales.size(), 4UL);

EXPECT_STREQ(locales[0]->language_code, "en");
EXPECT_STREQ(locales[0]->country_code, "GB");
EXPECT_STREQ(locales[0]->script_code, "ISO-8859-1");
EXPECT_STREQ(locales[0]->variant_code, "euro");

EXPECT_STREQ(locales[1]->language_code, "en");
EXPECT_STREQ(locales[1]->country_code, "US");
EXPECT_STREQ(locales[1]->script_code, nullptr);
EXPECT_STREQ(locales[1]->variant_code, nullptr);

EXPECT_STREQ(locales[2]->language_code, "sv");
EXPECT_STREQ(locales[2]->country_code, nullptr);
EXPECT_STREQ(locales[2]->script_code, nullptr);
EXPECT_STREQ(locales[2]->variant_code, nullptr);

EXPECT_STREQ(locales[3]->language_code, "zh");
EXPECT_STREQ(locales[3]->country_code, "CN");
EXPECT_STREQ(locales[3]->script_code, "UTF-8");
EXPECT_STREQ(locales[3]->variant_code, nullptr);
}

TEST(FlutterGlfwTest, GetLocalesEmpty) {
const char* locale_string = "";
std::list<std::string> locale_storage;

std::vector<std::unique_ptr<FlutterLocale>> locales =
GetLocales(locale_string, locale_storage);

EXPECT_EQ(locales.size(), 1UL);

EXPECT_STREQ(locales[0]->language_code, "C");
EXPECT_STREQ(locales[0]->country_code, nullptr);
EXPECT_STREQ(locales[0]->script_code, nullptr);
EXPECT_STREQ(locales[0]->variant_code, nullptr);
}

TEST(FlutterGlfwTest, GetLocalesNull) {
const char* locale_string;
std::list<std::string> locale_storage;

std::vector<std::unique_ptr<FlutterLocale>> locales =
GetLocales(locale_string, locale_storage);

EXPECT_EQ(locales.size(), 1UL);

EXPECT_STREQ(locales[0]->language_code, "C");
EXPECT_STREQ(locales[0]->country_code, nullptr);
EXPECT_STREQ(locales[0]->script_code, nullptr);
EXPECT_STREQ(locales[0]->variant_code, nullptr);
}
1 change: 1 addition & 0 deletions testing/run_tests.py
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,7 @@ def RunCCTests(build_dir, filter):

if IsLinux():
RunEngineExecutable(build_dir, 'flutter_linux_unittests', filter, shuffle_flags)
RunEngineExecutable(build_dir, 'flutter_glfw_unittests', filter, shuffle_flags)


def RunEngineBenchmarks(build_dir, filter):
Expand Down