Skip to content

Commit 3dc221e

Browse files
committed
initial v0.1.0
1 parent ddc15ca commit 3dc221e

File tree

7 files changed

+229
-0
lines changed

7 files changed

+229
-0
lines changed

.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,7 @@ install_manifest.txt
99
compile_commands.json
1010
CTestTestfile.cmake
1111
_deps
12+
13+
.vscode
14+
build
15+
build*

CMakeLists.txt

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
cmake_minimum_required(VERSION 3.25)
2+
3+
set(CMAKE_CXX_STANDARD 23)
4+
set(CMAKE_CXX_STANDARD_REQUIRED ON)
5+
set(CMAKE_CXX_EXTENSIONS OFF)
6+
7+
project(get-livecaptions)
8+
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
9+
add_executable(get-livecaptions src/main.cpp src/pch.cpp)
10+
target_precompile_headers(get-livecaptions PRIVATE src/pch.h)
11+

README.md

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,36 @@
11
# get-livecaptions-cpp
22
Get real time content of Windows System APP "Live Captions" [win+ctrl+L], write content into file. using c++/winrt, asio
3+
4+
check slibing project [get-livecaptions-rs](https://github.com/corbamico/get-livecaptions-rs)
5+
6+
## Usage
7+
8+
```cmd
9+
Usage: get-livecatpions file
10+
Options:
11+
file filename, to save content of live catpions running.
12+
```
13+
14+
## UIAutomation
15+
16+
To find the LiveCaptions GUI AutomationID, you may need tools as [inspect](https://learn.microsoft.com/en-us/windows/win32/winauto/inspect-objects), or [Automation-Spy](https://github.com/ddeltasolutions/Automation-Spy)
17+
18+
![](./doc/image.png)
19+
20+
## License
21+
22+
Licensed under either of
23+
24+
* Apache License, Version 2.0
25+
([LICENSE-APACHE](LICENSE-APACHE) or http://www.apache.org/licenses/LICENSE-2.0)
26+
* MIT license
27+
([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT)
28+
29+
at your option.
30+
31+
## Contribution
32+
33+
Unless you explicitly state otherwise, any contribution intentionally submitted
34+
for inclusion in the work by you, as defined in the Apache-2.0 license, shall be
35+
dual licensed as above, without any additional terms or conditions.
36+

doc/image.png

66.3 KB
Loading

src/main.cpp

Lines changed: 166 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,166 @@
1+
#include "pch.h"
2+
3+
using namespace winrt;
4+
using namespace winrt::Windows::Foundation;
5+
using namespace winrt::Windows::UI::UIAutomation;
6+
7+
std::string get_current_time()
8+
{
9+
// [TIPS]c+20 runtime is too expensive, + 500k for following implement.
10+
//
11+
// auto const time = std::chrono::current_zone()
12+
// ->to_local(std::chrono::system_clock::now());
13+
// return std::format("{:%Y/%m/%d %X}", time);
14+
15+
auto now = std::chrono::system_clock::now();
16+
std::stringstream ss;
17+
std::time_t now_c = std::chrono::system_clock::to_time_t(now);
18+
ss<<std::put_time(std::localtime(&now_c), "%Y/%m/%d %X");
19+
return ss.str();
20+
}
21+
22+
class Engine
23+
{
24+
winrt::com_ptr<IUIAutomation> _automation;
25+
winrt::com_ptr<IUIAutomationCondition> _condition;
26+
std::string _prebuffer;
27+
std::string _sfilename;
28+
29+
winrt::hstring get_livecaptions()
30+
{
31+
wil::unique_bstr text;
32+
winrt::com_ptr<IUIAutomationElement> window_element;
33+
winrt::com_ptr<IUIAutomationElement> text_element;
34+
35+
auto window = FindWindowW(L"LiveCaptionsDesktopWindow", nullptr);
36+
winrt::check_hresult(_automation->ElementFromHandle(window, window_element.put()));
37+
winrt::check_hresult(window_element->FindFirst(TreeScope_Descendants, _condition.get(), text_element.put()));
38+
winrt::check_hresult(text_element->get_CurrentName(text.put()));
39+
return text.get();
40+
}
41+
42+
public:
43+
Engine(const std::string &filename) : _sfilename{filename}
44+
{
45+
winrt::init_apartment();
46+
_automation = try_create_instance<IUIAutomation>(guid_of<CUIAutomation>());
47+
winrt::check_hresult(_automation->CreatePropertyCondition(UIA_AutomationIdPropertyId, wil::make_variant_bstr(L"CaptionsTextBlock"), _condition.put()));
48+
}
49+
~Engine() { winrt::uninit_apartment(); }
50+
51+
void save_current_captions(bool includeLastLine = false)
52+
{
53+
auto current = winrt::to_string(get_livecaptions());
54+
std::vector<std::string> lines;
55+
std::string line;
56+
std::istringstream iss(current);
57+
while (std::getline(iss, line))
58+
{
59+
lines.emplace_back(line);
60+
}
61+
// Find the first line not in the prebuffer
62+
size_t first_new_line = lines.size();
63+
for (size_t i = 0; i < lines.size(); i++)
64+
{
65+
if (_prebuffer.find(lines[i]) == std::string::npos)
66+
{
67+
first_new_line = i;
68+
break;
69+
}
70+
}
71+
_prebuffer = current;
72+
73+
if (first_new_line < lines.size())
74+
{
75+
// Append new lines to the file and prebuffer
76+
std::ofstream file(_sfilename, std::ios::app);
77+
if (!file.is_open())
78+
{
79+
std::cerr << "[Error]Failed to open file: " << _sfilename << std::endl;
80+
return;
81+
}
82+
83+
84+
file << "[" << get_current_time() << "] " << std::endl;
85+
86+
for (size_t i = first_new_line; i < lines.size(); ++i)
87+
{
88+
file << lines[i] << std::endl;
89+
}
90+
file.flush();
91+
file.close();
92+
}
93+
}
94+
static bool is_livecaption_running()
95+
{
96+
return FindWindowW(L"LiveCaptionsDesktopWindow", nullptr) != NULL;
97+
}
98+
};
99+
100+
101+
102+
void usage()
103+
{
104+
std::cerr << "Write all content of LiveCaptions Windows System Program into file, continually.Ctrl-C to exit." << std::endl;
105+
std::cerr << "Usage: get-livecatpions file" << std::endl;
106+
std::cerr << "Options:" << std::endl;
107+
std::cerr << " file filename, to save content of live catpions running." << std::endl;
108+
exit(1);
109+
}
110+
bool touch_file(const std::string &filename)
111+
{
112+
std::ofstream file(filename,std::ios::app);
113+
auto ret = file.is_open();
114+
file.close();
115+
return ret;
116+
}
117+
118+
int main(int argc, char *argv[])
119+
{
120+
if ((argc != 2) || (!touch_file(argv[1])))
121+
usage();
122+
if (!Engine::is_livecaption_running())
123+
{
124+
std::cerr << "[Error]Live Captions is not running." <<std::endl;
125+
exit(1);
126+
}
127+
128+
try
129+
{
130+
asio::io_context io_context(1);
131+
Engine eng(argv[1]);
132+
133+
asio::signal_set signals(io_context, SIGINT, SIGTERM);
134+
signals.async_wait([&](auto, auto){
135+
std::cerr << "ctrl-c to exit." <<std::endl;
136+
eng.save_current_captions();
137+
io_context.stop();
138+
});
139+
std::cout<<"Save content into file, every 1 min."<<std::endl;
140+
asio::co_spawn(io_context, [&]() -> asio::awaitable<void>
141+
{
142+
asio::steady_timer timer_10s(io_context);
143+
ULONG64 ulCount{0};
144+
while(true){
145+
timer_10s.expires_after(asio::chrono::seconds(10));
146+
co_await timer_10s.async_wait(asio::use_awaitable);
147+
//std::cout << "every 10s" <<std::endl;
148+
if (!Engine::is_livecaption_running())
149+
{
150+
std::cerr << "[Info]LiveCaptions isn't running. exit." <<std::endl;
151+
io_context.stop();
152+
break;
153+
}
154+
if(ulCount++%6==0) eng.save_current_captions();
155+
}
156+
co_return;
157+
},
158+
asio::detached);
159+
io_context.run();
160+
}
161+
catch (std::exception &e)
162+
{
163+
std::printf("Exception: %s\n", e.what());
164+
}
165+
return 0;
166+
}

src/pch.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
#include "pch.h"

src/pch.h

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
#pragma once
2+
3+
#define ASIO_NO_WIN32_LEAN_AND_MEAN
4+
#include <sdkddkver.h>
5+
#include <asio.hpp>
6+
#include <windows.h>
7+
#include <chrono>
8+
#include <iostream>
9+
#include <fstream>
10+
11+
#include <winrt/windows.ui.uiautomation.h>
12+
#include <uiautomation.h>
13+
#include <wil/resource.h>

0 commit comments

Comments
 (0)