Skip to content

Commit 872f33f

Browse files
committed
wip
1 parent 198235d commit 872f33f

File tree

7 files changed

+159
-56
lines changed

7 files changed

+159
-56
lines changed

.github/workflows/build-and-test.yml

+59-35
Original file line numberDiff line numberDiff line change
@@ -51,65 +51,89 @@ jobs:
5151
name: Run functional tests
5252
needs:
5353
- build-and-test
54-
runs-on: ubuntu-latest
54+
strategy:
55+
matrix:
56+
os: [ubuntu-latest, windows-latest]
57+
runs-on: ${{ matrix.os }}
5558
steps:
5659
- uses: actions/checkout@v4
5760
- uses: actions/download-artifact@v4
5861
with:
59-
name: klawa-ubuntu-latest
62+
name: klawa-${{ matrix.os }}
6063
path: zig-out/bin
6164
- name: Install dependencies
65+
shell: bash
66+
run: |
67+
pwd
68+
ls -alh zig-out/bin
69+
- if: matrix.os == 'ubuntu-latest'
70+
name: Install dependencies
6271
shell: bash
6372
run: |
6473
sudo apt-get update
6574
sudo apt-get install ffmpeg xdotool x11-apps xvfb
75+
- if: matrix.os == 'windows-latest'
76+
name: Install dependencies
77+
shell: bash
78+
run: |
79+
choco install autohotkey.portable ffmpeg
80+
- if: matrix.os == 'windows-latest'
81+
name: Install Mesa
82+
shell: cmd
83+
run: |
84+
curl.exe -L --output mesa.7z --url https://github.com/pal1000/mesa-dist-win/releases/download/24.2.7/mesa3d-24.2.7-release-msvc.7z
85+
"C:\Program Files\7-Zip\7z.exe" x mesa.7z
86+
dir
87+
systemwidedeploy.cmd 1
6688
- name: Install python dependencies
6789
shell: bash
6890
run: |
6991
cd tests
70-
python -m venv .env
71-
. .env/bin/activate
7292
pip install -r dev-requirements.txt
73-
- name: Run tests
93+
- if: matrix.os == 'ubuntu-latest'
94+
name: Fix executable permisions
7495
shell: bash
7596
run: |
7697
chmod +x zig-out/bin/klawa
98+
- name: Run tests
99+
shell: bash
100+
run: |
77101
# not running with pytest-xdist because renders are way off
78102
# when framerate drops below expected 60fps:
79-
cd tests && . .env/bin/activate && python -m pytest src/
103+
cd tests && python -m pytest src/
80104
- uses: actions/upload-artifact@v4
81105
if: always()
82106
with:
83-
name: report
107+
name: report-${{ matrix.os }}
84108
path: tests/report/
85109
retention-days: 2
86110
if-no-files-found: error
87111

88-
deploy-preview:
89-
name: Deploy tests results
90-
needs:
91-
- run-functional-tests
92-
runs-on: ubuntu-latest
93-
defaults:
94-
run:
95-
shell: bash
96-
working-directory: ./tests
97-
steps:
98-
- uses: actions/checkout@v4
99-
- uses: actions/download-artifact@v4
100-
with:
101-
name: report
102-
path: tests/report
103-
- name: Install Vercel CLI
104-
run: npm install --global vercel@latest
105-
- name: Pull Vercel Environment Information
106-
run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
107-
- name: Build Project Artifacts
108-
run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
109-
- name: Deploy Project Artifacts to Vercel
110-
run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} > url.txt
111-
- name: Add summary
112-
shell: bash
113-
run: |
114-
echo '### Deployed' >> $GITHUB_STEP_SUMMARY
115-
cat url.txt >> $GITHUB_STEP_SUMMARY
112+
# deploy-preview:
113+
# name: Deploy tests results
114+
# needs:
115+
# - run-functional-tests
116+
# runs-on: ubuntu-latest
117+
# defaults:
118+
# run:
119+
# shell: bash
120+
# working-directory: ./tests
121+
# steps:
122+
# - uses: actions/checkout@v4
123+
# - uses: actions/download-artifact@v4
124+
# with:
125+
# name: report
126+
# path: tests/report
127+
# - name: Install Vercel CLI
128+
# run: npm install --global vercel@latest
129+
# - name: Pull Vercel Environment Information
130+
# run: vercel pull --yes --environment=preview --token=${{ secrets.VERCEL_TOKEN }}
131+
# - name: Build Project Artifacts
132+
# run: vercel build --token=${{ secrets.VERCEL_TOKEN }}
133+
# - name: Deploy Project Artifacts to Vercel
134+
# run: vercel deploy --prebuilt --token=${{ secrets.VERCEL_TOKEN }} > url.txt
135+
# - name: Add summary
136+
# shell: bash
137+
# run: |
138+
# echo '### Deployed' >> $GITHUB_STEP_SUMMARY
139+
# cat url.txt >> $GITHUB_STEP_SUMMARY

