diff --git a/.github/workflows/nix.yml b/.github/workflows/nix-build.yml similarity index 85% rename from .github/workflows/nix.yml rename to .github/workflows/nix-build.yml index 94091468..199fa7b0 100644 --- a/.github/workflows/nix.yml +++ b/.github/workflows/nix-build.yml @@ -1,8 +1,13 @@ -name: Build +name: Build (Nix) + +on: + workflow_call: + secrets: + CACHIX_AUTH_TOKEN: + required: false -on: [push, pull_request, workflow_dispatch] jobs: - nix: + build: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 @@ -42,7 +47,6 @@ jobs: # with: # name: hyprland # authToken: '${{ secrets.CACHIX_AUTH_TOKEN }}' - + # - name: Build - run: nix flake check --print-build-logs --keep-going - + run: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}' -L --extra-substituters "https://hyprland.cachix.org" diff --git a/.github/workflows/nix-ci.yml b/.github/workflows/nix-ci.yml new file mode 100644 index 00000000..c07ae5ad --- /dev/null +++ b/.github/workflows/nix-ci.yml @@ -0,0 +1,15 @@ +name: Nix + +on: [push, pull_request, workflow_dispatch] + +jobs: + hyprlock: + if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) + uses: ./.github/workflows/nix-build.yml + secrets: inherit + + test: + if: (github.event_name != 'pull_request' || github.event.pull_request.head.repo.fork) + needs: hyprlock + uses: ./.github/workflows/nix-test.yml + secrets: inherit diff --git a/.github/workflows/nix-test.yml b/.github/workflows/nix-test.yml new file mode 100644 index 00000000..df1e74f0 --- /dev/null +++ b/.github/workflows/nix-test.yml @@ -0,0 +1,66 @@ +name: Test (Nix) + +on: + workflow_call: + secrets: + CACHIX_AUTH_TOKEN: + required: false + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Install Nix + uses: nixbuild/nix-quick-install-action@v31 + with: + nix_conf: | + keep-env-derivations = true + keep-outputs = true + + - name: Restore and save Nix store + uses: nix-community/cache-nix-action@v6 + with: + # restore and save a cache using this key + primary-key: nix-${{ runner.os }} + # if there's no cache hit, restore a cache by this prefix + restore-prefixes-first-match: nix-${{ runner.os }} + # collect garbage until the Nix store size (in bytes) is at most this number + # before trying to save a new cache + # 1G = 1073741824 + gc-max-store-size-linux: 5G + # do purge caches + purge: true + # purge all versions of the cache + purge-prefixes: nix-${{ runner.os }} + # created more than this number of seconds ago + purge-created: 0 + # or, last accessed more than this number of seconds ago + # relative to the start of the `Post Restore and save Nix store` phase + purge-last-accessed: 0 + # except any version with the key that is the same as the `primary-key` + purge-primary-key: never + + #- uses: cachix/cachix-action@v15 + # with: + # name: hyprland + # authToken: "${{ secrets.CACHIX_AUTH_TOKEN }}" + + - name: Run test VM + run: nix build 'github:${{ github.repository }}?ref=${{ github.ref }}#checks.x86_64-linux.tests' -L --extra-substituters "https://hyprland.cachix.org" + + - name: Check exit status + run: grep 0 result/exit_status + + - name: Upload logs + if: always() + uses: actions/upload-artifact@v4 + with: + name: logs + path: result/logs + + - name: Upload traces + if: always() + uses: actions/upload-artifact@v4 + with: + name: traces + path: result/traces diff --git a/CMakeLists.txt b/CMakeLists.txt index b3d6e4b2..774d990a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -162,3 +162,13 @@ install( FILES ${CMAKE_SOURCE_DIR}/assets/example.conf DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/hypr RENAME hyprlock.conf) + +if(TESTS) + include(CTest) + message(STATUS "Building hyprlock test meta package") + + enable_testing() + add_custom_target(tests) + + add_subdirectory(tests) +endif() diff --git a/flake.lock b/flake.lock index 449743c9..bcbf85a0 100644 --- a/flake.lock +++ b/flake.lock @@ -13,11 +13,11 @@ ] }, "locked": { - "lastModified": 1758572180, - "narHash": "sha256-Is8Rcp99Ynl3JFcU3k2lsmyf8WGacWKZtnVb0mVIZ6M=", + "lastModified": 1759490292, + "narHash": "sha256-T6iWzDOXp8Wv0KQOCTHpBcmAOdHJ6zc/l9xaztW6Ivc=", "owner": "hyprwm", "repo": "hyprgraphics", - "rev": "32e6b8386f7dc70a4cc01607a826a281f3c52364", + "rev": "9431db625cd9bb66ac55525479dce694101d6d7a", "type": "github" }, "original": { @@ -39,11 +39,11 @@ ] }, "locked": { - "lastModified": 1756810301, - "narHash": "sha256-wgZ3VW4VVtjK5dr0EiK9zKdJ/SOqGIBXVG85C3LVxQA=", + "lastModified": 1758927902, + "narHash": "sha256-LZgMds7M94+vuMql2bERQ6LiFFdhgsEFezE4Vn+Ys3A=", "owner": "hyprwm", "repo": "hyprlang", - "rev": "3d63fb4a42c819f198deabd18c0c2c1ded1de931", + "rev": "4dafa28d4f79877d67a7d1a654cddccf8ebf15da", "type": "github" }, "original": { @@ -62,11 +62,11 @@ ] }, "locked": { - "lastModified": 1756117388, - "narHash": "sha256-oRDel6pNl/T2tI+nc/USU9ZP9w08dxtl7hiZxa0C/Wc=", + "lastModified": 1759619523, + "narHash": "sha256-r1ed7AR2ZEb2U8gy321/Xcp1ho2tzn+gG1te/Wxsj1A=", "owner": "hyprwm", "repo": "hyprutils", - "rev": "b2ae3204845f5f2f79b4703b441252d8ad2ecfd0", + "rev": "3df7bde01efb3a3e8e678d1155f2aa3f19e177ef", "type": "github" }, "original": { @@ -100,11 +100,12 @@ }, "nixpkgs": { "locked": { - "lastModified": 1757745802, - "narHash": "sha256-hLEO2TPj55KcUFUU1vgtHE9UEIOjRcH/4QbmfHNF820=", - "path": "/nix/store/lvwgkdy9nrki8qcrdsqnpfrk7562m1dc-source", - "rev": "c23193b943c6c689d70ee98ce3128239ed9e32d1", - "type": "path" + "lastModified": 1759831965, + "narHash": "sha256-vgPm2xjOmKdZ0xKA6yLXPJpjOtQPHfaZDRtH+47XEBo=", + "owner": "NixOS", + "repo": "nixpkgs", + "rev": "c9b6fb798541223bbb396d287d16f43520250518", + "type": "github" }, "original": { "owner": "NixOS", diff --git a/flake.nix b/flake.nix index 1cc9a00d..5e34668c 100644 --- a/flake.nix +++ b/flake.nix @@ -43,7 +43,12 @@ pkgsFor = eachSystem (system: import nixpkgs { localSystem.system = system; - overlays = with self.overlays; [default]; + overlays = [self.overlays.default]; + }); + pkgsDebugFor = eachSystem (system: + import nixpkgs { + localSystem = system; + overlays = [self.overlays.hyprlock-debug]; }); in { overlays = import ./nix/overlays.nix {inherit inputs lib self;}; @@ -51,6 +56,7 @@ packages = eachSystem (system: { default = self.packages.${system}.hyprlock; inherit (pkgsFor.${system}) hyprlock; + inherit (pkgsDebugFor.${system}) hyprlock-debug hyprlock-test-meta; }); homeManagerModules = { @@ -58,7 +64,7 @@ hyprlock = builtins.throw "hyprlock: the flake HM module has been removed. Use the module from Home Manager upstream."; }; - checks = eachSystem (system: self.packages.${system}); + checks = eachSystem (system: self.packages.${system} // (import ./nix/tests/default.nix inputs pkgsFor.${system})); formatter = eachSystem (system: pkgsFor.${system}.alejandra); }; diff --git a/nix/default.nix b/nix/default.nix index d3c51050..fb038cc4 100644 --- a/nix/default.nix +++ b/nix/default.nix @@ -1,6 +1,7 @@ { lib, stdenv, + stdenvAdapters, cmake, pkg-config, cairo, @@ -19,14 +20,33 @@ wayland, wayland-protocols, wayland-scanner, + debug ? false, version ? "git", shortRev ? "", -}: -stdenv.mkDerivation { - pname = "hyprlock"; +}: let + inherit (builtins) foldl'; + inherit (lib.lists) flatten; + inherit (lib.sources) cleanSourceWith cleanSource; + inherit (lib.strings) hasSuffix optionalString; + + adapters = flatten [ + stdenvAdapters.useMoldLinker + (lib.optional debug stdenvAdapters.keepDebugInfo) + ]; + + customStdenv = foldl' (acc: adapter: adapter acc) stdenv adapters; + in +customStdenv.mkDerivation { + pname = "hyprlock${optionalString debug "-debug"}"; inherit version; - src = ../.; + src = cleanSourceWith { + filter = name: _type: let + baseName = baseNameOf (toString name); + in + ! (hasSuffix ".nix" baseName); + src = cleanSource ../.; + }; nativeBuildInputs = [ cmake @@ -57,6 +77,11 @@ stdenv.mkDerivation { HYPRLOCK_VERSION_COMMIT = ""; # Intentionally left empty (hyprlock --version will always print the commit) }; + cmakeBuildType = + if debug + then "Debug" + else "Release"; + meta = { homepage = "https://github.com/hyprwm/hyprlock"; description = "A gpu-accelerated screen lock for Hyprland"; diff --git a/nix/overlays.nix b/nix/overlays.nix index e3869422..3f71ffb2 100644 --- a/nix/overlays.nix +++ b/nix/overlays.nix @@ -29,6 +29,22 @@ in { }) ]; + hyprlock-debug = lib.composeManyExtensions [ + self.overlays.hyprlock + # Dependencies + (final: prev: { + hyprutils = prev.hyprutils.override {debug = true;}; + hyprgraphics = prev.hyprgraphics.override {debug = true;}; + hyprlock-debug = prev.hyprlock.override {debug = true;}; + hyprlock-test-meta = prev.callPackage ./test-meta.nix { + stdenv = prev.gcc14Stdenv; + version = version + "+date=" + (mkDate (inputs.self.lastModifiedDate or "19700101")) + "_" + (inputs.self.shortRev or "dirty"); + hyprland-protocols = final.hyprland-protocols; + wayland-scanner = final.wayland-scanner; + }; + }) + ]; + sdbuscpp = final: prev: { sdbus-cpp = prev.sdbus-cpp.overrideAttrs (self: super: { version = "2.0.0"; diff --git a/nix/test-meta.nix b/nix/test-meta.nix new file mode 100644 index 00000000..c217aacf --- /dev/null +++ b/nix/test-meta.nix @@ -0,0 +1,45 @@ +{ + cmake, + egl-wayland, + hyprland-protocols, + hyprlock, + hyprwayland-scanner, + lib, + pkg-config, + stdenv, + stdenvAdapters, + wayland-scanner, + version ? "git", +}: let + inherit (lib.sources) cleanSourceWith cleanSource; + inherit (lib.strings) hasSuffix; +in + stdenv.mkDerivation (finalAttrs: { + pname = "hyprlock-test-meta"; + inherit version; + + src = cleanSourceWith { + filter = name: _type: let + baseName = baseNameOf (toString name); + in + ! (hasSuffix ".nix" baseName); + src = cleanSource ../tests; + }; + + nativeBuildInputs = [ + cmake + hyprland-protocols + hyprwayland-scanner + pkg-config + wayland-scanner + ]; + + buildInputs = hyprlock.buildInputs; + + meta = { + homepage = "https://github.com/hyprwm/hyprlock"; + description = "Hyprlock testing utility"; + license = lib.licenses.bsd3; + platforms = hyprlock.meta.platforms; + }; + }) diff --git a/nix/tests/default.nix b/nix/tests/default.nix new file mode 100644 index 00000000..9a9306f6 --- /dev/null +++ b/nix/tests/default.nix @@ -0,0 +1,164 @@ +inputs: pkgs: let + inherit (pkgs) lib; + inherit (lib.lists) flatten; + flake = inputs.self.packages.${pkgs.stdenv.hostPlatform.system}; + + env = { + #"AQ_TRACE" = "1"; + #"HYPRLAND_TRACE" = "1"; + "HYPRLAND_HEADLESS_ONLY" = "1"; + "XDG_RUNTIME_DIR" = "/tmp"; + "XDG_CACHE_HOME" = "/tmp"; + }; + + envAddToSystemdRun = lib.concatStringsSep " " ( + lib.mapAttrsToList (k: v: "--setenv ${k}=${v} ") env + ); + + APITRACE_RECORD = true; + APITRACE_RECORD_PY = if APITRACE_RECORD then "True" else "False"; +in { + tests = pkgs.testers.runNixOSTest { + name = "hyprlock-tests"; + + nodes.machine = {pkgs, ...}: { + environment.systemPackages = with pkgs; flatten [ + # Programs needed for tests + coreutils # date command + procps # pidof + (lib.optional APITRACE_RECORD apitrace) + ]; + + # Enabled by default for some reason + services.speechd.enable = false; + + environment.variables = env; + + programs.hyprland = { + enable = true; + #withUWSM = true + }; + + programs.hyprlock = { + enable = true; + package = flake.hyprlock; + }; + + networking.dhcpcd.enable = false; + + # Disable portals + xdg.portal.enable = lib.mkForce false; + + # Autologin root into tty + services.getty.autologinUser = "alice"; + + system.stateVersion = "24.11"; + + environment.etc."hyprlock/assets".source = "${flake.hyprlock-test-meta}/share/hypr/assets/"; + + users.users.alice = { + isNormalUser = true; + # password: abcdefghijklmnopqrstuvwxyz1234567890-=!@#$%^&*()_+[]{};\':\\"]\\|,./<>?`~äöüćńóśź + hashedPassword = "$y$j9T$s.atBE5..ISB2OoPWrXnU1$.8yaRmR9iBV9e.Q9wM1hG0ciMMYLGhpmDqsJo8Sjiv2"; + }; + + virtualisation = { + cores = 4; + # Might crash with less + memorySize = 8192; + resolution = { + x = 1920; + y = 1080; + }; + + # Doesn't seem to do much, thought it would fix XWayland crashing + qemu.options = ["-vga none -device virtio-gpu-pci"]; + }; + }; + + testScript = '' + from pathlib import Path + # Wait for tty to be up + machine.wait_for_unit("multi-user.target") + # Startup Hyprland as the test compositor for hyprlock + print("Running Hyprland") + machine.execute("systemd-run -q -u hyprland --uid $(id -u alice) -p RuntimeMaxSec=60 ${envAddToSystemdRun} --setenv PATH=$PATH ${pkgs.hyprland}/bin/Hyprland -c ${flake.hyprlock-test-meta}/share/hypr/hyprland.conf") + machine.wait_for_file("/tmp/hyprland_exec_once_notification") + machine.execute("sleep 1") # slack just to be save + + _, systeminfo = machine.execute("hyprctl --instance 0 systeminfo") + print(systeminfo) + + test_files = [Path("${flake.hyprlock}/share/hypr/hyprlock.conf")] # also test the example configuration + test_files += list(Path("${flake.hyprlock-test-meta}/share/hypr/configs/").iterdir()) + for hyprlock_config in test_files: + print(f"Testing configuration file {hyprlock_config}") + log_file_path = "/tmp/hyprlock_test_" + hyprlock_config.stem + + hyprlock_cmd = f"hyprlock --config {str(hyprlock_config)} -v 2>&1 >{log_file_path}; echo $? > /tmp/exit_status" + if ${APITRACE_RECORD_PY}: + hyprlock_cmd = f"${lib.getExe' pkgs.apitrace "apitrace"} trace --output {log_file_path}.trace --api egl {hyprlock_cmd}" + machine.execute(f"hyprctl --instance 0 dispatch exec '{hyprlock_cmd}'") + + wait_for_lock_exit_status, out = machine.execute("WAYLAND_DISPLAY=wayland-1 ${lib.getExe' flake.hyprlock-test-meta "wait-for-lock"}") + print(f"Wait for lock exit code: {wait_for_lock_exit_status}") + if wait_for_lock_exit_status != 0: + break + + _, hyprlock_pid = machine.execute("pidof hyprlock") + print(f"Hyprlock pid {hyprlock_pid}") + + # wrong password + machine.send_chars("asdf\n") + + machine.execute("sleep 3") # default fail_timeout is 2 seconds + + # correct password + machine.send_chars("abcdefghijklmnopqrstuvwxyz1234567890-=!@#$%^&*()_+[]{};':\"]\\|,./<>?`~") + machine.send_key("alt_r-a") + machine.send_key("alt_r-o") + machine.send_key("alt_r-u") + machine.send_key("alt_r-apostrophe") + machine.send_key("c") + machine.send_key("alt_r-apostrophe") + machine.send_key("n") + machine.send_key("alt_r-apostrophe") + machine.send_key("o") + machine.send_key("alt_r-apostrophe") + machine.send_key("s") + machine.send_key("alt_r-apostrophe") + machine.send_key("z") + machine.send_chars("\n") + + machine.execute(f"waitpid {hyprlock_pid}") + _, exit_status = machine.execute("cat /tmp/exit_status") + print(f"Hyprlock exited with {exit_status}") + + machine.copy_from_vm(log_file_path, "logs") + if ${APITRACE_RECORD_PY}: + machine.copy_from_vm(log_file_path + ".trace", "traces") + + _, out = machine.execute(f"cat {log_file_path}") + print(f"Hyprlock log:\n{out}") + _, out = machine.execute(f"cat {log_file_path}") + + if not exit_status or int(exit_status) != 0: + break + + + machine.execute("hyprctl --instance 0 dispatch exit") + + _, exit_status = machine.execute("cat /tmp/exit_status") + # For the github runner, just to make sure wen don't accidentally succeed + if not exit_status.strip(): + _, __ = machine.execute("echo 99 >/tmp/exit_status") + exit_status = "99" + + machine.copy_from_vm("/tmp/exit_status") + assert int(exit_status) == 0, f"hyprlock exit code != 0 (exited with {exit_status})" + + # Finally - shutdown + machine.shutdown() + ''; + }; +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt new file mode 100644 index 00000000..81b562c3 --- /dev/null +++ b/tests/CMakeLists.txt @@ -0,0 +1,57 @@ +cmake_minimum_required(VERSION 3.27) + +project(hyprlock-test-meta DESCRIPTION "Package files used for hyprlock's integration tests") + +include(GNUInstallDirs) + +set(CMAKE_CXX_STANDARD 23) + +find_package(PkgConfig REQUIRED) +find_package(hyprwayland-scanner 0.4.4 REQUIRED) + +pkg_check_modules(wfldeps REQUIRED IMPORTED_TARGET + hyprland-protocols>=0.6.0 + hyprutils>=0.5.0 + wayland-client + wayland-protocols>=1.35 +) + +add_executable(wait-for-lock "waitForLock.cpp") + +target_link_libraries(wait-for-lock PRIVATE PkgConfig::wfldeps) + +# protocols +pkg_get_variable(WAYLAND_PROTOCOLS_DIR wayland-protocols pkgdatadir) +pkg_get_variable(WAYLAND_SCANNER_PKGDATA_DIR wayland-scanner pkgdatadir) +pkg_get_variable(HYPRLAND_PROTOCOLS hyprland-protocols pkgdatadir) +message(STATUS "Found hyprland-protocols at ${HYPRLAND_PROTOCOLS}") + +make_directory(${CMAKE_SOURCE_DIR}/protocols) +target_include_directories(wait-for-lock PRIVATE ${CMAKE_SOURCE_DIR}/protocols) + +# wayland client +add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp + ${CMAKE_SOURCE_DIR}/protocols/wayland.hpp + COMMAND hyprwayland-scanner --wayland-enums --client + ${WAYLAND_SCANNER_PKGDATA_DIR}/wayland.xml ${CMAKE_SOURCE_DIR}/protocols/) +target_sources(wait-for-lock PRIVATE ${CMAKE_SOURCE_DIR}/protocols/wayland.cpp) + +# hyprland-lock-notify-v1 +add_custom_command( + OUTPUT ${CMAKE_SOURCE_DIR}/protocols/hyprland-lock-notify-v1.cpp + ${CMAKE_SOURCE_DIR}/protocols/hyprland-lock-notify-v1.hpp + COMMAND hyprwayland-scanner --client ${HYPRLAND_PROTOCOLS}/protocols/hyprland-lock-notify-v1.xml + ${CMAKE_SOURCE_DIR}/protocols/) +target_sources(wait-for-lock PRIVATE ${CMAKE_SOURCE_DIR}/protocols/hyprland-lock-notify-v1.cpp) + +install(TARGETS wait-for-lock) + +install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/hyprland.conf + DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr) + +file(GLOB_RECURSE TESTCONFIGS CONFIGURE_DEPENDS "configs/*.conf") +install(FILES ${TESTCONFIGS} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr/configs) + +file(GLOB_RECURSE TESTCONFIGS CONFIGURE_DEPENDS "assets/*.png") +install(FILES ${TESTCONFIGS} DESTINATION ${CMAKE_INSTALL_DATAROOTDIR}/hypr/assets) diff --git a/tests/assets/avatar.png b/tests/assets/avatar.png new file mode 100644 index 00000000..3fc7d336 Binary files /dev/null and b/tests/assets/avatar.png differ diff --git a/tests/assets/background.png b/tests/assets/background.png new file mode 100644 index 00000000..6a03930a Binary files /dev/null and b/tests/assets/background.png differ diff --git a/tests/configs/images.conf b/tests/configs/images.conf new file mode 100644 index 00000000..c88e9b14 --- /dev/null +++ b/tests/configs/images.conf @@ -0,0 +1,64 @@ +background { + monitor= + path=/etc/hyprlock/assets/background.png +} + +image { + monitor= + path=/etc/hyprlock/assets/avatar.png + size=150 + position=0, 50 + halign=center + valign=center + border_size=3 + shadow_passes=1 + shadow_size=5 + shadow_boost=0.5 +} + +general { + hide_cursor=true +} + +input-field { + monitor= + size=50, 50 + capslock_color=rgb(CB7459) + dots_rounding=0 + dots_size=0.35 + dots_spacing=0.1 + dots_text_format=* + fade_on_empty=true + font_color=rgb(8F8F8F) + font_family=Noto Sans + inner_color=rgba(00000000) + outer_color=rgba(FFF7EDa0) + outline_thickness=4 + position=0, -5% + rounding=-1 + halign=center + valign=center +} + +label { + monitor= + color=rgb(FFF7ED) + font_family=Noto Sans + font_size=40 + position=0, 10% + text=$TIME $FAIL + halign=center + valign=center +} + +label { + monitor= + color=rgba(BEBEBEA0) + font_family=Noto Sans + font_size=24 + position=0, 15% + text=cmd[update:10000] date '+%A %d %B %Y' + halign=center + valign=center +} + diff --git a/tests/configs/layout_and_shape.conf b/tests/configs/layout_and_shape.conf new file mode 100644 index 00000000..206b42fb --- /dev/null +++ b/tests/configs/layout_and_shape.conf @@ -0,0 +1,90 @@ +background { + color=rgba(255, 255, 255, 0) +} + +shape { + size = 90%, 90% + position = 0, 0 + color = rgba(0, 0, 0, 0.5) + border_color = rgba(255, 255, 0, 1.0) + rounding = 5 + border_size = 10 + halign = center + valign = center +} + +shape { + size = 50%, 50% + position = -10, -10 + color = rgb(0, 0, 255) + rounding = 5 + border_size = 0 + halign = center + valign = center +} + +shape { + size = 50%, 50% + position = +10, +10 + color = rgb(00ff00) + color = rgba(00ff00ff) + rounding = 5 + border_size = 0 + halign = center + valign = center +} + +# Top left corner +shape { + size = 10%, 10% + position = 10, -10 + color = rgba(0, 255, 0, 1.0) + rounding = 5 + border_size = 0 + halign = left + valign = top +} + +# Top right corner +shape { + size = 10%, 10% + position = -10, -10 + color = rgba(0, 255, 0, 1.0) + rounding = 5 + border_size = 0 + halign = right + valign = top +} + +# Bottom left corner +shape { + size = 10%, 10% + position = 10, 10 + color = rgba(0, 255, 0, 1.0) + rounding = 5 + border_size = 0 + halign = left + valign = bottom +} + +# Bottom right corner +shape { + size = 10%, 10% + position = -10, 10 + color = rgba(0, 255, 0, 1.0) + rounding = 5 + border_size = 0 + halign = right + valign = bottom +} + +# Origin of shape centered +shape { + size = 10%, 10% + position = 50%, 50% + color = rgb(ff0000) + rounding = 5 + border_size = 0 + halign = left + valign = bottom +} diff --git a/tests/configs/lots_of_label_updates.conf b/tests/configs/lots_of_label_updates.conf new file mode 100644 index 00000000..ad841991 --- /dev/null +++ b/tests/configs/lots_of_label_updates.conf @@ -0,0 +1,221 @@ +background { + monitor= + blur_passes=2 + blur_size=10 + path=screenshot +} + +general { + hide_cursor=true +} + +input-field { + monitor= + size=50, 50 + capslock_color=rgb(CB7459) + dots_rounding=0 + dots_size=0.35 + dots_spacing=0.1 + dots_text_format=* + fade_on_empty=false + font_color=rgb(8F8F8F) + font_family=Noto Sans + inner_color=rgba(00000000) + outer_color=rgba(FFF7EDa0) + outline_thickness=4 + placeholder_text=$PROMPT + fail_text=$FAIL ($ATTEMPTS) + position=0, -5% + rounding=-1 + halign=center + valign=center +} + +label { + monitor= + color=rgb(FFF7ED) + font_family=Noto Sans + font_size=40 + position=0, 10% + text=$TIME $FAIL + halign=center + valign=center +} + +label { + monitor= + color=rgba(BEBEBEA0) + font_family=Noto Sans + font_size=24 + position=0, 15% + text=cmd[update:10000] date '+%A %d %B %Y' + halign=center + valign=center +} + +label { + text = cmd[update:200] date +'%H:%M:%S:%N' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, -200 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S:%N' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, -150 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S:%N' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, -100 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S:%N' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, -50 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 0 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 50 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 100 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 150 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 200 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 250 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 300 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 350 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 400 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 450 + halign = left + valign = center +} + +label { + text = cmd[update:200] date +'%H:%M:%S' + color = rgba(129, 162, 190, 1.0) + font_size = 35 + font_family = Noto Sans + + position = 150, 500 + halign = left + valign = center +} + + diff --git a/tests/hyprland.conf b/tests/hyprland.conf new file mode 100644 index 00000000..91a68d2c --- /dev/null +++ b/tests/hyprland.conf @@ -0,0 +1,41 @@ +monitor = Virtual-1,1920x1080@60,auto-right,1 +monitor = ,disabled + +input { + # to type german and polish specific letters via compose keys + kb_layout = eu +} + +render { + ctm_animation = 0 + cm_enabled = 0 + cm_fs_passthrough = 0 +} + +animations { + enabled = 0 +} + +decoration { + shadow { + enabled = 0 + } +} + +xwayland { + enabled = 0 +} + +misc { + disable_hyprland_logo = 1 + disable_splash_rendering = 1 + force_default_wallpaper = 0 + key_press_enables_dpms = 1 +} + +debug { + disable_logs = 0 +} + +# we use this in nix/tests/default.nix to be able to wait for hyprland startup +exec-once = echo "startup" > /tmp/hyprland_exec_once_notification diff --git a/tests/waitForLock.cpp b/tests/waitForLock.cpp new file mode 100644 index 00000000..dab433b0 --- /dev/null +++ b/tests/waitForLock.cpp @@ -0,0 +1,70 @@ +// This program exits when the wayland session gets locked, or 10 seconds have passed. +// In case it is already locked, it shall return immediatly. +// It uses hyprland-lock-notify to accomplish that. +#include "hyprland-lock-notify-v1.hpp" +#include "wayland.hpp" + +#include +#include +#include +#include + +using namespace Hyprutils::Memory; + +#define SP CSharedPointer + +struct SSessionLockState { + SP m_lockNotifier = nullptr; + SP m_lockNotification = nullptr; + bool m_didLock = false; +}; + +int main(int argc, char** argv) { + auto wlDisplay = wl_display_connect(nullptr); + if (!wlDisplay) { + std::println(stderr, "Failed to connect to Wayland display"); + return -1; + } + + auto state = makeShared(); + + auto wlRegistry = makeShared((wl_proxy*)wl_display_get_registry(wlDisplay)); + wlRegistry->setGlobal([state](CCWlRegistry* r, uint32_t name, const char* interface, uint32_t version) { + const std::string IFACE = interface; + + if (IFACE == hyprland_lock_notifier_v1_interface.name) + state->m_lockNotifier = + makeShared((wl_proxy*)wl_registry_bind((wl_registry*)r->resource(), name, &hyprland_lock_notifier_v1_interface, version)); + }); + + wl_display_roundtrip(wlDisplay); + + if (!state->m_lockNotifier) { + std::print(stderr, "Failed to bind to lock notifier\n"); + return -1; + } + + state->m_lockNotification = makeShared(state->m_lockNotifier->sendGetLockNotification()); + state->m_lockNotification->setLocked([state](auto) { state->m_didLock = true; }); + + wl_display_flush(wlDisplay); + + const auto STARTTP = std::chrono::system_clock::now(); + while (!state->m_didLock) { + if (wl_display_prepare_read(wlDisplay) == 0) { + wl_display_read_events(wlDisplay); + wl_display_dispatch_pending(wlDisplay); + } else { + wl_display_dispatch(wlDisplay); + } + + if (std::chrono::system_clock::now() - STARTTP > std::chrono::seconds(10)) { + std::print(stderr, "Timeout waiting for the lock event\n"); + return -1; + } + + std::this_thread::sleep_for(std::chrono::milliseconds(10)); + } + + return 0; +}