Add a sample UI for linux apps - lighting app (#24979)
* Initial version of a imgui UI in light app for linux.
Also fixed linux app shutdown to handle signals
by loop terminating instead of app killing.
* remove some fixme comments
* Fix unit tests ... with-ui variant was added
* Restyle
* Update text and remove some commented out code
* Restyle
---------
Co-authored-by: Andrei Litvin <andreilitvin@google.com>
diff --git a/.gitmodules b/.gitmodules
index b104291..d9f332d 100644
--- a/.gitmodules
+++ b/.gitmodules
@@ -289,3 +289,7 @@
path = third_party/libwebsockets/repo
url = https://github.com/warmcat/libwebsockets
platforms = linux,darwin,tizen
+[submodule "third_party/imgui/repo"]
+ path = third_party/imgui/repo
+ url = https://github.com/ocornut/imgui
+ platforms = linux
diff --git a/examples/common/QRCode/BUILD.gn b/examples/common/QRCode/BUILD.gn
index d7918f5..b876f48 100644
--- a/examples/common/QRCode/BUILD.gn
+++ b/examples/common/QRCode/BUILD.gn
@@ -21,7 +21,10 @@
static_library("QRCode") {
output_name = "libqrcode-common"
- sources = [ "repo/c/qrcodegen.c" ]
+ sources = [
+ "repo/c/qrcodegen.c",
+ "repo/c/qrcodegen.h",
+ ]
public_configs = [ ":qrcode-common_config" ]
diff --git a/examples/lighting-app/linux/BUILD.gn b/examples/lighting-app/linux/BUILD.gn
index 721f408..3b20243 100644
--- a/examples/lighting-app/linux/BUILD.gn
+++ b/examples/lighting-app/linux/BUILD.gn
@@ -16,6 +16,7 @@
import("${chip_root}/build/chip/tools.gni")
import("${chip_root}/src/app/common_flags.gni")
+import("${chip_root}/third_party/imgui/imgui.gni")
assert(chip_build_tools)
@@ -46,6 +47,18 @@
"${chip_root}/src/lib",
]
+ if (chip_examples_enable_imgui_ui) {
+ deps += [
+ "${chip_root}/examples/common/QRCode",
+ "${chip_root}/third_party/imgui",
+ ]
+
+ sources += [
+ "ui.cpp",
+ "ui.h",
+ ]
+ }
+
include_dirs = [ "include" ]
if (chip_enable_pw_rpc) {
diff --git a/examples/lighting-app/linux/main.cpp b/examples/lighting-app/linux/main.cpp
index 7813c59..a74aa5d 100644
--- a/examples/lighting-app/linux/main.cpp
+++ b/examples/lighting-app/linux/main.cpp
@@ -26,6 +26,10 @@
#include <lib/support/logging/CHIPLogging.h>
#include <platform/Linux/NetworkCommissioningDriver.h>
+#if defined(CHIP_IMGUI_ENABLED) && CHIP_IMGUI_ENABLED
+#include "ui.h"
+#endif
+
using namespace chip;
using namespace chip::app;
using namespace chip::app::Clusters;
@@ -81,7 +85,16 @@
}
LightingMgr().Init();
+
+#if defined(CHIP_IMGUI_ENABLED) && CHIP_IMGUI_ENABLED
+ example::Ui::Start();
+#endif
+
ChipLinuxAppMainLoop();
+#if defined(CHIP_IMGUI_ENABLED) && CHIP_IMGUI_ENABLED
+ example::Ui::Stop();
+#endif
+
return 0;
}
diff --git a/examples/lighting-app/linux/ui.cpp b/examples/lighting-app/linux/ui.cpp
new file mode 100644
index 0000000..32ff5f4
--- /dev/null
+++ b/examples/lighting-app/linux/ui.cpp
@@ -0,0 +1,370 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "ui.h"
+
+#include <Options.h> // examples/platform/linux/Options.h
+#include <app/server/OnboardingCodesUtil.h>
+#include <lib/support/logging/CHIPLogging.h>
+#include <platform/CHIPDeviceLayer.h>
+#include <platform/PlatformManager.h>
+#include <setup_payload/QRCodeSetupPayloadGenerator.h>
+#include <setup_payload/SetupPayload.h>
+
+#include <app-common/zap-generated/ids/Attributes.h>
+#include <app-common/zap-generated/ids/Clusters.h>
+#include <app/util/attribute-storage.h>
+
+#include <SDL.h>
+#include <SDL_opengl.h>
+#include <imgui.h>
+#include <imgui_impl_opengl3.h>
+#include <imgui_impl_sdl2.h>
+#include <qrcodegen.h>
+
+#include <atomic>
+#include <chrono>
+#include <semaphore.h>
+#include <thread>
+
+namespace example {
+namespace Ui {
+
+namespace {
+
+std::atomic<bool> gUiRunning{ false };
+
+class DeviceState
+{
+public:
+ DeviceState() { sem_init(&mChipLoopWaitSemaphore, 0 /* shared */, 0); }
+ ~DeviceState() { sem_destroy(&mChipLoopWaitSemaphore); }
+
+ // Initialize. MUST be called within the CHIP main loop as it
+ // loads startup data.
+ void Init();
+
+ // Use ImgUI to show the current state
+ void ShowUi();
+
+ // Fetches the current state from Ember
+ void UpdateState();
+
+private:
+ static constexpr int kQRCodeVersion = qrcodegen_VERSION_MAX;
+ static constexpr int kMaxQRBufferSize = qrcodegen_BUFFER_LEN_FOR_VERSION(kQRCodeVersion);
+
+ sem_t mChipLoopWaitSemaphore;
+
+ bool mHasQRCode = false;
+ uint8_t mQRData[kMaxQRBufferSize] = { 0 };
+
+ // light data:
+ bool mOnOff = false;
+
+ // Updates the data (run in the chip event loop)
+ void ChipLoopUpdate();
+
+ void InitQRCode();
+
+ // Run in CHIPMainLoop to access ember in a single threaded
+ // fashion
+ static void ChipLoopUpdateCallback(intptr_t self);
+};
+
+DeviceState gDeviceState;
+
+void DeviceState::Init()
+{
+ InitQRCode();
+}
+
+void DeviceState::InitQRCode()
+{
+
+ chip::PayloadContents payload = LinuxDeviceOptions::GetInstance().payload;
+ if (!payload.isValidQRCodePayload())
+ {
+ return;
+ }
+
+ char payloadBuffer[chip::QRCodeBasicSetupPayloadGenerator::kMaxQRCodeBase38RepresentationLength + 1];
+ chip::MutableCharSpan qrCode(payloadBuffer);
+
+ CHIP_ERROR err = GetQRCode(qrCode, payload);
+ if (err != CHIP_NO_ERROR)
+ {
+ ChipLogError(AppServer, "Failed to load QR code: %" CHIP_ERROR_FORMAT, err.Format());
+ return;
+ }
+
+ if (qrCode.size() > kMaxQRBufferSize)
+ {
+ ChipLogError(AppServer, "Insufficient qr code buffer size to encode");
+ return;
+ }
+
+ uint8_t tempAndData[kMaxQRBufferSize];
+ memcpy(tempAndData, qrCode.data(), qrCode.size());
+
+ mHasQRCode = qrcodegen_encodeBinary(tempAndData, qrCode.size(), mQRData, qrcodegen_Ecc_MEDIUM, qrcodegen_VERSION_MIN,
+ qrcodegen_VERSION_MAX, qrcodegen_Mask_AUTO, true);
+
+ if (!mHasQRCode)
+ {
+ ChipLogError(AppServer, "Failed to encode QR code");
+ return;
+ }
+}
+
+inline ImVec2 operator+(const ImVec2 & a, const ImVec2 & b)
+{
+ return ImVec2(a.x + b.x, a.y + b.y);
+}
+
+void DeviceState::ShowUi()
+{
+ ImGui::Begin("Light app");
+ ImGui::Text("Here is the current ember device state:");
+ ImGui::Checkbox("Light is ON", &mOnOff);
+ ImGui::End();
+
+ if (mHasQRCode)
+ {
+ ImGui::Begin("QR Code.");
+
+ ImDrawList * drawList = ImGui::GetWindowDrawList();
+
+ constexpr int kBorderSize = 35;
+ constexpr int kMinWindowSize = 200;
+ const int kQRCodeSize = qrcodegen_getSize(mQRData);
+
+ ImVec2 pos = ImGui::GetWindowPos();
+ ImVec2 size = ImGui::GetWindowSize();
+
+ if (size.y < kMinWindowSize)
+ {
+ size = ImVec2(kMinWindowSize, kMinWindowSize);
+ ImGui::SetWindowSize(size);
+ }
+
+ // Fill the entire window white, then figure out borders
+ drawList->AddRectFilled(pos, pos + size, IM_COL32_WHITE);
+
+ // add a border
+ if (size.x >= 2 * kBorderSize && size.y >= 2 * kBorderSize)
+ {
+ size.x -= 2 * kBorderSize;
+ size.y -= 2 * kBorderSize;
+ pos.x += kBorderSize;
+ pos.y += kBorderSize;
+ }
+
+ // create a square rectangle: keep only the smaller side and adjust the
+ // other
+ if (size.x > size.y)
+ {
+ pos.x += (size.x - size.y) / 2;
+ size.x = size.y;
+ }
+ else if (size.y > size.x)
+ {
+ pos.y += (size.y - size.x) / 2;
+ size.y = size.x;
+ }
+
+ const ImVec2 squareSize = ImVec2(size.x / static_cast<float>(kQRCodeSize), size.y / static_cast<float>(kQRCodeSize));
+
+ for (int y = 0; y < kQRCodeSize; ++y)
+ {
+ for (int x = 0; x < kQRCodeSize; ++x)
+ {
+ if (qrcodegen_getModule(mQRData, x, y))
+ {
+ ImVec2 placement =
+ ImVec2(pos.x + static_cast<float>(x) * squareSize.x, pos.y + static_cast<float>(y) * squareSize.y);
+ drawList->AddRectFilled(placement, placement + squareSize, IM_COL32_BLACK);
+ }
+ }
+ }
+
+ ImGui::End();
+ }
+}
+
+void DeviceState::ChipLoopUpdate()
+{
+ // This will contain a dimmable light
+ static constexpr chip::EndpointId kLightEndpointId = 1;
+
+ // TODO:
+ // - consider error checking
+ // - add more attributes to the display (color? brightness?)
+ {
+ uint8_t value;
+ emberAfReadServerAttribute(kLightEndpointId, chip::app::Clusters::OnOff::Id,
+ chip::app::Clusters::OnOff::Attributes::OnOff::Id, &value, sizeof(value));
+ mOnOff = (value != 0);
+ }
+}
+
+void DeviceState::ChipLoopUpdateCallback(intptr_t self)
+{
+ DeviceState * _this = reinterpret_cast<DeviceState *>(self);
+ _this->ChipLoopUpdate();
+ sem_post(&_this->mChipLoopWaitSemaphore); // notify complete
+}
+
+void DeviceState::UpdateState()
+{
+ chip::DeviceLayer::PlatformMgr().ScheduleWork(&ChipLoopUpdateCallback, reinterpret_cast<intptr_t>(this));
+ // ensure update is done when existing
+ if (sem_trywait(&mChipLoopWaitSemaphore) != 0)
+ {
+ if (!gUiRunning.load())
+ {
+ // UI should stop, no need to wait, probably chip main loop is stopped
+ return;
+ }
+ std::this_thread::yield();
+ }
+}
+
+void UiInit(SDL_GLContext * gl_context, SDL_Window ** window)
+{
+ if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_TIMER | SDL_INIT_GAMECONTROLLER) != 0)
+ {
+ ChipLogError(AppServer, "SDL Init Error: %s\n", SDL_GetError());
+ return;
+ }
+
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_FLAGS, 0);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_PROFILE_MASK, SDL_GL_CONTEXT_PROFILE_CORE);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MAJOR_VERSION, 3);
+ SDL_GL_SetAttribute(SDL_GL_CONTEXT_MINOR_VERSION, 0);
+#ifdef SDL_HINT_IME_SHOW_UI
+ SDL_SetHint(SDL_HINT_IME_SHOW_UI, "1");
+#endif
+
+ SDL_GL_SetAttribute(SDL_GL_DOUBLEBUFFER, 1);
+ SDL_GL_SetAttribute(SDL_GL_DEPTH_SIZE, 24);
+ SDL_GL_SetAttribute(SDL_GL_STENCIL_SIZE, 8);
+ SDL_WindowFlags window_flags = (SDL_WindowFlags)(SDL_WINDOW_OPENGL | SDL_WINDOW_RESIZABLE | SDL_WINDOW_ALLOW_HIGHDPI);
+ *window = SDL_CreateWindow("Dear ImGui SDL2+OpenGL3 example", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 1280, 720,
+ window_flags);
+ *gl_context = SDL_GL_CreateContext(*window);
+ SDL_GL_MakeCurrent(*window, *gl_context);
+ SDL_GL_SetSwapInterval(1); // Enable vsync
+
+ // Setup Dear ImGui context
+ IMGUI_CHECKVERSION();
+ ImGui::CreateContext();
+ ImGuiIO & io = ImGui::GetIO();
+ (void) io;
+ // io.ConfigFlags |= ImGuiConfigFlags_NavEnableKeyboard; // Enable Keyboard Controls
+ // io.ConfigFlags |= ImGuiConfigFlags_NavEnableGamepad; // Enable Gamepad Controls
+
+ // Setup Dear ImGui style
+ ImGui::StyleColorsDark();
+ // ImGui::StyleColorsLight();
+
+ // Setup Platform/Renderer backends
+ ImGui_ImplSDL2_InitForOpenGL(*window, *gl_context);
+ ImGui_ImplOpenGL3_Init("#version 130");
+}
+
+void UiShutdown(SDL_GLContext * gl_context, SDL_Window ** window)
+{
+ ImGui_ImplOpenGL3_Shutdown();
+ ImGui_ImplSDL2_Shutdown();
+ ImGui::DestroyContext();
+
+ SDL_GL_DeleteContext(*gl_context);
+ SDL_DestroyWindow(*window);
+ SDL_Quit();
+}
+
+void UiLoop()
+{
+ SDL_GLContext gl_context;
+ SDL_Window * window = nullptr;
+
+ UiInit(&gl_context, &window);
+
+ ImGuiIO & io = ImGui::GetIO();
+
+ while (gUiRunning.load())
+ {
+ SDL_Event event;
+ while (SDL_PollEvent(&event))
+ {
+ ImGui_ImplSDL2_ProcessEvent(&event);
+ if (event.type == SDL_QUIT)
+ {
+ chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
+ }
+ if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_CLOSE &&
+ event.window.windowID == SDL_GetWindowID(window))
+ {
+ chip::DeviceLayer::PlatformMgr().StopEventLoopTask();
+ }
+ }
+
+ ImGui_ImplOpenGL3_NewFrame();
+ ImGui_ImplSDL2_NewFrame();
+ ImGui::NewFrame();
+
+ gDeviceState.UpdateState();
+ gDeviceState.ShowUi();
+
+ // rendering
+ ImGui::Render();
+ glViewport(0, 0, (int) io.DisplaySize.x, (int) io.DisplaySize.y);
+ glClearColor(0, 0, 0, 0);
+ glClear(GL_COLOR_BUFFER_BIT);
+ ImGui_ImplOpenGL3_RenderDrawData(ImGui::GetDrawData());
+ SDL_GL_SwapWindow(window);
+ }
+
+ UiShutdown(&gl_context, &window);
+
+ ChipLogProgress(AppServer, "UI thread Stopped...");
+}
+
+static std::thread gUiThread;
+
+} // namespace
+
+void Start()
+{
+ // Init inside the "main" thread, so that it can access globals
+ // proparly (for QR code and such)
+ gDeviceState.Init();
+
+ gUiRunning = true;
+ std::thread uiThread(&UiLoop);
+ gUiThread.swap(uiThread);
+}
+
+void Stop()
+{
+ gUiRunning = false;
+ gUiThread.join();
+}
+
+} // namespace Ui
+} // namespace example
diff --git a/examples/lighting-app/linux/ui.h b/examples/lighting-app/linux/ui.h
new file mode 100644
index 0000000..85bf2aa
--- /dev/null
+++ b/examples/lighting-app/linux/ui.h
@@ -0,0 +1,28 @@
+/*
+ * Copyright (c) 2023 Project CHIP Authors
+ * All rights reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#pragma once
+
+namespace example {
+namespace Ui {
+
+void Start();
+
+void Stop();
+
+} // namespace Ui
+} // namespace example
diff --git a/examples/platform/linux/AppMain.cpp b/examples/platform/linux/AppMain.cpp
index 876e0da..05b8066 100644
--- a/examples/platform/linux/AppMain.cpp
+++ b/examples/platform/linux/AppMain.cpp
@@ -124,6 +124,11 @@
// TODO(16968): Lifecycle management of storage-using components like GroupDataProvider, etc
}
+void StopSignalHandler(int signal)
+{
+ DeviceLayer::PlatformMgr().StopEventLoopTask();
+}
+
} // namespace
#if CHIP_DEVICE_CONFIG_ENABLE_WPA
@@ -389,6 +394,8 @@
ApplicationInit();
+ signal(SIGTERM, StopSignalHandler);
+ signal(SIGINT, StopSignalHandler);
DeviceLayer::PlatformMgr().RunEventLoop();
#if CHIP_DEVICE_CONFIG_ENABLE_BOTH_COMMISSIONER_AND_COMMISSIONEE
diff --git a/scripts/build/build/targets.py b/scripts/build/build/targets.py
index 2a6b170..db7053e 100755
--- a/scripts/build/build/targets.py
+++ b/scripts/build/build/targets.py
@@ -148,6 +148,7 @@
target.AppendModifier('clang', use_clang=True)
target.AppendModifier('test', extra_tests=True)
target.AppendModifier('rpc', enable_rpcs=True)
+ target.AppendModifier('with-ui', imgui_ui=True)
return target
diff --git a/scripts/build/builders/host.py b/scripts/build/builders/host.py
index b05a034..b3bfbaa 100644
--- a/scripts/build/builders/host.py
+++ b/scripts/build/builders/host.py
@@ -229,6 +229,7 @@
use_coverage=False, use_dmalloc=False,
minmdns_address_policy=None,
minmdns_high_verbosity=False,
+ imgui_ui=False,
crypto_library: HostCryptoLibrary = None):
super(HostBuilder, self).__init__(
root=os.path.join(root, 'examples', app.ExamplePath()),
@@ -282,6 +283,9 @@
if use_libfuzzer:
self.extra_gn_options.append('is_libfuzzer=true')
+ if imgui_ui:
+ self.extra_gn_options.append('chip_examples_enable_imgui_ui=true')
+
self.use_coverage = use_coverage
if use_coverage:
self.extra_gn_options.append('use_coverage=true')
diff --git a/scripts/build/testdata/all_targets_linux_x64.txt b/scripts/build/testdata/all_targets_linux_x64.txt
index 7b7ae21..77c780e 100644
--- a/scripts/build/testdata/all_targets_linux_x64.txt
+++ b/scripts/build/testdata/all_targets_linux_x64.txt
@@ -8,7 +8,7 @@
esp32-{m5stack,c3devkit,devkitc,qemu}-{all-clusters,all-clusters-minimal,ota-provider,ota-requestor,shell,light,lock,bridge,temperature-measurement,ota-requestor,tests}[-rpc][-ipv6only]
genio-lighting-app
linux-fake-tests[-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-coverage][-dmalloc][-clang]
-linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,minmdns,light,lock,shell,ota-provider,ota-requestor,python-bindings,tv-app,tv-casting-app,bridge,dynamic-bridge,tests,chip-cert,address-resolve-tool}[-nodeps][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-coverage][-dmalloc][-clang][-test][-rpc]
+linux-{x64,arm64}-{rpc-console,all-clusters,all-clusters-minimal,chip-tool,thermostat,java-matter-controller,minmdns,light,lock,shell,ota-provider,ota-requestor,python-bindings,tv-app,tv-casting-app,bridge,dynamic-bridge,tests,chip-cert,address-resolve-tool}[-nodeps][-platform-mdns][-minmdns-verbose][-libnl][-same-event-loop][-no-interactive][-ipv6only][-no-ble][-no-wifi][-no-thread][-mbedtls][-boringssl][-asan][-tsan][-ubsan][-libfuzzer][-coverage][-dmalloc][-clang][-test][-rpc][-with-ui]
linux-x64-efr32-test-runner[-clang]
imx-{chip-tool,lighting-app,thermostat,all-clusters-app,all-clusters-minimal-app,ota-provider-app}[-release]
infineon-psoc6-{lock,light,all-clusters,all-clusters-minimal}[-ota][-updateimage]
diff --git a/third_party/imgui/BUILD.gn b/third_party/imgui/BUILD.gn
new file mode 100644
index 0000000..362e964
--- /dev/null
+++ b/third_party/imgui/BUILD.gn
@@ -0,0 +1,63 @@
+# Copyright (c) 2023 Project CHIP Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+
+config("imgui_config") {
+ include_dirs = [
+ "repo",
+ "repo/backends",
+ ]
+ defines = [ "CHIP_IMGUI_ENABLED=1" ]
+
+ # FUTURE: these should be parsed from `sdl2-config --cflags`
+ include_dirs += [ "/usr/include/SDL2" ]
+ defines += [ "_REENTRANT" ]
+}
+
+source_set("imgui") {
+ sources = [
+ "repo/imconfig.h",
+ "repo/imgui.cpp",
+ "repo/imgui.h",
+ "repo/imgui_draw.cpp",
+ "repo/imgui_internal.h",
+ "repo/imgui_tables.cpp",
+ "repo/imgui_widgets.cpp",
+ "repo/imstb_rectpack.h",
+ "repo/imstb_textedit.h",
+ "repo/imstb_truetype.h",
+ ]
+
+ # SDL2 + OPENGL3 backend enabled directly here since
+ # the includes are circular (backend includes imgui)
+ sources += [
+ "repo/backends/imgui_impl_opengl3.cpp",
+ "repo/backends/imgui_impl_opengl3.h",
+ "repo/backends/imgui_impl_sdl2.cpp",
+ "repo/backends/imgui_impl_sdl2.h",
+ ]
+
+ # FUTURE: SDL2 libs should be from `sdl2-config --libs`
+ # Also different platforms may require different seettings (e.g. on mac this
+ # seems to need `-framework OpenGl -framework CoreFoundation`
+ libs = [
+ "SDL2",
+ "GL",
+ "dl",
+ ]
+
+ public_configs = [ ":imgui_config" ]
+}
diff --git a/third_party/imgui/imgui.gni b/third_party/imgui/imgui.gni
new file mode 100644
index 0000000..6db9204
--- /dev/null
+++ b/third_party/imgui/imgui.gni
@@ -0,0 +1,23 @@
+# Copyright (c) 2023 Project CHIP Authors
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import("//build_overrides/build.gni")
+import("//build_overrides/chip.gni")
+
+import("${chip_root}/src/platform/device.gni")
+
+declare_args() {
+ # Enable UI functionality for example apps
+ chip_examples_enable_imgui_ui = false
+}
diff --git a/third_party/imgui/repo b/third_party/imgui/repo
new file mode 160000
index 0000000..85395b7
--- /dev/null
+++ b/third_party/imgui/repo
@@ -0,0 +1 @@
+Subproject commit 85395b76b08111961aa6e0ff026fd5152d48aa15