src/ffmpeg.zig

+8-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ const process = std.process;
33
const Child = process.Child;
44
const File = std.fs.File;
55

6+
const builtin = @import("builtin");
7+
68
pub const Ffmpeg = struct {
79
child: Child,
810

@@ -14,8 +16,13 @@ pub const Ffmpeg = struct {
1416
) !Ffmpeg {
1517
const resolution = try std.fmt.allocPrint(allocator, "{d}x{d}", .{ width, height });
1618
defer allocator.free(resolution);
19+
const exec_name: []const u8 = switch (builtin.target.os.tag) {
20+
.linux => "ffmpeg",
21+
.windows => "ffmpeg.exe",
22+
else => @compileError("unsupported platform"),
23+
};
1724
const args = [_][]const u8{
18-
"ffmpeg", "-y",
25+
exec_name, "-y",
1926
"-f", "rawvideo",
2027
"-framerate", "60",
2128
"-s", resolution,

src/layout_labels.zig

+53
Original file line numberDiff line numberDiff line change
@@ -432,3 +432,56 @@ const layout_labels_lookup_slice = blk: {
432432

433433
pub const labels_lookup = std.StaticStringMap(usize).initComptime(layout_labels_lookup_slice);
434434

435+
//const testing = @import("std").testing;
436+
//
437+
//test "test label mapping" {
438+
// const allocator = std.testing.allocator;
439+
// std.debug.print("{any}\n", .{x11.XK_BackSpace});
440+
//
441+
// const display: *x11.Display = x11.XOpenDisplay(null) orelse {
442+
// std.debug.print("Unable to connect to X server\n", .{});
443+
// return error.X11InitializationFailed;
444+
// };
445+
// var min_keycode: c_int = 0;
446+
// var max_keycode: c_int = 0;
447+
// _ = x11.XDisplayKeycodes(display, &min_keycode, &max_keycode);
448+
//
449+
// var keysyms_per_keycode: c_int = 0;
450+
// const origkeymap = x11.XGetKeyboardMapping(display, @intCast(min_keycode), (max_keycode - min_keycode + 1), &keysyms_per_keycode);
451+
// //defer x11.XFree(origkeymap);
452+
//
453+
// std.debug.print("{} {} keysyms_per_keycode {}\n", .{min_keycode, max_keycode, keysyms_per_keycode});
454+
//
455+
// //var lookup = try allocator.alloc(x11.KeySym, (max_keycode - min_keycode + 1) * keysyms_per_keycode);
456+
// //var lookup_index: usize = 0;
457+
//
458+
// var map = std.AutoHashMap(x11.KeySym, usize).init(allocator);
459+
// defer map.deinit();
460+
//
461+
// const no_symbol = "NoSymbol";
462+
// var keymap = origkeymap;
463+
// for (@as(usize, @intCast(min_keycode))..@as(usize, @intCast(max_keycode+1))) |keycode| {
464+
// var max = keysyms_per_keycode - 1;
465+
// while (max >= 0 and keymap[@as(usize, @intCast(max))] == x11.NoSymbol) {
466+
// max -= 1;
467+
// }
468+
// std.debug.print("{}\t", .{keycode});
469+
// for (0..@as(usize, @intCast(max+1))) |j| {
470+
// const ks = keymap[j];
471+
//
472+
// //lookup[lookup_index] = ks;
473+
// //lookup_index += 1;
474+
// try map.put(ks, keycode);
475+
//
476+
// const s = if (ks != x11.NoSymbol) x11.XKeysymToString(ks) else no_symbol.ptr;
477+
// std.debug.print("0x{x} ({s})\t", .{ks, s});
478+
// }
479+
// keymap += @as(usize, @intCast(keysyms_per_keycode));
480+
// std.debug.print("\n", .{});
481+
// }
482+
//
483+
// var iterator = map.iterator();
484+
// while (iterator.next()) |entry| {
485+
// std.debug.print("{x}: {}\n", .{entry.key_ptr.*, entry.value_ptr.*});
486+
// }
487+
//}

src/textures.zig

-1
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,6 @@ fn getPositions(sizes: []const rl.Vector2) [keycap_sizes.len]rl.Vector2 {
7979
}
8080

8181
pub fn getPositionBySize(size: rl.Vector2) rl.Vector2 {
82-
std.debug.print("Looking for {d} {d}\n", .{size.x, size.y});
8382
for (keycap_sizes, 0..) |s, i| {
8483
if (size.x == s.x and size.y == s.y) {
8584
return atlas_positions[i];

tests/pytest.ini

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
[pytest]
22
addopts =
33
--html=report/index.html
4-
--app-path=../zig-out/bin/klawa
4+
--app-dir=../zig-out/bin
55
generate_report_on_test = True
66
render_collapsed =
77
log_cli = True

tests/src/conftest.py

+6-4
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import os
44
import shutil
5+
import sys
56
from pathlib import Path
67

78
import pytest
@@ -11,18 +12,19 @@
1112

1213
def pytest_addoption(parser) -> None:
1314
parser.addoption(
14-
"--app-path",
15+
"--app-dir",
1516
action="store",
16-
help="Path to klawa executable",
17+
help="Path to directory of klawa executable",
1718
default=False,
1819
)
1920

2021

2122
@pytest.fixture(scope="session")
2223
def app_path(request) -> Path:
23-
app_path = request.config.getoption("--app-path")
24+
app_path = request.config.getoption("--app-dir")
2425
assert app_path, "App path is required"
25-
return Path(os.path.realpath(app_path))
26+
app_name = "klawa.exe" if sys.platform == "win32" else "klawa"
27+
return Path(os.path.realpath(app_path)) / app_name
2628

2729

2830
@pytest.fixture

tests/src/test_rendering.py

+32-14
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
import logging
22
import os
3-
from os.path import isfile
43
import signal
54
import shutil
65
import subprocess
@@ -58,7 +57,7 @@ def get_screen_manager():
5857
else:
5958
return HostScreenManager()
6059
else:
61-
pytest.skip(f"Platform '{sys.platform}' is not supported")
60+
return HostScreenManager()
6261

6362

6463
def set_keyboard_layout(lang):
@@ -91,20 +90,22 @@ def set_keyboard_layout(lang):
9190
def screen_manager():
9291
with get_screen_manager() as _:
9392

94-
# this is a trick to keep one keyboard layout for full display lifetime,
95-
# otherwise server would regenerate layout on last client disconection
96-
# https://stackoverflow.com/questions/75919741/how-to-add-keyboard-layouts-to-xvfb
97-
# 1. run some app in the background for full duration on test
98-
# 2. configure keyboard layout
99-
# 3. test
100-
dummy = subprocess.Popen(["xlogo"])
101-
time.sleep(0.5)
93+
if sys.platform == "linux":
94+
# this is a trick to keep one keyboard layout for full display lifetime,
95+
# otherwise server would regenerate layout on last client disconection
96+
# https://stackoverflow.com/questions/75919741/how-to-add-keyboard-layouts-to-xvfb
97+
# 1. run some app in the background for full duration on test
98+
# 2. configure keyboard layout
99+
# 3. test
100+
dummy = subprocess.Popen(["xlogo"])
101+
time.sleep(0.5)
102102

103-
set_keyboard_layout("pl")
103+
set_keyboard_layout("pl")
104104

105105
yield
106106

107-
dummy.kill()
107+
if sys.platform == "linux":
108+
dummy.kill()
108109

109110

110111
def log_config(tmpdir) -> None:
@@ -139,6 +140,7 @@ def run_process_capture_logs(command, cwd, name="", process_holder=None) -> None
139140
stderr=subprocess.STDOUT,
140141
text=True,
141142
cwd=cwd,
143+
encoding="utf-8",
142144
)
143145
assert process, "Process creation failed"
144146
assert process.stdout, "Could not get stdout"
@@ -152,6 +154,20 @@ def run_process_capture_logs(command, cwd, name="", process_holder=None) -> None
152154
process.wait()
153155

154156

157+
def run_typing_process(workdir, text: str) -> None:
158+
if sys.platform == "win32":
159+
with open(workdir / "type.ahk", "w", encoding="utf-8") as f:
160+
f.write("SetKeyDelay 400, 100\n") # 400ms between keys, 100ms between down/up.
161+
f.write(f"SendEvent \"{text}\"\n")
162+
f.write("exit")
163+
result = subprocess.run(["AutoHotkey.exe", "/ErrorStdOut", "type.ahk"], cwd=workdir)
164+
else:
165+
result = subprocess.run(["xdotool", "type", "--delay", "400", text])
166+
logger.info(f"stdout: {result.stdout}")
167+
logger.info(f"stderr: {result.stderr}")
168+
assert result.returncode == 0
169+
170+
155171
def __get_parameters():
156172
texts = [
157173
"The quick brown fox jumps over the lazy dog",
@@ -190,9 +206,9 @@ def test_record_and_render(app_isolation, text: str, example) -> None:
190206
args=([app, "--record", "events.bin"], app_dir, "klawa", processes,)
191207
)
192208
thread.start()
193-
time.sleep(2)
209+
time.sleep(10)
194210

195-
subprocess.run(["xdotool", "type", "--delay", "400", text])
211+
run_typing_process(app_dir, text)
196212

197213
process = processes.get("klawa")
198214
if process and process.poll() is None:
@@ -202,3 +218,5 @@ def test_record_and_render(app_isolation, text: str, example) -> None:
202218

203219
args = [app, "--replay", "events.bin", "--render", "output.webm"]
204220
run_process_capture_logs(args, app_dir)
221+
assert (app_dir / "output.webm").is_file()
222+

0 commit comments

Comments
 (0)