From df09f24b48604d2c4fd6bceb6aec2d03cd72f976 Mon Sep 17 00:00:00 2001 From: Sammy99jsp Date: Mon, 7 Jul 2025 18:54:48 +0100 Subject: [PATCH 1/8] Fresh Start (again!) --- .devcontainer/devcontainer.json | 57 - .devcontainer/pl10k.zsh | 271 -- .devcontainer/setup.sh | 11 - .github/ISSUE_TEMPLATE/bug_report.yml | 109 - .../pull_request_template.md | 10 - .github/dependabot.yml | 11 - .github/workflows/rust-clippy.yml | 54 - .vscode/extensions.json | 3 - .vscode/settings.json | 30 - CONTRIBUTING.md | 41 - Cargo.lock | 2189 +---------------- Cargo.toml | 51 +- DefaultConfig.jsonc | 11 - Devcontainer.md | 72 - ISSUE_TEMPLATE/bug_report.md | 30 - LICENSE | 674 ----- README.md | 21 - resources/avdanos-cursor-64.rgba | Bin 16384 -> 0 bytes resources/avdanos_cursor.rgba | Bin 16384 -> 0 bytes src/compat/mod.rs | 6 - src/compat/xdg_base_dirs.rs | 94 - src/compositor/backend/mod.rs | 14 - src/compositor/backend/udev.rs | 1219 --------- src/compositor/backend/winit.rs | 346 --- src/compositor/components/cursor.rs | 94 - src/compositor/components/mod.rs | 5 - src/compositor/drawing.rs | 88 - src/compositor/focus.rs | 204 -- src/compositor/handlers/input.rs | 126 - src/compositor/handlers/mod.rs | 3 - src/compositor/handlers/output.rs | 71 - src/compositor/handlers/xdg.rs | 3 - src/compositor/input/mod.rs | 922 ------- src/compositor/mod.rs | 31 - src/compositor/render.rs | 103 - src/compositor/shell/avwindow/mod.rs | 486 ---- src/compositor/shell/avwindow/state.rs | 1 - src/compositor/shell/grabs/grab_move.rs | 78 - src/compositor/shell/grabs/grab_resize.rs | 442 ---- src/compositor/shell/grabs/mod.rs | 5 - src/compositor/shell/mod.rs | 340 --- src/compositor/shell/x11.rs | 345 --- src/compositor/shell/xdg/decoration.rs | 85 - src/compositor/shell/xdg/handlers.rs | 63 - src/compositor/shell/xdg/mod.rs | 455 ---- src/compositor/state/mod.rs | 321 --- src/compositor/state/utils.rs | 300 --- src/config/config.rs | 128 - src/config/errors.rs | 50 - src/config/mod.rs | 8 - src/config/section.rs | 199 -- src/config/sections/keybinds.rs | 17 - src/config/sections/mod.rs | 1 - src/config/templating/avvalue.rs | 159 -- src/config/templating/macro.rs | 443 ---- src/config/templating/mod.rs | 6 - src/consts.rs | 10 - src/core/error.rs | 339 --- src/core/keyboard/avkeys.rs | 151 -- src/core/keyboard/mod.rs | 3 - src/core/mod.rs | 2 - src/macros/Cargo.toml | 18 - src/macros/src/avvalue.rs | 147 -- src/macros/src/delcaration.rs | 122 - src/macros/src/lib.rs | 462 ---- src/main.rs | 34 +- 66 files changed, 9 insertions(+), 12185 deletions(-) delete mode 100644 .devcontainer/devcontainer.json delete mode 100644 .devcontainer/pl10k.zsh delete mode 100644 .devcontainer/setup.sh delete mode 100644 .github/ISSUE_TEMPLATE/bug_report.yml delete mode 100644 .github/PULL_REQUEST_TEMPLATE/pull_request_template.md delete mode 100644 .github/dependabot.yml delete mode 100644 .github/workflows/rust-clippy.yml delete mode 100644 .vscode/extensions.json delete mode 100644 .vscode/settings.json delete mode 100644 CONTRIBUTING.md delete mode 100644 DefaultConfig.jsonc delete mode 100644 Devcontainer.md delete mode 100644 ISSUE_TEMPLATE/bug_report.md delete mode 100644 LICENSE delete mode 100644 README.md delete mode 100644 resources/avdanos-cursor-64.rgba delete mode 100644 resources/avdanos_cursor.rgba delete mode 100644 src/compat/mod.rs delete mode 100644 src/compat/xdg_base_dirs.rs delete mode 100644 src/compositor/backend/mod.rs delete mode 100644 src/compositor/backend/udev.rs delete mode 100644 src/compositor/backend/winit.rs delete mode 100644 src/compositor/components/cursor.rs delete mode 100644 src/compositor/components/mod.rs delete mode 100644 src/compositor/drawing.rs delete mode 100644 src/compositor/focus.rs delete mode 100644 src/compositor/handlers/input.rs delete mode 100644 src/compositor/handlers/mod.rs delete mode 100644 src/compositor/handlers/output.rs delete mode 100644 src/compositor/handlers/xdg.rs delete mode 100644 src/compositor/input/mod.rs delete mode 100644 src/compositor/mod.rs delete mode 100644 src/compositor/render.rs delete mode 100644 src/compositor/shell/avwindow/mod.rs delete mode 100644 src/compositor/shell/avwindow/state.rs delete mode 100644 src/compositor/shell/grabs/grab_move.rs delete mode 100644 src/compositor/shell/grabs/grab_resize.rs delete mode 100644 src/compositor/shell/grabs/mod.rs delete mode 100644 src/compositor/shell/mod.rs delete mode 100644 src/compositor/shell/x11.rs delete mode 100644 src/compositor/shell/xdg/decoration.rs delete mode 100644 src/compositor/shell/xdg/handlers.rs delete mode 100644 src/compositor/shell/xdg/mod.rs delete mode 100644 src/compositor/state/mod.rs delete mode 100644 src/compositor/state/utils.rs delete mode 100644 src/config/config.rs delete mode 100644 src/config/errors.rs delete mode 100644 src/config/mod.rs delete mode 100644 src/config/section.rs delete mode 100644 src/config/sections/keybinds.rs delete mode 100644 src/config/sections/mod.rs delete mode 100644 src/config/templating/avvalue.rs delete mode 100644 src/config/templating/macro.rs delete mode 100644 src/config/templating/mod.rs delete mode 100644 src/consts.rs delete mode 100644 src/core/error.rs delete mode 100644 src/core/keyboard/avkeys.rs delete mode 100644 src/core/keyboard/mod.rs delete mode 100644 src/core/mod.rs delete mode 100644 src/macros/Cargo.toml delete mode 100644 src/macros/src/avvalue.rs delete mode 100644 src/macros/src/delcaration.rs delete mode 100644 src/macros/src/lib.rs diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json deleted file mode 100644 index 41b91b1..0000000 --- a/.devcontainer/devcontainer.json +++ /dev/null @@ -1,57 +0,0 @@ -// For format details, see https://aka.ms/devcontainer.json. For config options, see the -// README at: https://github.com/devcontainers/templates/tree/main/src/ubuntu -{ - "name": "Ubuntu", - // Or use a Dockerfile or Docker Compose file. More info: https://containers.dev/guide/dockerfile - "image": "mcr.microsoft.com/devcontainers/base:jammy", - "features": { - "ghcr.io/devcontainers/features/common-utils:2": { - "installZsh": true, - "configureZshAsDefaultShell": true, - "installOhMyZsh": true, - "nonFreePackages": true, - "username": "root", - "userUid": "automatic", - "userGid": "automatic" - }, - "ghcr.io/rocker-org/devcontainer-features/apt-packages:1": { - "upgradePackages": true, - "packages": "pkg-config build-essential libudev-dev libwayland-dev libseat-dev libsystemd-dev libxkbcommon-dev libinput-dev libgbm-dev xauth x11-utils x11-xserver-utils libxrandr-dev libxi-dev libx11-xcb1 libx11-dev libegl1-mesa xwayland neofetch" - }, - "ghcr.io/dhoeric/features/act:1": {} - }, - - // Features to add to the dev container. More info: https://containers.dev/features. - // "features": {}, - - // Use 'forwardPorts' to make a list of ports inside the container available locally. - // "forwardPorts": [], - - // Use 'postCreateCommand' to run commands after the container is created. - "postCreateCommand": "sh /workspaces/Compositor/.devcontainer/setup.sh", - "customizations": { - "vscode": { - "extensions": [ - "rust-lang.rust-analyzer", - "serayuzgur.crates", - "tamasfe.even-better-toml", - "vadimcn.vscode-lldb", - "ms-vsliveshare.vsliveshare", - "GitHub.vscode-pull-request-github", - "formulahendry.code-runner", - "streetsidesoftware.code-spell-checker", - "huibizhang.codesnap-plus" - ] - } - }, - - // Configure tool-specific properties. - // "customizations": {}, - - // Uncomment to connect as root instead. More info: https://aka.ms/dev-containers-non-root. - "remoteUser": "root", - "runArgs": [ - "--privileged", - "--network=host" - ] -} diff --git a/.devcontainer/pl10k.zsh b/.devcontainer/pl10k.zsh deleted file mode 100644 index 2b44b70..0000000 --- a/.devcontainer/pl10k.zsh +++ /dev/null @@ -1,271 +0,0 @@ -'builtin' 'local' '-a' 'p10k_config_opts' -[[ ! -o 'aliases' ]] || p10k_config_opts+=('aliases') -[[ ! -o 'sh_glob' ]] || p10k_config_opts+=('sh_glob') -[[ ! -o 'no_brace_expand' ]] || p10k_config_opts+=('no_brace_expand') -'builtin' 'setopt' 'no_aliases' 'no_sh_glob' 'brace_expand' -() { - emulate -L zsh -o extended_glob - unset -m '(POWERLEVEL9K_*|DEFAULT_USER)~POWERLEVEL9K_GITSTATUS_DIR' - [[ $ZSH_VERSION == (5.<1->*|<6->.*) ]] || return - typeset -g POWERLEVEL9K_LEFT_PROMPT_ELEMENTS=( - # =========================[ Line #1 ]========================= - # os_icon # os identifier - devcontainer # devcontainer - dir # current directory - vcs # git status - # =========================[ Line #2 ]========================= - newline # \n - prompt_char # prompt symbol - ) - typeset -g POWERLEVEL9K_RIGHT_PROMPT_ELEMENTS=( - # =========================[ Line #1 ]========================= - status # exit code of the last command - background_jobs # presence of background jobs - # direnv # direnv status (https://direnv.net/) - rust_version # rustc version (https://www.rust-lang.org) - # context # user@hostname - # vpn_ip # virtual private network indicator - # load # CPU load - # disk_usage # disk usage - # ram # free RAM - # swap # used swap - # todo # todo items (https://github.com/todotxt/todo.txt-cli) - # timewarrior # timewarrior tracking status (https://timewarrior.net/) - # taskwarrior # taskwarrior task count (https://taskwarrior.org/) - # cpu_arch # CPU architecture - # =========================[ Line #2 ]========================= - newline # \n - # ip # ip address and bandwidth usage for a specified network interface - # public_ip # public IP address - # proxy # system-wide http/https/ftp proxy - # battery # internal battery - command_execution_time # duration of the last command - time # current time - ) - typeset -g POWERLEVEL9K_MODE=powerline - typeset -g POWERLEVEL9K_ICON_PADDING=none - typeset -g POWERLEVEL9K_ICON_BEFORE_CONTENT= - typeset -g POWERLEVEL9K_PROMPT_ADD_NEWLINE=true - typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_PREFIX= - typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_PREFIX= - typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_PREFIX= - typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_SUFFIX= - typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_SUFFIX= - typeset -g POWERLEVEL9K_MULTILINE_LAST_PROMPT_SUFFIX= - typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR='·' - typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_BACKGROUND= - typeset -g POWERLEVEL9K_MULTILINE_NEWLINE_PROMPT_GAP_BACKGROUND= - if [[ $POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_CHAR != ' ' ]]; then - typeset -g POWERLEVEL9K_MULTILINE_FIRST_PROMPT_GAP_FOREGROUND=240 - typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_FIRST_SEGMENT_END_SYMBOL='%{%}' - typeset -g POWERLEVEL9K_EMPTY_LINE_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='%{%}' - fi - typeset -g POWERLEVEL9K_BACKGROUND=236 - typeset -g POWERLEVEL9K_LEFT_SUBSEGMENT_SEPARATOR='%244F\uE0B1' - typeset -g POWERLEVEL9K_RIGHT_SUBSEGMENT_SEPARATOR='%244F\uE0B3' - typeset -g POWERLEVEL9K_LEFT_SEGMENT_SEPARATOR='\uE0B0' - typeset -g POWERLEVEL9K_RIGHT_SEGMENT_SEPARATOR='\uE0B2' - typeset -g POWERLEVEL9K_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL='\uE0B0' - typeset -g POWERLEVEL9K_RIGHT_PROMPT_FIRST_SEGMENT_START_SYMBOL='\uE0B2' - typeset -g POWERLEVEL9K_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL='' - typeset -g POWERLEVEL9K_RIGHT_PROMPT_LAST_SEGMENT_END_SYMBOL='' - typeset -g POWERLEVEL9K_EMPTY_LINE_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL= - typeset -g POWERLEVEL9K_OS_ICON_FOREGROUND=255 - typeset -g POWERLEVEL9K_PROMPT_CHAR_BACKGROUND= - typeset -g POWERLEVEL9K_PROMPT_CHAR_OK_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=76 - typeset -g POWERLEVEL9K_PROMPT_CHAR_ERROR_{VIINS,VICMD,VIVIS,VIOWR}_FOREGROUND=196 - typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIINS_CONTENT_EXPANSION='❯' - typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VICMD_CONTENT_EXPANSION='❮' - typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIVIS_CONTENT_EXPANSION='V' - typeset -g POWERLEVEL9K_PROMPT_CHAR_{OK,ERROR}_VIOWR_CONTENT_EXPANSION='▶' - typeset -g POWERLEVEL9K_PROMPT_CHAR_OVERWRITE_STATE=true - typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_LAST_SEGMENT_END_SYMBOL= - typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_PROMPT_FIRST_SEGMENT_START_SYMBOL= - typeset -g POWERLEVEL9K_PROMPT_CHAR_LEFT_{LEFT,RIGHT}_WHITESPACE= - typeset -g POWERLEVEL9K_DIR_FOREGROUND=31 - typeset -g POWERLEVEL9K_SHORTEN_STRATEGY=truncate_to_unique - typeset -g POWERLEVEL9K_SHORTEN_DELIMITER= - typeset -g POWERLEVEL9K_DIR_SHORTENED_FOREGROUND=103 - typeset -g POWERLEVEL9K_DIR_ANCHOR_FOREGROUND=39 - typeset -g POWERLEVEL9K_DIR_ANCHOR_BOLD=true - local anchor_files=( - .bzr - .citc - .git - .hg - .shorten_folder_marker - .svn - CVS - Cargo.toml - ) - typeset -g POWERLEVEL9K_SHORTEN_FOLDER_MARKER="(${(j:|:)anchor_files})" - typeset -g POWERLEVEL9K_DIR_TRUNCATE_BEFORE_MARKER=false - typeset -g POWERLEVEL9K_SHORTEN_DIR_LENGTH=1 - typeset -g POWERLEVEL9K_DIR_MAX_LENGTH=80 - typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS=40 - typeset -g POWERLEVEL9K_DIR_MIN_COMMAND_COLUMNS_PCT=50 - typeset -g POWERLEVEL9K_DIR_HYPERLINK=false - typeset -g POWERLEVEL9K_DIR_SHOW_WRITABLE=v3 - typeset -g POWERLEVEL9K_LOCK_ICON='∅' - typeset -g POWERLEVEL9K_DIR_CLASSES=() - typeset -g POWERLEVEL9K_VCS_BRANCH_ICON='\UE0A0 ' - typeset -g POWERLEVEL9K_VCS_UNTRACKED_ICON='?' - function my_git_formatter() { - emulate -L zsh - if [[ -n $P9K_CONTENT ]]; then - typeset -g my_git_format=$P9K_CONTENT - return - fi - if (( $1 )); then - local meta='%246F' - local clean='%76F' - local modified='%178F' - local untracked='%39F' - local conflicted='%196F' - else - local meta='%244F' - local clean='%244F' - local modified='%244F' - local untracked='%244F' - local conflicted='%244F' - fi - local res - if [[ -n $VCS_STATUS_LOCAL_BRANCH ]]; then - local branch=${(V)VCS_STATUS_LOCAL_BRANCH} - (( $#branch > 32 )) && branch[13,-13]="…" - res+="${clean}${(g::)POWERLEVEL9K_VCS_BRANCH_ICON}${branch//\%/%%}" - fi - if [[ -n $VCS_STATUS_TAG - && -z $VCS_STATUS_LOCAL_BRANCH - ]]; then - local tag=${(V)VCS_STATUS_TAG} - (( $#tag > 32 )) && tag[13,-13]="…" - res+="${meta}#${clean}${tag//\%/%%}" - fi - [[ -z $VCS_STATUS_LOCAL_BRANCH && -z $VCS_STATUS_TAG ]] && - res+="${meta}@${clean}${VCS_STATUS_COMMIT[1,8]}" - if [[ -n ${VCS_STATUS_REMOTE_BRANCH:#$VCS_STATUS_LOCAL_BRANCH} ]]; then - res+="${meta}:${clean}${(V)VCS_STATUS_REMOTE_BRANCH//\%/%%}" - fi - if [[ $VCS_STATUS_COMMIT_SUMMARY == (|*[^[:alnum:]])(wip|WIP)(|[^[:alnum:]]*) ]]; then - res+=" ${modified}wip" - fi - (( VCS_STATUS_COMMITS_BEHIND )) && res+=" ${clean}⇣${VCS_STATUS_COMMITS_BEHIND}" - (( VCS_STATUS_COMMITS_AHEAD && !VCS_STATUS_COMMITS_BEHIND )) && res+=" " - (( VCS_STATUS_COMMITS_AHEAD )) && res+="${clean}⇡${VCS_STATUS_COMMITS_AHEAD}" - (( VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" ${clean}⇠${VCS_STATUS_PUSH_COMMITS_BEHIND}" - (( VCS_STATUS_PUSH_COMMITS_AHEAD && !VCS_STATUS_PUSH_COMMITS_BEHIND )) && res+=" " - (( VCS_STATUS_PUSH_COMMITS_AHEAD )) && res+="${clean}⇢${VCS_STATUS_PUSH_COMMITS_AHEAD}" - (( VCS_STATUS_STASHES )) && res+=" ${clean}*${VCS_STATUS_STASHES}" - [[ -n $VCS_STATUS_ACTION ]] && res+=" ${conflicted}${VCS_STATUS_ACTION}" - (( VCS_STATUS_NUM_CONFLICTED )) && res+=" ${conflicted}~${VCS_STATUS_NUM_CONFLICTED}" - (( VCS_STATUS_NUM_STAGED )) && res+=" ${modified}+${VCS_STATUS_NUM_STAGED}" - (( VCS_STATUS_NUM_UNSTAGED )) && res+=" ${modified}!${VCS_STATUS_NUM_UNSTAGED}" - (( VCS_STATUS_NUM_UNTRACKED )) && res+=" ${untracked}${(g::)POWERLEVEL9K_VCS_UNTRACKED_ICON}${VCS_STATUS_NUM_UNTRACKED}" - (( VCS_STATUS_HAS_UNSTAGED == -1 )) && res+=" ${modified}─" - typeset -g my_git_format=$res - } - functions -M my_git_formatter 2>/dev/null - typeset -g POWERLEVEL9K_VCS_MAX_INDEX_SIZE_DIRTY=-1 - typeset -g POWERLEVEL9K_VCS_DISABLED_WORKDIR_PATTERN='~' - typeset -g POWERLEVEL9K_VCS_DISABLE_GITSTATUS_FORMATTING=true - typeset -g POWERLEVEL9K_VCS_CONTENT_EXPANSION='${$((my_git_formatter(1)))+${my_git_format}}' - typeset -g POWERLEVEL9K_VCS_LOADING_CONTENT_EXPANSION='${$((my_git_formatter(0)))+${my_git_format}}' - typeset -g POWERLEVEL9K_VCS_{STAGED,UNSTAGED,UNTRACKED,CONFLICTED,COMMITS_AHEAD,COMMITS_BEHIND}_MAX_NUM=-1 - typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_COLOR=76 - typeset -g POWERLEVEL9K_VCS_LOADING_VISUAL_IDENTIFIER_COLOR=244 - typeset -g POWERLEVEL9K_VCS_VISUAL_IDENTIFIER_EXPANSION= - typeset -g POWERLEVEL9K_VCS_BACKENDS=(git) - typeset -g POWERLEVEL9K_VCS_CLEAN_FOREGROUND=76 - typeset -g POWERLEVEL9K_VCS_UNTRACKED_FOREGROUND=76 - typeset -g POWERLEVEL9K_VCS_MODIFIED_FOREGROUND=178 - typeset -g POWERLEVEL9K_STATUS_EXTENDED_STATES=true - typeset -g POWERLEVEL9K_STATUS_OK=false - typeset -g POWERLEVEL9K_STATUS_OK_FOREGROUND=70 - typeset -g POWERLEVEL9K_STATUS_OK_VISUAL_IDENTIFIER_EXPANSION='✔' - typeset -g POWERLEVEL9K_STATUS_OK_PIPE=true - typeset -g POWERLEVEL9K_STATUS_OK_PIPE_FOREGROUND=70 - typeset -g POWERLEVEL9K_STATUS_OK_PIPE_VISUAL_IDENTIFIER_EXPANSION='✔' - typeset -g POWERLEVEL9K_STATUS_ERROR=false - typeset -g POWERLEVEL9K_STATUS_ERROR_FOREGROUND=160 - typeset -g POWERLEVEL9K_STATUS_ERROR_VISUAL_IDENTIFIER_EXPANSION='✘' - typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL=true - typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_FOREGROUND=160 - typeset -g POWERLEVEL9K_STATUS_VERBOSE_SIGNAME=false - typeset -g POWERLEVEL9K_STATUS_ERROR_SIGNAL_VISUAL_IDENTIFIER_EXPANSION='✘' - typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE=true - typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_FOREGROUND=160 - typeset -g POWERLEVEL9K_STATUS_ERROR_PIPE_VISUAL_IDENTIFIER_EXPANSION='✘' - typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_THRESHOLD=3 - typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_PRECISION=0 - typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FOREGROUND=248 - typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_FORMAT='d h m s' - typeset -g POWERLEVEL9K_COMMAND_EXECUTION_TIME_VISUAL_IDENTIFIER_EXPANSION= - typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VERBOSE=false - typeset -g POWERLEVEL9K_BACKGROUND_JOBS_FOREGROUND=37 - typeset -g POWERLEVEL9K_BACKGROUND_JOBS_VISUAL_IDENTIFIER_EXPANSION='≡' - typeset -g POWERLEVEL9K_DIRENV_FOREGROUND=178 - typeset -g POWERLEVEL9K_DISK_USAGE_NORMAL_FOREGROUND=35 - typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_FOREGROUND=220 - typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_FOREGROUND=160 - typeset -g POWERLEVEL9K_DISK_USAGE_WARNING_LEVEL=90 - typeset -g POWERLEVEL9K_DISK_USAGE_CRITICAL_LEVEL=95 - typeset -g POWERLEVEL9K_DISK_USAGE_ONLY_WARNING=false - typeset -g POWERLEVEL9K_RAM_FOREGROUND=66 - typeset -g POWERLEVEL9K_SWAP_FOREGROUND=96 - typeset -g POWERLEVEL9K_LOAD_WHICH=5 - typeset -g POWERLEVEL9K_LOAD_NORMAL_FOREGROUND=66 - typeset -g POWERLEVEL9K_LOAD_WARNING_FOREGROUND=178 - typeset -g POWERLEVEL9K_LOAD_CRITICAL_FOREGROUND=166 - typeset -g POWERLEVEL9K_TODO_FOREGROUND=110 - typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_TOTAL=true - typeset -g POWERLEVEL9K_TODO_HIDE_ZERO_FILTERED=false - typeset -g POWERLEVEL9K_TIMEWARRIOR_FOREGROUND=110 - typeset -g POWERLEVEL9K_TIMEWARRIOR_CONTENT_EXPANSION='${P9K_CONTENT:0:24}${${P9K_CONTENT:24}:+…}' - typeset -g POWERLEVEL9K_TASKWARRIOR_FOREGROUND=74 - typeset -g POWERLEVEL9K_CPU_ARCH_FOREGROUND=172 - typeset -g POWERLEVEL9K_CONTEXT_ROOT_FOREGROUND=178 - typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_FOREGROUND=180 - typeset -g POWERLEVEL9K_CONTEXT_FOREGROUND=180 - typeset -g POWERLEVEL9K_CONTEXT_ROOT_TEMPLATE='%B%n@%m' - typeset -g POWERLEVEL9K_CONTEXT_{REMOTE,REMOTE_SUDO}_TEMPLATE='%n@%m' - typeset -g POWERLEVEL9K_CONTEXT_TEMPLATE='%n@%m' - typeset -g POWERLEVEL9K_CONTEXT_{DEFAULT,SUDO}_{CONTENT,VISUAL_IDENTIFIER}_EXPANSION= - typeset -g POWERLEVEL9K_RUST_VERSION_FOREGROUND=37 - typeset -g POWERLEVEL9K_RUST_VERSION_PROJECT_ONLY=true - typeset -g POWERLEVEL9K_PACKAGE_FOREGROUND=117 - typeset -g POWERLEVEL9K_PUBLIC_IP_FOREGROUND=94 - typeset -g POWERLEVEL9K_VPN_IP_FOREGROUND=81 - typeset -g POWERLEVEL9K_VPN_IP_CONTENT_EXPANSION= - typeset -g POWERLEVEL9K_VPN_IP_INTERFACE='(gpd|wg|(.*tun)|tailscale)[0-9]*' - typeset -g POWERLEVEL9K_VPN_IP_SHOW_ALL=false - typeset -g POWERLEVEL9K_IP_FOREGROUND=38 - typeset -g POWERLEVEL9K_IP_CONTENT_EXPANSION='${P9K_IP_RX_RATE:+%70F⇣$P9K_IP_RX_RATE }${P9K_IP_TX_RATE:+%215F⇡$P9K_IP_TX_RATE }%38F$P9K_IP_IP' - typeset -g POWERLEVEL9K_IP_INTERFACE='[ew].*' - typeset -g POWERLEVEL9K_PROXY_FOREGROUND=68 - typeset -g POWERLEVEL9K_BATTERY_LOW_THRESHOLD=20 - typeset -g POWERLEVEL9K_BATTERY_LOW_FOREGROUND=160 - typeset -g POWERLEVEL9K_BATTERY_{CHARGING,CHARGED}_FOREGROUND=70 - typeset -g POWERLEVEL9K_BATTERY_DISCONNECTED_FOREGROUND=178 - typeset -g POWERLEVEL9K_BATTERY_STAGES=('%K{232}▁' '%K{232}▂' '%K{232}▃' '%K{232}▄' '%K{232}▅' '%K{232}▆' '%K{232}▇' '%K{232}█') - typeset -g POWERLEVEL9K_BATTERY_VERBOSE=false - typeset -g POWERLEVEL9K_WIFI_FOREGROUND=68 - typeset -g POWERLEVEL9K_TIME_FOREGROUND=66 - typeset -g POWERLEVEL9K_TIME_FORMAT='%D{%H:%M:%S}' - typeset -g POWERLEVEL9K_TIME_UPDATE_ON_COMMAND=false - typeset -g POWERLEVEL9K_TIME_VISUAL_IDENTIFIER_EXPANSION= - function prompt_devcontainer() { - p10k segment -f 208 -i '' -t 'Devcontainer' - } - function instant_prompt_example() { - prompt_example - prompt_devcontainer - } - typeset -g POWERLEVEL9K_TRANSIENT_PROMPT=off - typeset -g POWERLEVEL9K_INSTANT_PROMPT=verbose - typeset -g POWERLEVEL9K_DISABLE_HOT_RELOAD=true - (( ! $+functions[p10k] )) || p10k reload -} -typeset -g POWERLEVEL9K_CONFIG_FILE=${${(%):-%x}:a} -(( ${#p10k_config_opts} )) && setopt ${p10k_config_opts[@]} -'builtin' 'unset' 'p10k_config_opts' diff --git a/.devcontainer/setup.sh b/.devcontainer/setup.sh deleted file mode 100644 index 39227e9..0000000 --- a/.devcontainer/setup.sh +++ /dev/null @@ -1,11 +0,0 @@ -git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/.oh-my-zsh/themes/powerlevel10k; -sed -i 's/devcontainers/powerlevel10k\/powerlevel10k/g' ~/.zshrc; -echo "\ -source /workspaces/Compositor/.devcontainer/pl10k.zsh\n\ -export PKG_CONFIG_PATH=\"/usr/lib/$(ls /usr/lib/|grep "linux-gnu")/pkgconfig\"\n\ -export XDG_RUNTIME_DIR=/run/user/$(id -u)\n\ -export DISPLAY=host.docker.internal:0 -" >> ~/.zshrc; -su - root -c "curl https://sh.rustup.rs -sSf | sh -s -- -y --default-toolchain nightly" -mkdir -p /run/user/$(id -u) -chmod 0700 /run/user/$(id -u) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index bbeef30..0000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,109 +0,0 @@ -name: "Bug report" -title: "[Bug] - Title" -description: Report a bug in the compositor -labels: ["bug"] -body: - - type: markdown - attributes: - value: | - ### Before you start - - **Are you looking for development help?** - ↩ We don't provide development help here. Please search for help on Google, Stack Overflow, or our [Discord server](https://discord.gg/avdanos). - - **Do you have a question?** - ↩ Please ask it on our [Discord server](https://discord.gg/avdanos) - - **Does your issue already exist?** - ↩ Please search for existing issue before making a new one. - - **Is your issue about feature request or grammar/translation?** - ↩ Submit a different type of issue on the _issue_ tab. - - **Is your issue about another repository?** - ↩ Please only submit issues related to the compositor here. If your issue is about web demo, please open an issue on the [demo repository](https://github.com/Avdan-OS/Demo). - - **Need help with Linux?** - 🙋 To get help with [Linux](https://unix.stackexchange.com/), [Ubuntu](https://askubuntu.com/), [Manjaro](https://forum.manjaro.org/), and other distributions, check the distribution's forum and support page. - - --- - - type: checkboxes - id: latest - attributes: - label: Please make sure your system is up to date - options: - - label: Yes, my system is up to date - required: true - - type: dropdown - id: arch - attributes: - label: Select your CPU architecture - description: We do not support unofficial builds, and please do not file issues about them. - options: - - x86-64 - - AArch64 - validations: - required: true - - type: dropdown - id: distro - attributes: - label: Select your Linux distribution - options: - - Archlinux - - Debian - - Ubuntu - - RHEL - - Gentoo - - Slackware - - SUSE - - Other - validations: - required: true - - type: input - id: ram - attributes: - label: Please enter your RAM size - validations: - required: true - - type: input - id: gpu - attributes: - label: Please enter your GPU model - validations: - required: true - - type: textarea - id: additional - attributes: - label: Please enter any additional hardware and software information - - type: textarea - id: details - attributes: - label: Please explain what went wrong - description: Please include as much information as possible. - validations: - required: true - - type: textarea - id: references - attributes: - label: Provide us links that may help us fix this issue, if you have any - description: Ex. Any release notes, bugs, pull requests, or documentation pages - - type: textarea - id: screenshot - attributes: - label: Provide a screenshot of the problem, if you can - - type: textarea - id: more-info - attributes: - label: Additional information - description: Anything else you want to share with us - - type: checkboxes - id: duplicate - attributes: - label: Please make sure this issue is not a duplicate of an existing issue. - options: - - label: Yes, I have searched for this issue on this repository and none were found - required: true - - type: markdown - attributes: - value: | - **Thank you for filling this bug report, and we will try to respond as soon as possible.** diff --git a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md b/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md deleted file mode 100644 index 506a017..0000000 --- a/.github/PULL_REQUEST_TEMPLATE/pull_request_template.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -title: "[Compositor] - [Title]" ---- - -**Please describe.** -A clear and concise description of what the pr does(if possible). - -**Add X to say yes** -You have checked that this pr don't have errors - [] -You have checked that this pr follow code guidelines - [] diff --git a/.github/dependabot.yml b/.github/dependabot.yml deleted file mode 100644 index 4e30578..0000000 --- a/.github/dependabot.yml +++ /dev/null @@ -1,11 +0,0 @@ -# To get started with Dependabot version updates, you'll need to specify which -# package ecosystems to update and where the package manifests are located. -# Please see the documentation for all configuration options: -# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates - -version: 2 -updates: - - package-ecosystem: "cargo" # See documentation for possible values - directory: "/" # Location of package manifests - schedule: - interval: "daily" diff --git a/.github/workflows/rust-clippy.yml b/.github/workflows/rust-clippy.yml deleted file mode 100644 index 71d4248..0000000 --- a/.github/workflows/rust-clippy.yml +++ /dev/null @@ -1,54 +0,0 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# rust-clippy is a tool that runs a bunch of lints to catch common -# mistakes in your Rust code and help improve your Rust code. -# More details at https://github.com/rust-lang/rust-clippy -# and https://rust-lang.github.io/rust-clippy/ - -name: rust-clippy analyze - -on: - push: - branches: [ "dev", main ] - pull_request: - # The branches below must be a subset of the branches above - branches: [ "dev" ] - schedule: - - cron: '44 22 * * 5' - -jobs: - rust-clippy-analyze: - name: Run rust-clippy analyzing - runs-on: ubuntu-latest - permissions: - contents: read - security-events: write - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Install Rust toolchain - uses: actions-rs/toolchain@16499b5e05bf2e26879000db0c1d13f7e13fa3af #@v1 - with: - profile: minimal - toolchain: stable - components: clippy - override: true - - - name: Install required cargo - run: cargo install clippy-sarif sarif-fmt - - - name: Run rust-clippy - run: - sudo cargo clippy - --all-features - --message-format=json | clippy-sarif | tee rust-clippy-results.sarif | sarif-fmt - continue-on-error: true - - - name: Upload analysis results to GitHub - uses: github/codeql-action/upload-sarif@v1 - with: - sarif_file: rust-clippy-results.sarif - wait-for-processing: true diff --git a/.vscode/extensions.json b/.vscode/extensions.json deleted file mode 100644 index 082bc94..0000000 --- a/.vscode/extensions.json +++ /dev/null @@ -1,3 +0,0 @@ -{ - "recommendations": ["ms-vscode-remote.remote-containers"] -} diff --git a/.vscode/settings.json b/.vscode/settings.json deleted file mode 100644 index 2f1a4fd..0000000 --- a/.vscode/settings.json +++ /dev/null @@ -1,30 +0,0 @@ -{ - "cSpell.words": [ - "alacritty", - "Avdan", - "avdanos", - "avkeys", - "avmacro", - "avvalue", - "Calloop", - "cannonize", - "consts", - "deez", - "Fullscreen", - "Hasher", - "Indentable", - "keybinds", - "Nadva", - "Navda", - "Renderbuffer", - "smithay", - "stdlog", - "templating", - "winit", - "xwayland" - ], - "code-runner.executorMapByGlob": { - "*": "cd $dir && cargo run" - }, - "code-runner.runInTerminal": true -} diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index 91ada92..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,41 +0,0 @@ -# Contributing Guidelines - -## Basics - -To start your contribution, please follow the steps below: - -1. Create a fork of this repo - -2. Git clone your fork to your machine - -```sh -git clone url://to.your/github/fork.git -``` - -3. Use dev branch, commits targetting main will be ignored. - -```sh -git checkout -b dev remotes/upstream/dev -``` - -4. Make changes as you want - -5. Commit your changes - -```sh -git commit -m "concise message of the change" -``` - -`Tip: you can set up code signing and add -S to the commit command above to get a verified check mark` - -6. Push your changes - -```sh -git push -``` - -7. Create pull request on GitHub - -No Direct Commits - -Follow the [Rust Styling Guideline](https://docs.avdanos.com/docs/contributing/development/code_guidelines/rust_guidelines) diff --git a/Cargo.lock b/Cargo.lock index c992388..ae72646 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1,2192 +1,7 @@ # This file is automatically @generated by Cargo. # It is not intended for manual editing. -version = 3 +version = 4 [[package]] -name = "aho-corasick" -version = "1.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e60d3430d3a69478ad0993f19238d2df97c507009a52b3c10addcd7f6bcb916" -dependencies = [ - "memchr", -] - -[[package]] -name = "appendlist" -version = "1.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e149dc73cd30538307e7ffa2acd3d2221148eaeed4871f246657b1c3eaa1cbd2" - -[[package]] -name = "approx" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f2a05fd1bd10b2527e20a2cd32d8873d115b8b39fe219ee25f42a8aca6ba278" -dependencies = [ - "num-traits", -] - -[[package]] -name = "arc-swap" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "69f7f8c3906b62b754cd5326047894316021dcfe5a194c8ea52bdd94934a3457" - -[[package]] -name = "ash" -version = "0.37.3+1.3.251" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "39e9c3835d686b0a6084ab4234fcd1b07dbf6e4767dce60874b12356a25ecd4a" -dependencies = [ - "libloading 0.7.4", -] - -[[package]] -name = "autocfg" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" - -[[package]] -name = "bitflags" -version = "1.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" - -[[package]] -name = "bitflags" -version = "2.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" - -[[package]] -name = "block" -version = "0.1.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0d8c1fef690941d3e7788d328517591fecc684c084084702d6ff1641e993699a" - -[[package]] -name = "bumpalo" -version = "3.16.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "79296716171880943b8470b5f8d03aa55eb2e645a4874bdbb28adb49162e012c" - -[[package]] -name = "bytemuck" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94bbb0ad554ad961ddc5da507a12a29b14e4ae5bda06b19f575a3e6079d2e2ae" -dependencies = [ - "bytemuck_derive", -] - -[[package]] -name = "bytemuck_derive" -version = "1.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0cc8b54b395f2fcfbb3d90c47b01c7f444d94d05bdeb775811dec868ac3bbc26" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "byteorder" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" - -[[package]] -name = "calloop" -version = "0.10.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52e0d00eb1ea24371a97d2da6201c6747a633dc6dc1988ef503403b4c59504a8" -dependencies = [ - "bitflags 1.3.2", - "log", - "nix 0.25.1", - "slotmap", - "thiserror 1.0.69", - "vec_map", -] - -[[package]] -name = "cc" -version = "1.1.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b62ac837cdb5cb22e10a256099b4fc502b1dfe560cb282963a974d7abd80e476" -dependencies = [ - "shlex", -] - -[[package]] -name = "cfg-if" -version = "1.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" - -[[package]] -name = "cgmath" -version = "0.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a98d30140e3296250832bbaaff83b27dcd6fa3cc70fb6f1f3e5c9c0023b5317" -dependencies = [ - "approx", - "num-traits", -] - -[[package]] -name = "cocoa" -version = "0.24.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f425db7937052c684daec3bd6375c8abe2d146dca4b8b143d6db777c39138f3a" -dependencies = [ - "bitflags 1.3.2", - "block", - "cocoa-foundation", - "core-foundation", - "core-graphics", - "foreign-types", - "libc", - "objc", -] - -[[package]] -name = "cocoa-foundation" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8c6234cbb2e4c785b456c0644748b1ac416dd045799740356f8363dfe00c93f7" -dependencies = [ - "bitflags 1.3.2", - "block", - "core-foundation", - "core-graphics-types", - "libc", - "objc", -] - -[[package]] -name = "colored" -version = "2.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cbf2150cce219b664a8a70df7a1f933836724b503f8a413af9365b4dcc4d90b8" -dependencies = [ - "lazy_static", - "windows-sys 0.48.0", -] - -[[package]] -name = "compositor" +name = "avdanos-compositor" version = "0.0.1" -dependencies = [ - "bitflags 2.6.0", - "colored", - "compositor-macros", - "json-tree", - "json_comments", - "lazy_static", - "libc", - "rand", - "regex", - "serde", - "serde_json", - "slog", - "slog-term", - "smithay", - "thiserror 2.0.3", - "xcursor", - "xkbcommon 0.8.0", -] - -[[package]] -name = "compositor-macros" -version = "0.1.0" -dependencies = [ - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "core-foundation" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "91e195e091a93c46f7102ec7818a2aa394e1e1771c3ab4825963fa03e45afb8f" -dependencies = [ - "core-foundation-sys", - "libc", -] - -[[package]] -name = "core-foundation-sys" -version = "0.8.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" - -[[package]] -name = "core-graphics" -version = "0.22.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2581bbab3b8ffc6fcbd550bf46c355135d16e9ff2a6ea032ad6b9bf1d7efe4fb" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "core-graphics-types", - "foreign-types", - "libc", -] - -[[package]] -name = "core-graphics-types" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "45390e6114f68f718cc7a830514a96f903cccd70d02a8f6d9f643ac4ba45afaf" -dependencies = [ - "bitflags 1.3.2", - "core-foundation", - "libc", -] - -[[package]] -name = "cty" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b365fabc795046672053e29c954733ec3b05e4be654ab130fe8f1f94d7051f35" - -[[package]] -name = "darling" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a01d95850c592940db9b8194bc39f4bc0e89dee5c4265e4b1807c34a9aba453c" -dependencies = [ - "darling_core", - "darling_macro", -] - -[[package]] -name = "darling_core" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "859d65a907b6852c9361e3185c862aae7fafd2887876799fa55f5f99dc40d610" -dependencies = [ - "fnv", - "ident_case", - "proc-macro2", - "quote", - "strsim", - "syn 1.0.109", -] - -[[package]] -name = "darling_macro" -version = "0.13.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c972679f83bdf9c42bd905396b6c3588a843a17f0f16dfcfa3e2c5d57441835" -dependencies = [ - "darling_core", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "deranged" -version = "0.3.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b42b6fa04a440b495c8b04d0e71b707c585f83cb9cb28cf8cd0d976c315e31b4" -dependencies = [ - "powerfmt", -] - -[[package]] -name = "dirs-next" -version = "2.0.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1" -dependencies = [ - "cfg-if", - "dirs-sys-next", -] - -[[package]] -name = "dirs-sys-next" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d" -dependencies = [ - "libc", - "redox_users", - "winapi", -] - -[[package]] -name = "dispatch" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd0c93bb4b0c6d9b77f4435b0ae98c24d17f1c45b2ff844c6151a07256ca923b" - -[[package]] -name = "dlib" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "330c60081dcc4c72131f8eb70510f1ac07223e5d4163db481a04a0befcffa412" -dependencies = [ - "libloading 0.8.5", -] - -[[package]] -name = "downcast-rs" -version = "1.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" - -[[package]] -name = "drm" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98d190a8cb219483212f462d9e34abebe24a13cdb7f0c9a64c5b59175ccd62d2" -dependencies = [ - "bitflags 1.3.2", - "bytemuck", - "drm-ffi", - "drm-fourcc", - "nix 0.26.4", -] - -[[package]] -name = "drm-ffi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "48310b65d262ac1140f63e2bbc605c73a417c7e46452ea8c20a3cad698af96d0" -dependencies = [ - "drm-sys", - "nix 0.26.4", -] - -[[package]] -name = "drm-fourcc" -version = "2.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" - -[[package]] -name = "drm-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52ecd1ca96a64fd7444a58a2ce3aa8be79cea3c1c7f7f69a4b89624018b46377" -dependencies = [ - "libc", -] - -[[package]] -name = "encoding" -version = "0.2.33" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6b0d943856b990d12d3b55b359144ff341533e516d94098b1d3fc1ac666d36ec" -dependencies = [ - "encoding-index-japanese", - "encoding-index-korean", - "encoding-index-simpchinese", - "encoding-index-singlebyte", - "encoding-index-tradchinese", -] - -[[package]] -name = "encoding-index-japanese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04e8b2ff42e9a05335dbf8b5c6f7567e5591d0d916ccef4e0b1710d32a0d0c91" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-korean" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dc33fb8e6bcba213fe2f14275f0963fd16f0a02c878e3095ecfdf5bee529d81" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-simpchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d87a7194909b9118fc707194baa434a4e3b0fb6a5a757c73c3adb07aa25031f7" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-singlebyte" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3351d5acffb224af9ca265f435b859c7c01537c0849754d3db3fdf2bfe2ae84a" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding-index-tradchinese" -version = "1.20141219.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fd0e20d5688ce3cab59eb3ef3a2083a5c77bf496cb798dc6fcdb75f323890c18" -dependencies = [ - "encoding_index_tests", -] - -[[package]] -name = "encoding_index_tests" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" - -[[package]] -name = "equivalent" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" - -[[package]] -name = "errno" -version = "0.2.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f639046355ee4f37944e44f60642c6f3a7efa3cf6b78c78a0d989a8ce6c396a1" -dependencies = [ - "errno-dragonfly", - "libc", - "winapi", -] - -[[package]] -name = "errno" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" -dependencies = [ - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "errno-dragonfly" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "aa68f1b12764fab894d2755d2518754e71b4fd80ecfb822714a1206c2aab39bf" -dependencies = [ - "cc", - "libc", -] - -[[package]] -name = "fastrand" -version = "2.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8c02a5121d4ea3eb16a80748c74f5549a5665e4c21333c6098f283870fbdea6" - -[[package]] -name = "fnv" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" - -[[package]] -name = "foreign-types" -version = "0.3.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6f339eb8adc052cd2ca78910fda869aefa38d22d5cb648e6485e4d3fc06f3b1" -dependencies = [ - "foreign-types-shared", -] - -[[package]] -name = "foreign-types-shared" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "00b0228411908ca8685dba7fc2cdd70ec9990a6e753e89b6ac91a84c40fbaf4b" - -[[package]] -name = "gbm" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fffcdb4a9a42ceeb8170a916a84d44ec1fbfbbd753070180a98db92b71bfd431" -dependencies = [ - "bitflags 1.3.2", - "drm", - "drm-fourcc", - "gbm-sys", - "libc", -] - -[[package]] -name = "gbm-sys" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b63eba9b9b7a231514482deb08759301c9f9f049ac6869403f381834ebfeaf67" -dependencies = [ - "libc", -] - -[[package]] -name = "gethostname" -version = "0.2.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c1ebd34e35c46e00bb73e81363248d627782724609fe1b6396f553f68fe3862e" -dependencies = [ - "libc", - "winapi", -] - -[[package]] -name = "getrandom" -version = "0.2.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" -dependencies = [ - "cfg-if", - "libc", - "wasi", -] - -[[package]] -name = "gl_generator" -version = "0.14.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" -dependencies = [ - "khronos_api", - "log", - "xml-rs", -] - -[[package]] -name = "hashbrown" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" - -[[package]] -name = "hashbrown" -version = "0.14.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" - -[[package]] -name = "hermit-abi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" - -[[package]] -name = "hermit-abi" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" - -[[package]] -name = "ident_case" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" - -[[package]] -name = "indexmap" -version = "1.9.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" -dependencies = [ - "autocfg", - "hashbrown 0.12.3", -] - -[[package]] -name = "indexmap" -version = "2.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "68b900aa2f7301e21c36462b170ee99994de34dff39a4a6a528e80e7376d07e5" -dependencies = [ - "equivalent", - "hashbrown 0.14.5", -] - -[[package]] -name = "input" -version = "0.8.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6e74cd82cedcd66db78742a8337bdc48f188c4d2c12742cbc5cd85113f0b059" -dependencies = [ - "bitflags 1.3.2", - "input-sys", - "io-lifetimes", - "libc", - "udev", -] - -[[package]] -name = "input-sys" -version = "1.18.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd4f5b4d1c00331c5245163aacfe5f20be75b564c7112d45893d4ae038119eb0" - -[[package]] -name = "instant" -version = "0.1.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0242819d153cba4b4b05a5a8f2a7e9bbf97b6055b2a002b395c96b5ff3c0222" -dependencies = [ - "cfg-if", - "js-sys", - "wasm-bindgen", - "web-sys", -] - -[[package]] -name = "io-lifetimes" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" -dependencies = [ - "hermit-abi 0.3.9", - "libc", - "windows-sys 0.48.0", -] - -[[package]] -name = "is-terminal" -version = "0.4.13" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "261f68e344040fbd0edea105bef17c66edf46f984ddb1115b775ce31be948f4b" -dependencies = [ - "hermit-abi 0.4.0", - "libc", - "windows-sys 0.52.0", -] - -[[package]] -name = "itoa" -version = "1.0.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" - -[[package]] -name = "jni-sys" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8eaf4bc02d17cbdd7ff4c7438cafcdf7fb9a4613313ad11b4f8fefe7d3fa0130" - -[[package]] -name = "js-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1868808506b929d7b0cfa8f75951347aa71bb21144b7791bae35d9bccfcfe37a" -dependencies = [ - "wasm-bindgen", -] - -[[package]] -name = "json-tree" -version = "0.1.0" -source = "git+https://github.com/Sammy99jsp/json-tree.git#b4d332c75143134e86f33fb59addf8e32c5c8bb4" -dependencies = [ - "colored", - "lazy_static", - "regex", -] - -[[package]] -name = "json_comments" -version = "0.2.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9dbbfed4e59ba9750e15ba154fdfd9329cee16ff3df539c2666b70f58cc32105" - -[[package]] -name = "khronos_api" -version = "3.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" - -[[package]] -name = "lazy_static" -version = "1.5.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bbd2bcb4c963f2ddae06a2efc7e9f3591312473c50c6685e1f298068316e66fe" - -[[package]] -name = "libc" -version = "0.2.164" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "433bfe06b8c75da9b2e3fbea6e5329ff87748f0b144ef75306e674c3f6f7c13f" - -[[package]] -name = "libloading" -version = "0.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b67380fd3b2fbe7527a606e18729d21c6f3951633d0500574c4dc22d2d638b9f" -dependencies = [ - "cfg-if", - "winapi", -] - -[[package]] -name = "libloading" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4979f22fdb869068da03c9f7528f8297c6fd2606bc3a4affe42e6a823fdb8da4" -dependencies = [ - "cfg-if", - "windows-targets 0.52.6", -] - -[[package]] -name = "libredox" -version = "0.1.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c0ff37bd590ca25063e35af745c343cb7a0271906fb7b37e4813e8f79f00268d" -dependencies = [ - "bitflags 2.6.0", - "libc", -] - -[[package]] -name = "libseat" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "845e5c255462c9bc7c71c17b996766b76e3c66f2ddd5846bfbc83f18382aa648" -dependencies = [ - "cc", - "errno 0.2.8", - "libseat-sys", - "pkg-config", - "slog", -] - -[[package]] -name = "libseat-sys" -version = "0.1.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3671cb5e03871f1d6bf0b3b5daa9275549e348fa6359e0f9adb910ca163d4c34" -dependencies = [ - "pkg-config", -] - -[[package]] -name = "libudev-sys" -version = "0.1.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" -dependencies = [ - "libc", - "pkg-config", -] - -[[package]] -name = "linux-raw-sys" -version = "0.4.14" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" - -[[package]] -name = "lock_api" -version = "0.4.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "07af8b9cdd281b7915f413fa73f29ebd5d55d0d3f0155584dade1ff18cea1b17" -dependencies = [ - "autocfg", - "scopeguard", -] - -[[package]] -name = "log" -version = "0.4.22" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" - -[[package]] -name = "malloc_buf" -version = "0.0.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62bb907fe88d54d8d9ce32a3cceab4218ed2f6b7d35617cafe9adf84e43919cb" -dependencies = [ - "libc", -] - -[[package]] -name = "memchr" -version = "2.7.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" - -[[package]] -name = "memmap2" -version = "0.5.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83faa42c0a078c393f6b29d5db232d8be22776a891f8f56e5284faee4a20b327" -dependencies = [ - "libc", -] - -[[package]] -name = "memmap2" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f49388d20533534cd19360ad3d6a7dadc885944aa802ba3995040c5ec11288c6" -dependencies = [ - "libc", -] - -[[package]] -name = "memmap2" -version = "0.9.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fe751422e4a8caa417e13c3ea66452215d7d63e19e604f4980461212f3ae1322" -dependencies = [ - "libc", -] - -[[package]] -name = "memoffset" -version = "0.6.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5aa361d4faea93603064a027415f07bd8e1d5c88c9fbf68bf56a285428fd79ce" -dependencies = [ - "autocfg", -] - -[[package]] -name = "memoffset" -version = "0.7.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5de893c32cde5f383baa4c04c5d6dbdd735cfd4a794b0debdb2bb1b421da5ff4" -dependencies = [ - "autocfg", -] - -[[package]] -name = "mio" -version = "0.8.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a4a650543ca06a924e8b371db273b2756685faae30f8487da1b56505a8f78b0c" -dependencies = [ - "libc", - "log", - "wasi", - "windows-sys 0.48.0", -] - -[[package]] -name = "ndk" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "451422b7e4718271c8b5b3aadf5adedba43dc76312454b387e98fae0fc951aa0" -dependencies = [ - "bitflags 1.3.2", - "jni-sys", - "ndk-sys", - "num_enum", - "raw-window-handle 0.5.2", - "thiserror 1.0.69", -] - -[[package]] -name = "ndk-context" -version = "0.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" - -[[package]] -name = "ndk-glue" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0434fabdd2c15e0aab768ca31d5b7b333717f03cf02037d5a0a3ff3c278ed67f" -dependencies = [ - "libc", - "log", - "ndk", - "ndk-context", - "ndk-macro", - "ndk-sys", - "once_cell", - "parking_lot", -] - -[[package]] -name = "ndk-macro" -version = "0.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0df7ac00c4672f9d5aece54ee3347520b7e20f158656c7db2e6de01902eb7a6c" -dependencies = [ - "darling", - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "ndk-sys" -version = "0.4.1+23.1.7779620" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3cf2aae958bd232cac5069850591667ad422d263686d75b52a065f9badeee5a3" -dependencies = [ - "jni-sys", -] - -[[package]] -name = "nix" -version = "0.24.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa52e972a9a719cecb6864fb88568781eb706bac2cd1d4f04a648542dbf78069" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - -[[package]] -name = "nix" -version = "0.25.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f346ff70e7dbfd675fe90590b92d59ef2de15a8779ae305ebcbfd3f0caf59be4" -dependencies = [ - "autocfg", - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.6.5", -] - -[[package]] -name = "nix" -version = "0.26.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" -dependencies = [ - "bitflags 1.3.2", - "cfg-if", - "libc", - "memoffset 0.7.1", - "pin-utils", -] - -[[package]] -name = "num-conv" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" - -[[package]] -name = "num-traits" -version = "0.2.19" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "071dfc062690e90b734c0b2273ce72ad0ffa95f0c74596bc250dcfd960262841" -dependencies = [ - "autocfg", -] - -[[package]] -name = "num_enum" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f646caf906c20226733ed5b1374287eb97e3c2a5c227ce668c1f2ce20ae57c9" -dependencies = [ - "num_enum_derive", -] - -[[package]] -name = "num_enum_derive" -version = "0.5.11" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcbff9bc912032c62bf65ef1d5aea88983b420f4f839db1e9b0c281a25c9c799" -dependencies = [ - "proc-macro-crate", - "proc-macro2", - "quote", - "syn 1.0.109", -] - -[[package]] -name = "objc" -version = "0.2.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "915b1b472bc21c53464d6c8461c9d3af805ba1ef837e1cac254428f4a77177b1" -dependencies = [ - "malloc_buf", -] - -[[package]] -name = "once_cell" -version = "1.19.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3fdb12b2476b595f9358c5161aa467c2438859caa136dec86c26fdd2efe17b92" - -[[package]] -name = "parking_lot" -version = "0.12.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bf18183cf54e8d6059647fc3063646a1801cf30896933ec2311622cc4b9a27" -dependencies = [ - "lock_api", - "parking_lot_core", -] - -[[package]] -name = "parking_lot_core" -version = "0.9.10" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e401f977ab385c9e4e3ab30627d6f26d00e2c73eef317493c4ec6d468726cf8" -dependencies = [ - "cfg-if", - "libc", - "redox_syscall", - "smallvec", - "windows-targets 0.52.6", -] - -[[package]] -name = "percent-encoding" -version = "2.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e3148f5046208a5d56bcfc03053e3ca6334e51da8dfb19b6cdc8b306fae3283e" - -[[package]] -name = "pin-utils" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b870d8c151b6f2fb93e84a13146138f05d02ed11c7e7c54f8826aaaf7c9f184" - -[[package]] -name = "pkg-config" -version = "0.3.30" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d231b230927b5e4ad203db57bbcbee2802f6bce620b1e4a9024a07d94e2907ec" - -[[package]] -name = "powerfmt" -version = "0.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "439ee305def115ba05938db6eb1644ff94165c5ab5e9420d1c1bcedbba909391" - -[[package]] -name = "ppv-lite86" -version = "0.2.20" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "77957b295656769bb8ad2b6a6b09d897d94f05c41b069aede1fcdaa675eaea04" -dependencies = [ - "zerocopy", -] - -[[package]] -name = "proc-macro-crate" -version = "1.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f4c021e1093a56626774e81216a4ce732a735e5bad4868a03f3ed65ca0c3919" -dependencies = [ - "once_cell", - "toml_edit", -] - -[[package]] -name = "proc-macro2" -version = "1.0.86" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" -dependencies = [ - "unicode-ident", -] - -[[package]] -name = "quick-xml" -version = "0.28.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ce5e73202a820a31f8a0ee32ada5e21029c81fd9e3ebf668a40832e4219d9d1" -dependencies = [ - "memchr", -] - -[[package]] -name = "quote" -version = "1.0.37" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b5b9d34b8991d19d98081b46eacdd8eb58c6f2b201139f7c5f643cc155a633af" -dependencies = [ - "proc-macro2", -] - -[[package]] -name = "rand" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" -dependencies = [ - "libc", - "rand_chacha", - "rand_core", -] - -[[package]] -name = "rand_chacha" -version = "0.3.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" -dependencies = [ - "ppv-lite86", - "rand_core", -] - -[[package]] -name = "rand_core" -version = "0.6.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" -dependencies = [ - "getrandom", -] - -[[package]] -name = "raw-window-handle" -version = "0.4.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b800beb9b6e7d2df1fe337c9e3d04e3af22a124460fb4c30fcc22c9117cefb41" -dependencies = [ - "cty", -] - -[[package]] -name = "raw-window-handle" -version = "0.5.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f2ff9a1f06a88b01621b7ae906ef0211290d1c8a168a15542486a8f61c0833b9" - -[[package]] -name = "redox_syscall" -version = "0.5.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2a908a6e00f1fdd0dfd9c0eb08ce85126f6d8bbda50017e74bc4a4b7d4a926a4" -dependencies = [ - "bitflags 2.6.0", -] - -[[package]] -name = "redox_users" -version = "0.4.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba009ff324d1fc1b900bd1fdb31564febe58a8ccc8a6fdbb93b543d33b13ca43" -dependencies = [ - "getrandom", - "libredox", - "thiserror 1.0.69", -] - -[[package]] -name = "regex" -version = "1.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b544ef1b4eac5dc2db33ea63606ae9ffcfac26c1416a2806ae0bf5f56b201191" -dependencies = [ - "aho-corasick", - "memchr", - "regex-automata", - "regex-syntax", -] - -[[package]] -name = "regex-automata" -version = "0.4.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "368758f23274712b504848e9d5a6f010445cc8b87a7cdb4d7cbee666c1288da3" -dependencies = [ - "aho-corasick", - "memchr", - "regex-syntax", -] - -[[package]] -name = "regex-syntax" -version = "0.8.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" - -[[package]] -name = "rustix" -version = "0.38.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f55e80d50763938498dd5ebb18647174e0c76dc38c5505294bb224624f30f36" -dependencies = [ - "bitflags 2.6.0", - "errno 0.3.9", - "libc", - "linux-raw-sys", - "windows-sys 0.52.0", -] - -[[package]] -name = "rustversion" -version = "1.0.17" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "955d28af4278de8121b7ebeb796b6a45735dc01436d898801014aced2773a3d6" - -[[package]] -name = "ryu" -version = "1.0.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3cb5ba0dc43242ce17de99c180e96db90b235b8a9fdc9543c96d2209116bd9f" - -[[package]] -name = "scan_fmt" -version = "0.2.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b53b0a5db882a8e2fdaae0a43f7b39e7e9082389e978398bdf223a55b581248" - -[[package]] -name = "scoped-tls" -version = "1.0.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" - -[[package]] -name = "scopeguard" -version = "1.2.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" - -[[package]] -name = "serde" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6513c1ad0b11a9376da888e3e0baa0077f1aed55c17f50e7b2397136129fb88f" -dependencies = [ - "serde_derive", -] - -[[package]] -name = "serde_derive" -version = "1.0.215" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ad1e866f866923f252f05c889987993144fb74e722403468a4ebd70c3cd756c0" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "serde_json" -version = "1.0.133" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c7fceb2473b9166b2294ef05efcb65a3db80803f0b03ef86a5fc88a2b85ee377" -dependencies = [ - "itoa", - "memchr", - "ryu", - "serde", -] - -[[package]] -name = "shlex" -version = "1.3.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" - -[[package]] -name = "slog" -version = "2.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8347046d4ebd943127157b94d63abb990fcf729dc4e9978927fdf4ac3c998d06" - -[[package]] -name = "slog-scope" -version = "4.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2f95a4b4c3274cd2869549da82b57ccc930859bdbf5bcea0424bc5f140b3c786" -dependencies = [ - "arc-swap", - "lazy_static", - "slog", -] - -[[package]] -name = "slog-stdlog" -version = "4.1.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6706b2ace5bbae7291d3f8d2473e2bfab073ccd7d03670946197aec98471fa3e" -dependencies = [ - "log", - "slog", - "slog-scope", -] - -[[package]] -name = "slog-term" -version = "2.9.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6e022d0b998abfe5c3782c1f03551a596269450ccd677ea51c56f8b214610e8" -dependencies = [ - "is-terminal", - "slog", - "term", - "thread_local", - "time", -] - -[[package]] -name = "slotmap" -version = "1.0.7" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dbff4acf519f630b3a3ddcfaea6c06b42174d9a44bc70c620e9ed1649d58b82a" -dependencies = [ - "version_check", -] - -[[package]] -name = "smallvec" -version = "1.13.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3c5e1a9a646d36c3599cd173a41282daf47c44583ad367b8e6837255952e5c67" - -[[package]] -name = "smithay" -version = "0.3.0" -source = "git+https://github.com/smithay/smithay.git?rev=6253576c#6253576c219f05838a75af8edbb927a4e7e9d8ad" -dependencies = [ - "appendlist", - "ash", - "bitflags 1.3.2", - "calloop", - "cc", - "cgmath", - "downcast-rs", - "drm", - "drm-ffi", - "drm-fourcc", - "encoding", - "gbm", - "gl_generator", - "indexmap 1.9.3", - "input", - "lazy_static", - "libc", - "libloading 0.7.4", - "libseat", - "nix 0.26.4", - "once_cell", - "pkg-config", - "rand", - "scan_fmt", - "scopeguard", - "slog", - "slog-stdlog", - "tempfile", - "thiserror 1.0.69", - "udev", - "wayland-backend", - "wayland-egl", - "wayland-protocols 0.30.1", - "wayland-protocols-misc", - "wayland-protocols-wlr", - "wayland-server", - "wayland-sys 0.30.1", - "winit", - "x11rb", - "xkbcommon 0.5.1", -] - -[[package]] -name = "smithay-client-toolkit" -version = "0.16.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "870427e30b8f2cbe64bf43ec4b86e88fe39b0a84b3f15efd9c9c2d020bc86eb9" -dependencies = [ - "bitflags 1.3.2", - "calloop", - "dlib", - "lazy_static", - "log", - "memmap2 0.5.10", - "nix 0.24.3", - "pkg-config", - "wayland-client", - "wayland-cursor", - "wayland-protocols 0.29.5", -] - -[[package]] -name = "strsim" -version = "0.10.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" - -[[package]] -name = "syn" -version = "1.0.109" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "72b64191b275b66ffe2469e8af2c1cfe3bafa67b529ead792a6d0160888b4237" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "syn" -version = "2.0.87" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "25aa4ce346d03a6dcd68dd8b4010bcb74e54e62c90c573f394c46eae99aba32d" -dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", -] - -[[package]] -name = "tempfile" -version = "3.12.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "04cbcdd0c794ebb0d4cf35e88edd2f7d2c4c3e9a5a6dab322839b321c6a87a64" -dependencies = [ - "cfg-if", - "fastrand", - "once_cell", - "rustix", - "windows-sys 0.59.0", -] - -[[package]] -name = "term" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f" -dependencies = [ - "dirs-next", - "rustversion", - "winapi", -] - -[[package]] -name = "thiserror" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" -dependencies = [ - "thiserror-impl 1.0.69", -] - -[[package]] -name = "thiserror" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c006c85c7651b3cf2ada4584faa36773bd07bac24acfb39f3c431b36d7e667aa" -dependencies = [ - "thiserror-impl 2.0.3", -] - -[[package]] -name = "thiserror-impl" -version = "1.0.69" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "thiserror-impl" -version = "2.0.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f077553d607adc1caf65430528a576c757a71ed73944b66ebb58ef2bbd243568" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] - -[[package]] -name = "thread_local" -version = "1.1.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8b9ef9bad013ada3808854ceac7b46812a6465ba368859a37e2100283d2d719c" -dependencies = [ - "cfg-if", - "once_cell", -] - -[[package]] -name = "time" -version = "0.3.36" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5dfd88e563464686c916c7e46e623e520ddc6d79fa6641390f2e3fa86e83e885" -dependencies = [ - "deranged", - "itoa", - "num-conv", - "powerfmt", - "serde", - "time-core", - "time-macros", -] - -[[package]] -name = "time-core" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ef927ca75afb808a4d64dd374f00a2adf8d0fcff8e7b184af886c3c87ec4a3f3" - -[[package]] -name = "time-macros" -version = "0.2.18" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f252a68540fde3a3877aeea552b832b40ab9a69e318efd078774a01ddee1ccf" -dependencies = [ - "num-conv", - "time-core", -] - -[[package]] -name = "toml_datetime" -version = "0.6.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0dd7358ecb8fc2f8d014bf86f6f638ce72ba252a2c3a2572f2a795f1d23efb41" - -[[package]] -name = "toml_edit" -version = "0.19.15" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421" -dependencies = [ - "indexmap 2.5.0", - "toml_datetime", - "winnow", -] - -[[package]] -name = "udev" -version = "0.7.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4ebdbbd670373442a12fe9ef7aeb53aec4147a5a27a00bbc3ab639f08f48191a" -dependencies = [ - "libc", - "libudev-sys", - "pkg-config", -] - -[[package]] -name = "unicode-ident" -version = "1.0.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3354b9ac3fae1ff6755cb6db53683adb661634f67557942dea4facebec0fee4b" - -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - -[[package]] -name = "version_check" -version = "0.9.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" - -[[package]] -name = "wasi" -version = "0.11.0+wasi-snapshot-preview1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" - -[[package]] -name = "wasm-bindgen" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a82edfc16a6c469f5f44dc7b571814045d60404b55a0ee849f9bcfa2e63dd9b5" -dependencies = [ - "cfg-if", - "once_cell", - "wasm-bindgen-macro", -] - -[[package]] -name = "wasm-bindgen-backend" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9de396da306523044d3302746f1208fa71d7532227f15e347e2d93e4145dd77b" -dependencies = [ - "bumpalo", - "log", - "once_cell", - "proc-macro2", - "quote", - "syn 2.0.87", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-macro" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "585c4c91a46b072c92e908d99cb1dcdf95c5218eeb6f3bf1efa991ee7a68cccf" -dependencies = [ - "quote", - "wasm-bindgen-macro-support", -] - -[[package]] -name = "wasm-bindgen-macro-support" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "afc340c74d9005395cf9dd098506f7f44e38f2b4a21c6aaacf9a105ea5e1e836" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", - "wasm-bindgen-backend", - "wasm-bindgen-shared", -] - -[[package]] -name = "wasm-bindgen-shared" -version = "0.2.93" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c62a0a307cb4a311d3a07867860911ca130c3494e8c2719593806c08bc5d0484" - -[[package]] -name = "wayland-backend" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "41b48e27457e8da3b2260ac60d0a94512f5cba36448679f3747c0865b7893ed8" -dependencies = [ - "cc", - "downcast-rs", - "io-lifetimes", - "nix 0.26.4", - "scoped-tls", - "smallvec", - "wayland-sys 0.30.1", -] - -[[package]] -name = "wayland-client" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f3b068c05a039c9f755f881dc50f01732214f5685e379829759088967c46715" -dependencies = [ - "bitflags 1.3.2", - "downcast-rs", - "libc", - "nix 0.24.3", - "scoped-tls", - "wayland-commons", - "wayland-scanner 0.29.5", - "wayland-sys 0.29.5", -] - -[[package]] -name = "wayland-commons" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8691f134d584a33a6606d9d717b95c4fa20065605f798a3f350d78dced02a902" -dependencies = [ - "nix 0.24.3", - "once_cell", - "smallvec", - "wayland-sys 0.29.5", -] - -[[package]] -name = "wayland-cursor" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6865c6b66f13d6257bef1cd40cbfe8ef2f150fb8ebbdb1e8e873455931377661" -dependencies = [ - "nix 0.24.3", - "wayland-client", - "xcursor", -] - -[[package]] -name = "wayland-egl" -version = "0.30.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1187695fe81c3153c3163f9d2953149f638c5d7dbc6fe988914ca3f4961e28ed" -dependencies = [ - "wayland-backend", - "wayland-sys 0.30.1", -] - -[[package]] -name = "wayland-protocols" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b950621f9354b322ee817a23474e479b34be96c2e909c14f7bc0100e9a970bc6" -dependencies = [ - "bitflags 1.3.2", - "wayland-client", - "wayland-commons", - "wayland-scanner 0.29.5", -] - -[[package]] -name = "wayland-protocols" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3b28101e5ca94f70461a6c2d610f76d85ad223d042dd76585ab23d3422dd9b4d" -dependencies = [ - "bitflags 1.3.2", - "wayland-backend", - "wayland-scanner 0.30.1", - "wayland-server", -] - -[[package]] -name = "wayland-protocols-misc" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "897d4e99645e1ed9245e9e6b5efa78828d2b23b661016d63d55251243d812f8b" -dependencies = [ - "bitflags 1.3.2", - "wayland-backend", - "wayland-protocols 0.30.1", - "wayland-scanner 0.30.1", - "wayland-server", -] - -[[package]] -name = "wayland-protocols-wlr" -version = "0.1.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fce991093320e4a6a525876e6b629ab24da25f9baef0c2e0080ad173ec89588a" -dependencies = [ - "bitflags 1.3.2", - "wayland-backend", - "wayland-protocols 0.30.1", - "wayland-scanner 0.30.1", - "wayland-server", -] - -[[package]] -name = "wayland-scanner" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f4303d8fa22ab852f789e75a967f0a2cdc430a607751c0499bada3e451cbd53" -dependencies = [ - "proc-macro2", - "quote", - "xml-rs", -] - -[[package]] -name = "wayland-scanner" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9b873b257fbc32ec909c0eb80dea312076a67014e65e245f5eb69a6b8ab330e" -dependencies = [ - "proc-macro2", - "quick-xml", - "quote", -] - -[[package]] -name = "wayland-server" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9c43c28096fe1d49fff7d1079404fdd0f669cd1a5b00c615bdfe71bb1884d23a" -dependencies = [ - "bitflags 1.3.2", - "downcast-rs", - "io-lifetimes", - "nix 0.26.4", - "wayland-backend", - "wayland-scanner 0.30.1", -] - -[[package]] -name = "wayland-sys" -version = "0.29.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be12ce1a3c39ec7dba25594b97b42cb3195d54953ddb9d3d95a7c3902bc6e9d4" -dependencies = [ - "dlib", - "lazy_static", - "pkg-config", -] - -[[package]] -name = "wayland-sys" -version = "0.30.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "96b2a02ac608e07132978689a6f9bf4214949c85998c247abadd4f4129b1aa06" -dependencies = [ - "dlib", - "libc", - "log", - "memoffset 0.7.1", - "pkg-config", -] - -[[package]] -name = "web-sys" -version = "0.3.70" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "26fdeaafd9bd129f65e7c031593c24d62186301e0c72c8978fa1678be7d532c0" -dependencies = [ - "js-sys", - "wasm-bindgen", -] - -[[package]] -name = "winapi" -version = "0.3.9" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" -dependencies = [ - "winapi-i686-pc-windows-gnu", - "winapi-x86_64-pc-windows-gnu", -] - -[[package]] -name = "winapi-i686-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" - -[[package]] -name = "winapi-wsapoll" -version = "0.1.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1eafc5f679c576995526e81635d0cf9695841736712b4e892f87abbe6fed3f28" -dependencies = [ - "winapi", -] - -[[package]] -name = "winapi-x86_64-pc-windows-gnu" -version = "0.4.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" - -[[package]] -name = "windows-sys" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ea04155a16a59f9eab786fe12a4a450e75cdb175f9e0d80da1e17db09f55b8d2" -dependencies = [ - "windows_aarch64_msvc 0.36.1", - "windows_i686_gnu 0.36.1", - "windows_i686_msvc 0.36.1", - "windows_x86_64_gnu 0.36.1", - "windows_x86_64_msvc 0.36.1", -] - -[[package]] -name = "windows-sys" -version = "0.48.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" -dependencies = [ - "windows-targets 0.48.5", -] - -[[package]] -name = "windows-sys" -version = "0.52.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-sys" -version = "0.59.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" -dependencies = [ - "windows-targets 0.52.6", -] - -[[package]] -name = "windows-targets" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" -dependencies = [ - "windows_aarch64_gnullvm 0.48.5", - "windows_aarch64_msvc 0.48.5", - "windows_i686_gnu 0.48.5", - "windows_i686_msvc 0.48.5", - "windows_x86_64_gnu 0.48.5", - "windows_x86_64_gnullvm 0.48.5", - "windows_x86_64_msvc 0.48.5", -] - -[[package]] -name = "windows-targets" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" -dependencies = [ - "windows_aarch64_gnullvm 0.52.6", - "windows_aarch64_msvc 0.52.6", - "windows_i686_gnu 0.52.6", - "windows_i686_gnullvm", - "windows_i686_msvc 0.52.6", - "windows_x86_64_gnu 0.52.6", - "windows_x86_64_gnullvm 0.52.6", - "windows_x86_64_msvc 0.52.6", -] - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" - -[[package]] -name = "windows_aarch64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9bb8c3fd39ade2d67e9874ac4f3db21f0d710bee00fe7cab16949ec184eeaa47" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" - -[[package]] -name = "windows_aarch64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" - -[[package]] -name = "windows_i686_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "180e6ccf01daf4c426b846dfc66db1fc518f074baa793aa7d9b9aaeffad6a3b6" - -[[package]] -name = "windows_i686_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" - -[[package]] -name = "windows_i686_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" - -[[package]] -name = "windows_i686_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" - -[[package]] -name = "windows_i686_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e2e7917148b2812d1eeafaeb22a97e4813dfa60a3f8f78ebe204bcc88f12f024" - -[[package]] -name = "windows_i686_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" - -[[package]] -name = "windows_i686_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4dcd171b8776c41b97521e5da127a2d86ad280114807d0b2ab1e462bc764d9e1" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" - -[[package]] -name = "windows_x86_64_gnu" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" - -[[package]] -name = "windows_x86_64_gnullvm" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.36.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c811ca4a8c853ef420abd8592ba53ddbbac90410fab6903b3e79972a631f7680" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.48.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" - -[[package]] -name = "windows_x86_64_msvc" -version = "0.52.6" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" - -[[package]] -name = "winit" -version = "0.27.5" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bb796d6fbd86b2fd896c9471e6f04d39d750076ebe5680a3958f00f5ab97657c" -dependencies = [ - "bitflags 1.3.2", - "cocoa", - "core-foundation", - "core-graphics", - "dispatch", - "instant", - "libc", - "log", - "mio", - "ndk", - "ndk-glue", - "objc", - "once_cell", - "parking_lot", - "percent-encoding", - "raw-window-handle 0.4.3", - "raw-window-handle 0.5.2", - "smithay-client-toolkit", - "wasm-bindgen", - "wayland-client", - "wayland-protocols 0.29.5", - "web-sys", - "windows-sys 0.36.1", - "x11-dl", -] - -[[package]] -name = "winnow" -version = "0.5.40" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" -dependencies = [ - "memchr", -] - -[[package]] -name = "x11-dl" -version = "2.21.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" -dependencies = [ - "libc", - "once_cell", - "pkg-config", -] - -[[package]] -name = "x11rb" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cdf3c79412dd91bae7a7366b8ad1565a85e35dd049affc3a6a2c549e97419617" -dependencies = [ - "gethostname", - "nix 0.25.1", - "winapi", - "winapi-wsapoll", - "x11rb-protocol", -] - -[[package]] -name = "x11rb-protocol" -version = "0.11.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e0b1513b141123073ce54d5bb1d33f801f17508fbd61e02060b1214e96d39c56" -dependencies = [ - "nix 0.25.1", -] - -[[package]] -name = "xcursor" -version = "0.3.8" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0ef33da6b1660b4ddbfb3aef0ade110c8b8a781a3b6382fa5f2b5b040fd55f61" - -[[package]] -name = "xkbcommon" -version = "0.5.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "52db25b599e92bf6e3904134618728eeb7b49a5a4f38f107f92399bb9c496b88" -dependencies = [ - "libc", - "memmap2 0.7.1", -] - -[[package]] -name = "xkbcommon" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8d66ca9352cbd4eecbbc40871d8a11b4ac8107cfc528a6e14d7c19c69d0e1ac9" -dependencies = [ - "libc", - "memmap2 0.9.4", - "xkeysym", -] - -[[package]] -name = "xkeysym" -version = "0.2.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" - -[[package]] -name = "xml-rs" -version = "0.8.21" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "539a77ee7c0de333dcc6da69b177380a0b81e0dacfa4f7344c465a36871ee601" - -[[package]] -name = "zerocopy" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1b9b4fd18abc82b8136838da5d50bae7bdea537c574d8dc1a34ed098d6c166f0" -dependencies = [ - "byteorder", - "zerocopy-derive", -] - -[[package]] -name = "zerocopy-derive" -version = "0.7.35" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" -dependencies = [ - "proc-macro2", - "quote", - "syn 2.0.87", -] diff --git a/Cargo.toml b/Cargo.toml index f96f8e4..1d8cc5a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,54 +1,15 @@ [package] -name = "compositor" +name = "avdanos-compositor" description = "Wayland compositor made for AvdanOS" -documentation = "https://docs.avdanos.com/" -homepage = "https://avdanos.com/" -license = "GPL-3.0" +documentation = "https://docs.avdanos.dev/" +homepage = "https://avdanos.dev/" +license = "MIT" version = "0.0.1" authors = [ - "Akane ", - "Froxcey ", - "Sammy ", + "Sammy ", ] -edition = "2021" +edition = "2024" [dependencies] -serde_json = "1.0.133" -lazy_static = "1.5.0" -json_comments = "0.2.2" -regex = "1.11.1" -slog-term = "2.9.1" -bitflags = "2.6.0" -colored = "2.1.0" -xcursor = "0.3.8" -thiserror = "2.0.3" -rand = "0.8.5" -xkbcommon = "0.8.0" -libc = "0.2.164" - -[dependencies.compositor-macros] - -path = "./src/macros" - -[dependencies.json-tree] - -git = "https://github.com/Sammy99jsp/json-tree.git" -features = ["jsonc"] - -[dependencies.smithay] - -git = "https://github.com/smithay/smithay.git" -rev = "6253576c" -features = ["desktop", "default", "backend_egl", "use_system_lib"] - -[dependencies.serde] - -version = "1.0.215" -features = ["derive"] - -[dependencies.slog] - -version = "2.7.0" -features = ["max_level_trace", "release_max_level_warn"] diff --git a/DefaultConfig.jsonc b/DefaultConfig.jsonc deleted file mode 100644 index bdb3bae..0000000 --- a/DefaultConfig.jsonc +++ /dev/null @@ -1,11 +0,0 @@ -{ - "$schema": "https://schema.avdanos.org/Compositor.json", - // Hello World - // Template Test - "keybinds": { - "multitasking": { - "workspace(d)": "Ctrl+Logo+{n}", - "moveFocusedWindowToWorkspace(n)": "Shift+Logo+{n}" - } - } -} diff --git a/Devcontainer.md b/Devcontainer.md deleted file mode 100644 index ee147bc..0000000 --- a/Devcontainer.md +++ /dev/null @@ -1,72 +0,0 @@ -# Using devcontainer - -## Why? - -Normally, the compositor can only run on Linux. By leveraging the power of Docker, it could also be debugged on other operating systems. If you're on Windows or MacOS, use this guide to test or debug the compositor. - -## Prerequisites - -To use devcontainer, you need the following: - -- Docker -- VSCode - -In addition, you will need a xserver to display the compositor. - ->  If you are using NVim, you might need to install and launch NVim in the container manually. Efforts are being made to make this process more automatic. - -**MacOS** - -You need XQuartz: - -```sh -brew install --cask xquartz -``` - -and you need to allow docker to access XQuartz by - -1. Open XQuartz -2. Open settings \(through menubar or `cmd`+`,`) -3. Go to security -4. Enable `Allow connections from network clients` - -**Windows** - -Hi, Frox here. I don't use Windows, so I would really appreciate if someone can help me with this part of the guide. - -**Linux** - -You probably already have xhost, so run the following command to allow docker to access xhost: - -```sh -xhost +local: -``` - -If xhost is not found, you need to install it: - -**Ubuntu and Debian**: - -```sh -sudo apt-get install x11-xserver-utils -``` - -**CentOS and Fedora**: - -```sh -sudo dnf install xorg-x11-xauth -``` - -**Arch Linux**: - -```sh -sudo pacman -S xorg-xhost -``` - -## Running the devcontainer - -1. Open the compositor project in VSCode. -2. Open command and search for `Dev Containers: Reopen in Container` -3. Installation and configuration will start automatically -4. Enjoy! - -Now if you want to run the compositor, running `cargo run` should in theory just run. That, however, may have some compatibility issues. Happy coding! diff --git a/ISSUE_TEMPLATE/bug_report.md b/ISSUE_TEMPLATE/bug_report.md deleted file mode 100644 index 42e7846..0000000 --- a/ISSUE_TEMPLATE/bug_report.md +++ /dev/null @@ -1,30 +0,0 @@ ---- -Name: Bug report -About: Report bugs about our Wayland Compositor here. -Title: "[Bug Report]: " -Labels: bug -Assignees: akane6704, Sammy99jsp, froxcey ---- - -### Please make sure that you: - -- Search for any other issue relating to your bug (open and closed) -- Follow the proper format - -### Please describe your bug: -_Describe bug here_ - -### Steps to reproduce: -1. Do this -2. Do that -3. Open this -4. Open that -5. See error - -### What result were you expecting? -_I was expecting it to..._ - -### What did you get instead? -_It..._ - -### Screenshots (if applicable): diff --git a/LICENSE b/LICENSE deleted file mode 100644 index f288702..0000000 --- a/LICENSE +++ /dev/null @@ -1,674 +0,0 @@ - GNU GENERAL PUBLIC LICENSE - Version 3, 29 June 2007 - - Copyright (C) 2007 Free Software Foundation, Inc. - Everyone is permitted to copy and distribute verbatim copies - of this license document, but changing it is not allowed. - - Preamble - - The GNU General Public License is a free, copyleft license for -software and other kinds of works. - - The licenses for most software and other practical works are designed -to take away your freedom to share and change the works. By contrast, -the GNU General Public License is intended to guarantee your freedom to -share and change all versions of a program--to make sure it remains free -software for all its users. We, the Free Software Foundation, use the -GNU General Public License for most of our software; it applies also to -any other work released this way by its authors. You can apply it to -your programs, too. - - When we speak of free software, we are referring to freedom, not -price. Our General Public Licenses are designed to make sure that you -have the freedom to distribute copies of free software (and charge for -them if you wish), that you receive source code or can get it if you -want it, that you can change the software or use pieces of it in new -free programs, and that you know you can do these things. - - To protect your rights, we need to prevent others from denying you -these rights or asking you to surrender the rights. Therefore, you have -certain responsibilities if you distribute copies of the software, or if -you modify it: responsibilities to respect the freedom of others. - - For example, if you distribute copies of such a program, whether -gratis or for a fee, you must pass on to the recipients the same -freedoms that you received. You must make sure that they, too, receive -or can get the source code. And you must show them these terms so they -know their rights. - - Developers that use the GNU GPL protect your rights with two steps: -(1) assert copyright on the software, and (2) offer you this License -giving you legal permission to copy, distribute and/or modify it. - - For the developers' and authors' protection, the GPL clearly explains -that there is no warranty for this free software. For both users' and -authors' sake, the GPL requires that modified versions be marked as -changed, so that their problems will not be attributed erroneously to -authors of previous versions. - - Some devices are designed to deny users access to install or run -modified versions of the software inside them, although the manufacturer -can do so. This is fundamentally incompatible with the aim of -protecting users' freedom to change the software. The systematic -pattern of such abuse occurs in the area of products for individuals to -use, which is precisely where it is most unacceptable. Therefore, we -have designed this version of the GPL to prohibit the practice for those -products. If such problems arise substantially in other domains, we -stand ready to extend this provision to those domains in future versions -of the GPL, as needed to protect the freedom of users. - - Finally, every program is threatened constantly by software patents. -States should not allow patents to restrict development and use of -software on general-purpose computers, but in those that do, we wish to -avoid the special danger that patents applied to a free program could -make it effectively proprietary. To prevent this, the GPL assures that -patents cannot be used to render the program non-free. - - The precise terms and conditions for copying, distribution and -modification follow. - - TERMS AND CONDITIONS - - 0. Definitions. - - "This License" refers to version 3 of the GNU General Public License. - - "Copyright" also means copyright-like laws that apply to other kinds of -works, such as semiconductor masks. - - "The Program" refers to any copyrightable work licensed under this -License. Each licensee is addressed as "you". "Licensees" and -"recipients" may be individuals or organizations. - - To "modify" a work means to copy from or adapt all or part of the work -in a fashion requiring copyright permission, other than the making of an -exact copy. The resulting work is called a "modified version" of the -earlier work or a work "based on" the earlier work. - - A "covered work" means either the unmodified Program or a work based -on the Program. - - To "propagate" a work means to do anything with it that, without -permission, would make you directly or secondarily liable for -infringement under applicable copyright law, except executing it on a -computer or modifying a private copy. Propagation includes copying, -distribution (with or without modification), making available to the -public, and in some countries other activities as well. - - To "convey" a work means any kind of propagation that enables other -parties to make or receive copies. Mere interaction with a user through -a computer network, with no transfer of a copy, is not conveying. - - An interactive user interface displays "Appropriate Legal Notices" -to the extent that it includes a convenient and prominently visible -feature that (1) displays an appropriate copyright notice, and (2) -tells the user that there is no warranty for the work (except to the -extent that warranties are provided), that licensees may convey the -work under this License, and how to view a copy of this License. If -the interface presents a list of user commands or options, such as a -menu, a prominent item in the list meets this criterion. - - 1. Source Code. - - The "source code" for a work means the preferred form of the work -for making modifications to it. "Object code" means any non-source -form of a work. - - A "Standard Interface" means an interface that either is an official -standard defined by a recognized standards body, or, in the case of -interfaces specified for a particular programming language, one that -is widely used among developers working in that language. - - The "System Libraries" of an executable work include anything, other -than the work as a whole, that (a) is included in the normal form of -packaging a Major Component, but which is not part of that Major -Component, and (b) serves only to enable use of the work with that -Major Component, or to implement a Standard Interface for which an -implementation is available to the public in source code form. A -"Major Component", in this context, means a major essential component -(kernel, window system, and so on) of the specific operating system -(if any) on which the executable work runs, or a compiler used to -produce the work, or an object code interpreter used to run it. - - The "Corresponding Source" for a work in object code form means all -the source code needed to generate, install, and (for an executable -work) run the object code and to modify the work, including scripts to -control those activities. However, it does not include the work's -System Libraries, or general-purpose tools or generally available free -programs which are used unmodified in performing those activities but -which are not part of the work. For example, Corresponding Source -includes interface definition files associated with source files for -the work, and the source code for shared libraries and dynamically -linked subprograms that the work is specifically designed to require, -such as by intimate data communication or control flow between those -subprograms and other parts of the work. - - The Corresponding Source need not include anything that users -can regenerate automatically from other parts of the Corresponding -Source. - - The Corresponding Source for a work in source code form is that -same work. - - 2. Basic Permissions. - - All rights granted under this License are granted for the term of -copyright on the Program, and are irrevocable provided the stated -conditions are met. This License explicitly affirms your unlimited -permission to run the unmodified Program. The output from running a -covered work is covered by this License only if the output, given its -content, constitutes a covered work. This License acknowledges your -rights of fair use or other equivalent, as provided by copyright law. - - You may make, run and propagate covered works that you do not -convey, without conditions so long as your license otherwise remains -in force. You may convey covered works to others for the sole purpose -of having them make modifications exclusively for you, or provide you -with facilities for running those works, provided that you comply with -the terms of this License in conveying all material for which you do -not control copyright. Those thus making or running the covered works -for you must do so exclusively on your behalf, under your direction -and control, on terms that prohibit them from making any copies of -your copyrighted material outside their relationship with you. - - Conveying under any other circumstances is permitted solely under -the conditions stated below. Sublicensing is not allowed; section 10 -makes it unnecessary. - - 3. Protecting Users' Legal Rights From Anti-Circumvention Law. - - No covered work shall be deemed part of an effective technological -measure under any applicable law fulfilling obligations under article -11 of the WIPO copyright treaty adopted on 20 December 1996, or -similar laws prohibiting or restricting circumvention of such -measures. - - When you convey a covered work, you waive any legal power to forbid -circumvention of technological measures to the extent such circumvention -is effected by exercising rights under this License with respect to -the covered work, and you disclaim any intention to limit operation or -modification of the work as a means of enforcing, against the work's -users, your or third parties' legal rights to forbid circumvention of -technological measures. - - 4. Conveying Verbatim Copies. - - You may convey verbatim copies of the Program's source code as you -receive it, in any medium, provided that you conspicuously and -appropriately publish on each copy an appropriate copyright notice; -keep intact all notices stating that this License and any -non-permissive terms added in accord with section 7 apply to the code; -keep intact all notices of the absence of any warranty; and give all -recipients a copy of this License along with the Program. - - You may charge any price or no price for each copy that you convey, -and you may offer support or warranty protection for a fee. - - 5. Conveying Modified Source Versions. - - You may convey a work based on the Program, or the modifications to -produce it from the Program, in the form of source code under the -terms of section 4, provided that you also meet all of these conditions: - - a) The work must carry prominent notices stating that you modified - it, and giving a relevant date. - - b) The work must carry prominent notices stating that it is - released under this License and any conditions added under section - 7. This requirement modifies the requirement in section 4 to - "keep intact all notices". - - c) You must license the entire work, as a whole, under this - License to anyone who comes into possession of a copy. This - License will therefore apply, along with any applicable section 7 - additional terms, to the whole of the work, and all its parts, - regardless of how they are packaged. This License gives no - permission to license the work in any other way, but it does not - invalidate such permission if you have separately received it. - - d) If the work has interactive user interfaces, each must display - Appropriate Legal Notices; however, if the Program has interactive - interfaces that do not display Appropriate Legal Notices, your - work need not make them do so. - - A compilation of a covered work with other separate and independent -works, which are not by their nature extensions of the covered work, -and which are not combined with it such as to form a larger program, -in or on a volume of a storage or distribution medium, is called an -"aggregate" if the compilation and its resulting copyright are not -used to limit the access or legal rights of the compilation's users -beyond what the individual works permit. Inclusion of a covered work -in an aggregate does not cause this License to apply to the other -parts of the aggregate. - - 6. Conveying Non-Source Forms. - - You may convey a covered work in object code form under the terms -of sections 4 and 5, provided that you also convey the -machine-readable Corresponding Source under the terms of this License, -in one of these ways: - - a) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by the - Corresponding Source fixed on a durable physical medium - customarily used for software interchange. - - b) Convey the object code in, or embodied in, a physical product - (including a physical distribution medium), accompanied by a - written offer, valid for at least three years and valid for as - long as you offer spare parts or customer support for that product - model, to give anyone who possesses the object code either (1) a - copy of the Corresponding Source for all the software in the - product that is covered by this License, on a durable physical - medium customarily used for software interchange, for a price no - more than your reasonable cost of physically performing this - conveying of source, or (2) access to copy the - Corresponding Source from a network server at no charge. - - c) Convey individual copies of the object code with a copy of the - written offer to provide the Corresponding Source. This - alternative is allowed only occasionally and noncommercially, and - only if you received the object code with such an offer, in accord - with subsection 6b. - - d) Convey the object code by offering access from a designated - place (gratis or for a charge), and offer equivalent access to the - Corresponding Source in the same way through the same place at no - further charge. You need not require recipients to copy the - Corresponding Source along with the object code. If the place to - copy the object code is a network server, the Corresponding Source - may be on a different server (operated by you or a third party) - that supports equivalent copying facilities, provided you maintain - clear directions next to the object code saying where to find the - Corresponding Source. Regardless of what server hosts the - Corresponding Source, you remain obligated to ensure that it is - available for as long as needed to satisfy these requirements. - - e) Convey the object code using peer-to-peer transmission, provided - you inform other peers where the object code and Corresponding - Source of the work are being offered to the general public at no - charge under subsection 6d. - - A separable portion of the object code, whose source code is excluded -from the Corresponding Source as a System Library, need not be -included in conveying the object code work. - - A "User Product" is either (1) a "consumer product", which means any -tangible personal property which is normally used for personal, family, -or household purposes, or (2) anything designed or sold for incorporation -into a dwelling. In determining whether a product is a consumer product, -doubtful cases shall be resolved in favor of coverage. For a particular -product received by a particular user, "normally used" refers to a -typical or common use of that class of product, regardless of the status -of the particular user or of the way in which the particular user -actually uses, or expects or is expected to use, the product. A product -is a consumer product regardless of whether the product has substantial -commercial, industrial or non-consumer uses, unless such uses represent -the only significant mode of use of the product. - - "Installation Information" for a User Product means any methods, -procedures, authorization keys, or other information required to install -and execute modified versions of a covered work in that User Product from -a modified version of its Corresponding Source. The information must -suffice to ensure that the continued functioning of the modified object -code is in no case prevented or interfered with solely because -modification has been made. - - If you convey an object code work under this section in, or with, or -specifically for use in, a User Product, and the conveying occurs as -part of a transaction in which the right of possession and use of the -User Product is transferred to the recipient in perpetuity or for a -fixed term (regardless of how the transaction is characterized), the -Corresponding Source conveyed under this section must be accompanied -by the Installation Information. But this requirement does not apply -if neither you nor any third party retains the ability to install -modified object code on the User Product (for example, the work has -been installed in ROM). - - The requirement to provide Installation Information does not include a -requirement to continue to provide support service, warranty, or updates -for a work that has been modified or installed by the recipient, or for -the User Product in which it has been modified or installed. Access to a -network may be denied when the modification itself materially and -adversely affects the operation of the network or violates the rules and -protocols for communication across the network. - - Corresponding Source conveyed, and Installation Information provided, -in accord with this section must be in a format that is publicly -documented (and with an implementation available to the public in -source code form), and must require no special password or key for -unpacking, reading or copying. - - 7. Additional Terms. - - "Additional permissions" are terms that supplement the terms of this -License by making exceptions from one or more of its conditions. -Additional permissions that are applicable to the entire Program shall -be treated as though they were included in this License, to the extent -that they are valid under applicable law. If additional permissions -apply only to part of the Program, that part may be used separately -under those permissions, but the entire Program remains governed by -this License without regard to the additional permissions. - - When you convey a copy of a covered work, you may at your option -remove any additional permissions from that copy, or from any part of -it. (Additional permissions may be written to require their own -removal in certain cases when you modify the work.) You may place -additional permissions on material, added by you to a covered work, -for which you have or can give appropriate copyright permission. - - Notwithstanding any other provision of this License, for material you -add to a covered work, you may (if authorized by the copyright holders of -that material) supplement the terms of this License with terms: - - a) Disclaiming warranty or limiting liability differently from the - terms of sections 15 and 16 of this License; or - - b) Requiring preservation of specified reasonable legal notices or - author attributions in that material or in the Appropriate Legal - Notices displayed by works containing it; or - - c) Prohibiting misrepresentation of the origin of that material, or - requiring that modified versions of such material be marked in - reasonable ways as different from the original version; or - - d) Limiting the use for publicity purposes of names of licensors or - authors of the material; or - - e) Declining to grant rights under trademark law for use of some - trade names, trademarks, or service marks; or - - f) Requiring indemnification of licensors and authors of that - material by anyone who conveys the material (or modified versions of - it) with contractual assumptions of liability to the recipient, for - any liability that these contractual assumptions directly impose on - those licensors and authors. - - All other non-permissive additional terms are considered "further -restrictions" within the meaning of section 10. If the Program as you -received it, or any part of it, contains a notice stating that it is -governed by this License along with a term that is a further -restriction, you may remove that term. If a license document contains -a further restriction but permits relicensing or conveying under this -License, you may add to a covered work material governed by the terms -of that license document, provided that the further restriction does -not survive such relicensing or conveying. - - If you add terms to a covered work in accord with this section, you -must place, in the relevant source files, a statement of the -additional terms that apply to those files, or a notice indicating -where to find the applicable terms. - - Additional terms, permissive or non-permissive, may be stated in the -form of a separately written license, or stated as exceptions; -the above requirements apply either way. - - 8. Termination. - - You may not propagate or modify a covered work except as expressly -provided under this License. Any attempt otherwise to propagate or -modify it is void, and will automatically terminate your rights under -this License (including any patent licenses granted under the third -paragraph of section 11). - - However, if you cease all violation of this License, then your -license from a particular copyright holder is reinstated (a) -provisionally, unless and until the copyright holder explicitly and -finally terminates your license, and (b) permanently, if the copyright -holder fails to notify you of the violation by some reasonable means -prior to 60 days after the cessation. - - Moreover, your license from a particular copyright holder is -reinstated permanently if the copyright holder notifies you of the -violation by some reasonable means, this is the first time you have -received notice of violation of this License (for any work) from that -copyright holder, and you cure the violation prior to 30 days after -your receipt of the notice. - - Termination of your rights under this section does not terminate the -licenses of parties who have received copies or rights from you under -this License. If your rights have been terminated and not permanently -reinstated, you do not qualify to receive new licenses for the same -material under section 10. - - 9. Acceptance Not Required for Having Copies. - - You are not required to accept this License in order to receive or -run a copy of the Program. Ancillary propagation of a covered work -occurring solely as a consequence of using peer-to-peer transmission -to receive a copy likewise does not require acceptance. However, -nothing other than this License grants you permission to propagate or -modify any covered work. These actions infringe copyright if you do -not accept this License. Therefore, by modifying or propagating a -covered work, you indicate your acceptance of this License to do so. - - 10. Automatic Licensing of Downstream Recipients. - - Each time you convey a covered work, the recipient automatically -receives a license from the original licensors, to run, modify and -propagate that work, subject to this License. You are not responsible -for enforcing compliance by third parties with this License. - - An "entity transaction" is a transaction transferring control of an -organization, or substantially all assets of one, or subdividing an -organization, or merging organizations. If propagation of a covered -work results from an entity transaction, each party to that -transaction who receives a copy of the work also receives whatever -licenses to the work the party's predecessor in interest had or could -give under the previous paragraph, plus a right to possession of the -Corresponding Source of the work from the predecessor in interest, if -the predecessor has it or can get it with reasonable efforts. - - You may not impose any further restrictions on the exercise of the -rights granted or affirmed under this License. For example, you may -not impose a license fee, royalty, or other charge for exercise of -rights granted under this License, and you may not initiate litigation -(including a cross-claim or counterclaim in a lawsuit) alleging that -any patent claim is infringed by making, using, selling, offering for -sale, or importing the Program or any portion of it. - - 11. Patents. - - A "contributor" is a copyright holder who authorizes use under this -License of the Program or a work on which the Program is based. The -work thus licensed is called the contributor's "contributor version". - - A contributor's "essential patent claims" are all patent claims -owned or controlled by the contributor, whether already acquired or -hereafter acquired, that would be infringed by some manner, permitted -by this License, of making, using, or selling its contributor version, -but do not include claims that would be infringed only as a -consequence of further modification of the contributor version. For -purposes of this definition, "control" includes the right to grant -patent sublicenses in a manner consistent with the requirements of -this License. - - Each contributor grants you a non-exclusive, worldwide, royalty-free -patent license under the contributor's essential patent claims, to -make, use, sell, offer for sale, import and otherwise run, modify and -propagate the contents of its contributor version. - - In the following three paragraphs, a "patent license" is any express -agreement or commitment, however denominated, not to enforce a patent -(such as an express permission to practice a patent or covenant not to -sue for patent infringement). To "grant" such a patent license to a -party means to make such an agreement or commitment not to enforce a -patent against the party. - - If you convey a covered work, knowingly relying on a patent license, -and the Corresponding Source of the work is not available for anyone -to copy, free of charge and under the terms of this License, through a -publicly available network server or other readily accessible means, -then you must either (1) cause the Corresponding Source to be so -available, or (2) arrange to deprive yourself of the benefit of the -patent license for this particular work, or (3) arrange, in a manner -consistent with the requirements of this License, to extend the patent -license to downstream recipients. "Knowingly relying" means you have -actual knowledge that, but for the patent license, your conveying the -covered work in a country, or your recipient's use of the covered work -in a country, would infringe one or more identifiable patents in that -country that you have reason to believe are valid. - - If, pursuant to or in connection with a single transaction or -arrangement, you convey, or propagate by procuring conveyance of, a -covered work, and grant a patent license to some of the parties -receiving the covered work authorizing them to use, propagate, modify -or convey a specific copy of the covered work, then the patent license -you grant is automatically extended to all recipients of the covered -work and works based on it. - - A patent license is "discriminatory" if it does not include within -the scope of its coverage, prohibits the exercise of, or is -conditioned on the non-exercise of one or more of the rights that are -specifically granted under this License. You may not convey a covered -work if you are a party to an arrangement with a third party that is -in the business of distributing software, under which you make payment -to the third party based on the extent of your activity of conveying -the work, and under which the third party grants, to any of the -parties who would receive the covered work from you, a discriminatory -patent license (a) in connection with copies of the covered work -conveyed by you (or copies made from those copies), or (b) primarily -for and in connection with specific products or compilations that -contain the covered work, unless you entered into that arrangement, -or that patent license was granted, prior to 28 March 2007. - - Nothing in this License shall be construed as excluding or limiting -any implied license or other defenses to infringement that may -otherwise be available to you under applicable patent law. - - 12. No Surrender of Others' Freedom. - - If conditions are imposed on you (whether by court order, agreement or -otherwise) that contradict the conditions of this License, they do not -excuse you from the conditions of this License. If you cannot convey a -covered work so as to satisfy simultaneously your obligations under this -License and any other pertinent obligations, then as a consequence you may -not convey it at all. For example, if you agree to terms that obligate you -to collect a royalty for further conveying from those to whom you convey -the Program, the only way you could satisfy both those terms and this -License would be to refrain entirely from conveying the Program. - - 13. Use with the GNU Affero General Public License. - - Notwithstanding any other provision of this License, you have -permission to link or combine any covered work with a work licensed -under version 3 of the GNU Affero General Public License into a single -combined work, and to convey the resulting work. The terms of this -License will continue to apply to the part which is the covered work, -but the special requirements of the GNU Affero General Public License, -section 13, concerning interaction through a network will apply to the -combination as such. - - 14. Revised Versions of this License. - - The Free Software Foundation may publish revised and/or new versions of -the GNU General Public License from time to time. Such new versions will -be similar in spirit to the present version, but may differ in detail to -address new problems or concerns. - - Each version is given a distinguishing version number. If the -Program specifies that a certain numbered version of the GNU General -Public License "or any later version" applies to it, you have the -option of following the terms and conditions either of that numbered -version or of any later version published by the Free Software -Foundation. If the Program does not specify a version number of the -GNU General Public License, you may choose any version ever published -by the Free Software Foundation. - - If the Program specifies that a proxy can decide which future -versions of the GNU General Public License can be used, that proxy's -public statement of acceptance of a version permanently authorizes you -to choose that version for the Program. - - Later license versions may give you additional or different -permissions. However, no additional obligations are imposed on any -author or copyright holder as a result of your choosing to follow a -later version. - - 15. Disclaimer of Warranty. - - THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY -APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT -HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY -OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, -THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR -PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM -IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF -ALL NECESSARY SERVICING, REPAIR OR CORRECTION. - - 16. Limitation of Liability. - - IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING -WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS -THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY -GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE -USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF -DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD -PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), -EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF -SUCH DAMAGES. - - 17. Interpretation of Sections 15 and 16. - - If the disclaimer of warranty and limitation of liability provided -above cannot be given local legal effect according to their terms, -reviewing courts shall apply local law that most closely approximates -an absolute waiver of all civil liability in connection with the -Program, unless a warranty or assumption of liability accompanies a -copy of the Program in return for a fee. - - END OF TERMS AND CONDITIONS - - How to Apply These Terms to Your New Programs - - If you develop a new program, and you want it to be of the greatest -possible use to the public, the best way to achieve this is to make it -free software which everyone can redistribute and change under these terms. - - To do so, attach the following notices to the program. It is safest -to attach them to the start of each source file to most effectively -state the exclusion of warranty; and each file should have at least -the "copyright" line and a pointer to where the full notice is found. - - - Copyright (C) - - This program is free software: you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation, either version 3 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program. If not, see . - -Also add information on how to contact you by electronic and paper mail. - - If the program does terminal interaction, make it output a short -notice like this when it starts in an interactive mode: - - Copyright (C) - This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. - This is free software, and you are welcome to redistribute it - under certain conditions; type `show c' for details. - -The hypothetical commands `show w' and `show c' should show the appropriate -parts of the General Public License. Of course, your program's commands -might be different; for a GUI interface, you would use an "about box". - - You should also get your employer (if you work as a programmer) or school, -if any, to sign a "copyright disclaimer" for the program, if necessary. -For more information on this, and how to apply and follow the GNU GPL, see -. - - The GNU General Public License does not permit incorporating your program -into proprietary programs. If your program is a subroutine library, you -may consider it more useful to permit linking proprietary applications with -the library. If this is what you want to do, use the GNU Lesser General -Public License instead of this License. But first, please read -. diff --git a/README.md b/README.md deleted file mode 100644 index fad2624..0000000 --- a/README.md +++ /dev/null @@ -1,21 +0,0 @@ -# Compositor - -This is a Wayland compositor written in Rust made using [Smithay](https://smithay.github.io/). - -Currently, it is heavily based off Smithay's `anvil` reference compositor. - -## Features - -- [ ] Backends - - [x] winit - - [x] udev -- [ ] Window Decorations -- [ ] Abstractions over Wayland Stack -- [ ] Integration with GUI Shell components -- [ ] API - -## Contributing - -Please see [contributing guidelines](https://github.com/Avdan-OS/Compositor/blob/main/CONTRIBUTING.md) for more information. - -> If you want to look into developing in devcontainer, please see [DevContainer.md](./Devcontainer.md). diff --git a/resources/avdanos-cursor-64.rgba b/resources/avdanos-cursor-64.rgba deleted file mode 100644 index aadbcc10fc6e055cd967e045c5a2a0f5086f26d1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmd^`2W(a6701oqdvCM10JZE*D4-HXOBkXQgCIztMhqc}7$J;MT8UX|L<^J=3W!#0 zDIi1wlp!O;2!S#k0yY>h28_Y$-P7Oy*4MHq0rLDFes+%Z)5FH^`ToD}ob#P??sZsL zSkbVsur@Fp210Gn_wvC{!D=G%_wXg`fho`yN<)6g>((5B8{rW=fin;ZzXGjIA;`-n z-~R_BKpLb#EXWt8g8sh*%!USv9teJx^Xbs{K8wK)(UQithLtd~<{ViIw@U30D)^6RpWoOQuu@x&;SoiMT zol>Pr`4IXNVqh;!ga%L$a=T^fZ`rb?Z{50e_W1E*OHNLMgD$8GlP+19ypXRA=5LOQ;Xgl3(S|AdiH7jn}%`9}S(TZg-M@49&* zA|k@6RjZa$v}jS~g+z2;hWXG1%0q7Og?G}QbsbVtQtZ*AM-~+oWs4Utwr<_JIn)m6 z1q&8@f#z$l3w{Txt(6x-xsUi!`n`3~tMbB?D_3m8h7C4q)F@*O9O_2Z(2wCHEPxJB z213>R*01xyJ0}kvI%G3v&UD(hZ|`tUI>f_s1S=l?4kMrr6oF7Pzx8{*p!}$qxO3-@ zZQi`u#*G_i&6_v3V#SIXVwE5N4WC1Ar~si9582kAbsbbYoj7sA=FXjK?b@|7`YFXk zGMZyyBMgCX2$eG;p!(C&(kwAC(Jo%RXpxbTHhA!0r*7T4KF)|#&PuI;dZ`(pyigW$ zx|a%=e$Nl|s(5(t;DPH4)22-`dMm|)`Uu6tb=U%`nJPj~#zRo_D<`CsfYG4Z@qJrR^=lop2IBei=XUn&SzESjnGF~)z$#U$IQBd-raeJbALUZrxh*wsh(R z`NFrb6#9c^EWxTBb40(F7regu{Q2|7jK$Hst#akcnm?;%x&@lCXx^s0P#6N87jjI0 z)_738aO1`e+rE9fO`JH<8a8aGb21&h@t}D469l7P2nqd)2h|HtpFXv7=g!%xRjaIT z-@eKVs-IKHk9X1kC42&{Ay9cCB=mdnpmmTh#Ky+jmMvRs?AWoI!y9LWdMV8pPQqgN z0LnpjE#&m;oK#-8fB(K6J9f+#Em~x~diApM<;yEKYW}Pl%X;_)=ss9XbgJR&Xg`|FOEwm1*6_gh)UAkoRxId+Hl3q%=F&dUb zOVHWikA<{;Zyofi+EFvdS+i!jIU!^A{1u{q1L~jrv0S5H@gP4qdi1C}Z={Fvd|)ZU z>-i&|_XJG87qdFYguWJmGJTdlS9wwQCYv^G>eb8p(9t#&^lL7zIK6P;g5A4!&yC$c zuYvkV_0seWx)-NDIto-L`(pvyf9=hYBS+lXpYEAz7JBX4HGB5#nVZW4u@3qS<$;qY zPukF-Lmi&kkPlpd(f)|%Jps|L>r&mK-o9hUj#j&NZR^^#tIe4+$34U47dq3kYo7j7 zr%u_hVZ%a7zwQNS?%SY21EWqcW}e3E(;^}wY|oxOZrlb!|BDwd?C{~k*1vy$`M>u6 zEDX-+{g-~-8?9fzzEQ6$E;aK_saUb151(*2w;aWX`hNL}_ENm7>1P#V$}3s&Un0L} zKWK*Nj|I&CU(v5x{d>3sPw@wzdOF=3*|B4XMbq~y9)65|oqx)Gr%#`@F=NK)-h$4* zJD@tS0r+FV(jU*oXx6^~v_IdGtCDKes9^&K4s`cVv%vWH_{=lnZEGQ)(b=+O$r5YR zrj6lSTH_P&F;oD5ELi&ExR}3!dR09?r#(8)H79Du!~HQwGoJ$o4%qG6x7~exwPw(I zXwSWSDp#*wwWUj!TBlB(G$&Gg-G)^l4LMr>>RRRh%J&)Zujl3>p(d1sS}+0B!^g4? zFX?F=o}+N)&!6w+GsT4bK{cN4A?e(F`0$}!zkc2J@89pv$g<2u68M}ipcm*KyT2BY z{Z|f?e=7ggt5;9?&(p8jU7-y0Z_ReqvqbSTPgsLge%|TRr;jsr>Qra->eaSs(ty;CR4jnpJmo8o0I)Z2N+}Uu(y)(u871#j7pc-VS_vh__seiQp z(y!SaJ^HKr@9Ed?lWCT#c+m6qe+1>pYplaF;=;(2nuY6C=eu(J6Sxe!Ky&f#P#Lnb z@4Vf$fbGBPQq8M)MozOT`TuIr9=)ocZ@H$p2!}o(e^>(A+pVxI1yqE$?UVjrKB3sC1I?fXGzIxd8Spp1^VT<7u=J}g(S9oTzl$l3wXf1y zGz;<(eLuT#&PTk~f~Eg9zikmnODLOWXJ0@`&)m1wV#;?}O|^@9ypbbE zsxDRhN5f|j>iX+)jjGcg=$U`jFRI6+=f{|Gzn*i@eQnM%>DT?csSvEV<)Z^)%1;a6 zI?t`8t5?;GQ9b6nntVmiN$=geS7*1^lb_%-M}Xotluh~Rw{Vm*H<{Yn)~{dh_NZ@* z0sX&FeXbe3YQOOCa6S8{dzU*wJ?YzWKR=SAru}IJkr2l{7N6!Sdd^wT?x_FxA@j8B zIVb&{0DmLk6e&_9h0js!&x1-3>ZbkCbwA4iu^2!D3#WbssUgbr->T7S`dG^=A?{&{U zmG^5FrMy=gLf!Yd#7xs(^#s+Ocj>d_2kv>Md(oP=>6u17@2t6na^1p(3yr?bs#dM4 zdvc2TTkt6;oFkk?;w#rEREdeD_*>~d%h9p(wzY6%XY&IP`$1? zJ~x|UR%c=p(0#?_upcz9_y)d(3w*8fU1yy3{FhJ%q&--5Iv@RBO+AO=p%L@|t;J-R W4in)c=m_f1lsj@$uH$n5Xa5Ca;%6@a diff --git a/resources/avdanos_cursor.rgba b/resources/avdanos_cursor.rgba deleted file mode 100644 index 35adaa63536a8aedea1fd799b02e2977a577b688..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 16384 zcmeI3ZAe~a7{}*JzHfr< z#QdkX0WvNh4k6a5&$6uKxd1mmF-%#~sh(d7eEN{J8z_oWuS9 z-`D+q+_lReeh2&x_#N;&;HCr5;dkXA$<5#U($8Wb<_93)OYkyyl;SCf9Rt+w0!yF~ zJP96!FzcrWgdSW6&x1hV%dxKi@ZrOfot>>7u$%#JgLoe{U|oN0ZLN%sj!JoXx$NG( zTYXr7c8&`<;4vTcVO@V+U7axI=jY|(#f!3k|9dbe(AwH6d-v{D4_J zg@uI@5)z_5EJJuFcm{Y|E^Jx9%jJ^E$w?y@Vq#(>ARs_}_!0UmK?v}c*s^}j1C9$f zZ{8Hk%oo_Nls2yA6B7#2)qWIxL(*c{h9~M%gfT&*C!bn8R`Mc zzraTz8aOGoT|a%`9C7>hZ8>x1jKs#qst;T*41!`1Q^78r>CX9zFs0DBh>?z%!OCLPVTEcqMtt8yLV46UAiP$Sy`G3 zE@+Jb@2t2)h_PBnIjdOEza^=buNli`FTws6T zzPbr`a$NAZe$4~=aO>7BId<&W#`W_T@E{v_B6du_`oMAF^5x5N;J^XhOL3l@0!ffRUPM_AU+ zz25QT$EBmALzb47tV&;ho@>SM@Nmt6SM3mwcUsn8T3Tw|X$a!OYeB3xkkB^U6?eloH7T9^$Y=?eS*$A%LN&)YMT)?SoFN)we)6urn;{&(F`-dTaZe>&x?ueSm$KXFY}w z92+>dM?^$OSXh`o&yshE--9aPiEk||>rYQl*ZSX2KV%re@hcB-eB?N(&uGap^J~C1 z-v>Mu+o^v5mEP>^hCVzFxSpjSBp2{|yC~pw=U=Aw??wH+?K^<&Z2unU_qJf({R*V2 zs>-eT**t&Wy?a+uQc|=JcYB+7)R+0ndkEqD%zKKh#K6FS`QG~jFVxQ}e@OjtadC3> z>Q%Spb4`2h+&Kvj4rcpv-OqbHZ;X6YdI3m5L4jL)c&=mlt*9yYNAdhl-QigEobyo! zSmSpr&CSim{ltS3f!ev2FDWU}e$Ta91F-9i4zIR7xX0kVtnuyd>C>kl=)u_7m=qNi zY2Lf=%yp0eygj&Y8w1APlevBV{CVl^?Unxieq--aQ&S^}iHX|o?DOA(RNxE2xu5`W z&q-~DPJRo{vC*_nXPfJtWhVZ%bAN`{dp!8P()-{$z`jkr+U7I(yC1v>B7v_4b73EN c3A_PLfHJ`Ul}ZHu@H^ml!0&+H0f#y8H&m28VgLXD diff --git a/src/compat/mod.rs b/src/compat/mod.rs deleted file mode 100644 index 03195b4..0000000 --- a/src/compat/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -//! -//! A collection of modules to help integrate to external APIs and standards. -//! -pub mod xdg_base_dirs; - -pub use xdg_base_dirs::{XdgBaseDir, BaseDir}; \ No newline at end of file diff --git a/src/compat/xdg_base_dirs.rs b/src/compat/xdg_base_dirs.rs deleted file mode 100644 index 5727f82..0000000 --- a/src/compat/xdg_base_dirs.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! -//! -//! ## XDG Base Directory (0.8) -//! The module handles all to do with the XDG Base Diretory Standard. -//! -//! View the original spec at [specification.freedesktop.org](https://specifications.freedesktop.org/basedir-spec/basedir-spec-latest.html). -//! - -use std::{env, path::PathBuf}; - -pub trait BaseDir { - /// - /// Return the label of the corresponding environment - /// variable for this base directory. - /// - fn env_variable(&self) -> &'static str; - - /// - /// Return the default path for this base directory. - /// Called when: - /// * The folder at the explicitly path does not exist; or - /// * The corresponding environment vaiable is not set. - /// - /// - fn default(&self) -> PathBuf; - - /// - /// Returns the path of the base directory. - /// Handles any error cases. - /// - /// - fn path(&self) -> PathBuf { - env::var(self.env_variable()) - .ok() - .and_then(|p| { - let p = PathBuf::from(p); - p.exists().then_some(p) - }) - .unwrap_or(self.default()) - } -} - -/// -/// Wrapper enum over the XDG base directories. -/// -pub enum XdgBaseDir { - /// - /// From `$XDG_DATA_HOME`: - /// > \[The] base directory relative to which user-specific data files should be stored. - /// - Data, - - /// - /// From `$XDG_CONFIG_HOME`: - /// > \[The] base directory relative to which user-specific configuration files should be stored. - /// - Config, - - /// - /// From `$XDG_STATE_HOME`: - /// > \[The] base directory relative to which user-specific state files should be stored. - /// > - /// > Contains state data that should persist between (application) restarts, but that is not important or portable enough to the user that it should be stored in $XDG_DATA_HOME. It may contain: - /// > * actions history (logs, history, recently used files, …) - /// > * current state of the application that can be reused on a restart (view, layout, open files, undo history, …) - /// - State, -} - -impl BaseDir for XdgBaseDir { - fn env_variable(&self) -> &'static str { - match self { - Self::Data => "XDG_DATA_HOME", - Self::Config => "XDG_CONFIG_HOME", - Self::State => "XDG_STATE_HOME", - } - } - - fn default(&self) -> PathBuf { - let mut path: PathBuf = env::var("HOME").expect("NO $HOME SET! YOU MONSTER!").into(); - - if !path.exists() { - panic!("$HOME INEXISTANT -- PANIC!"); - } - - match self { - XdgBaseDir::Data => path.push(".local/share"), - XdgBaseDir::Config => path.push(".config"), - XdgBaseDir::State => path.push(".local/state"), - }; - - path - } -} diff --git a/src/compositor/backend/mod.rs b/src/compositor/backend/mod.rs deleted file mode 100644 index c50049c..0000000 --- a/src/compositor/backend/mod.rs +++ /dev/null @@ -1,14 +0,0 @@ -mod udev; -mod winit; - -pub use udev::{run_udev, UdevData}; -pub use winit::run_winit; - -use smithay::{output::Output, reexports::wayland_server::protocol::wl_surface::WlSurface}; - -pub trait Backend { - const HAS_RELATIVE_MOTION: bool = false; - fn seat_name(&self) -> String; - fn reset_buffers(&mut self, output: &Output); - fn early_import(&mut self, surface: &WlSurface); -} diff --git a/src/compositor/backend/udev.rs b/src/compositor/backend/udev.rs deleted file mode 100644 index 6424676..0000000 --- a/src/compositor/backend/udev.rs +++ /dev/null @@ -1,1219 +0,0 @@ -//! -//! The `udev` backend allows us to directly -//! interface with the drm (Display Rendering Manager) -//! which allows to draw pixels to the screen. -//! -//! Also: -//! * we interact with graphics hardware, too. -//! * start user sessions here. -//! * other processes. -//! -//! This module is basically a copy-paste from anvil's -//! `udev` backend, so this will be heavily worked on later. -//! - -// NOTE: We are also including the EGL features by default. -// So, yup.. - -// TODO(Sammy99jsp) REVIEW THIS ENTIRE MODULE: - -use std::{ - borrow::Cow, - cell::RefCell, - collections::{hash_map::Entry, HashMap}, - ffi::OsString, - os::unix::prelude::FromRawFd, - path::PathBuf, - rc::Rc, - sync::{atomic::Ordering, Mutex}, - time::Duration, -}; - -use libc::dev_t; - -use slog::Logger; -use smithay::{ - backend::{ - allocator::dmabuf::Dmabuf, - drm::{ - DrmDevice, DrmDeviceFd, DrmError, DrmEvent, DrmEventMetadata, DrmNode, - GbmBufferedSurface, NodeType, - }, - egl::{EGLContext, EGLDevice, EGLDisplay}, - libinput::{LibinputInputBackend, LibinputSessionInterface}, - renderer::{ - damage::{DamageTrackedRenderer, DamageTrackedRendererError}, - element::{texture::TextureBuffer, AsRenderElements}, - gles2::{Gles2Renderbuffer, Gles2Renderer}, - multigpu::{egl::EglGlesBackend, GpuManager, MultiRenderer, MultiTexture}, - Bind, Frame, ImportDma, ImportEgl, Renderer, - }, - session::{libseat::LibSeatSession, Event as SessionEvent, Session}, - udev::{all_gpus, primary_gpu, UdevBackend, UdevEvent}, - SwapBuffersError, - }, - delegate_dmabuf, - desktop::{space::SurfaceTree, utils::OutputPresentationFeedback, Space}, - input::pointer::{CursorImageAttributes, CursorImageStatus}, - output::{Mode, Output, PhysicalProperties, Subpixel}, - reexports::{ - calloop::{ - timer::{TimeoutAction, Timer}, - Dispatcher, EventLoop, LoopHandle, RegistrationToken, - }, - drm::{ - self, - control::{ - connector::{self, Info as ConnectorInfo, State as ConnectorState}, - crtc, - encoder::Info as EncoderInfo, - Device, - }, - }, - gbm::Device as GbmDevice, - input::Libinput, - nix::fcntl::OFlag, - wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, - wayland_server::{backend::GlobalId, protocol::wl_surface, Display, DisplayHandle}, - }, - utils::{Clock, DeviceFd, IsAlive, Logical, Monotonic, Point, Rectangle, Scale, Transform}, - wayland::{ - compositor, - dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportError}, - input_method::{InputMethodHandle, InputMethodSeat}, - }, -}; - -use crate::compositor::{ - components::Cursor, - drawing::{PointerElement, CLEAR_COLOR}, - render::{render_output, CustomRenderElements}, - shell::{self, AvWindow}, - state::{post_repaint, take_presentation_feedback, CalloopData, Navda}, -}; - -use super::Backend; - -type UdevRenderer<'a> = MultiRenderer< - 'a, - 'a, - EglGlesBackend, - EglGlesBackend, - Gles2Renderbuffer, ->; - -#[derive(Debug, PartialEq)] -struct UdevOutputId { - device_id: DrmNode, - crtc: crtc::Handle, -} - -pub struct UdevData { - pub session: LibSeatSession, - dh: DisplayHandle, - // EGL - dmabuf_state: Option<(DmabufState, DmabufGlobal)>, - primary_gpu: DrmNode, - gpus: GpuManager>, - backends: HashMap, - pointer_images: Vec<(xcursor::parser::Image, TextureBuffer)>, - pointer_element: PointerElement, - pointer_image: Cursor, - logger: slog::Logger, -} - -impl Backend for UdevData { - fn seat_name(&self) -> String { - self.session.seat() - } - - fn reset_buffers(&mut self, output: &Output) { - if let Some(id) = output.user_data().get::() { - if let Some(gpu) = self.backends.get(&id.device_id) { - let surfaces = gpu.surfaces.borrow(); - if let Some(surface) = surfaces.get(&id.crtc) { - surface.borrow_mut().surface.reset_buffers(); - } - } - } - } - - fn early_import(&mut self, surface: &wl_surface::WlSurface) { - if let Err(err) = self - .gpus - .early_import(Some(self.primary_gpu), self.primary_gpu, surface) - { - slog::warn!(self.logger, "Early buffer import failed: {}", err); - } - } -} - -// EGL -impl DmabufHandler for Navda { - fn dmabuf_state(&mut self) -> &mut DmabufState { - &mut self.backend_data.dmabuf_state.as_mut().unwrap().0 - } - - fn dmabuf_imported( - &mut self, - _global: &DmabufGlobal, - dmabuf: Dmabuf, - ) -> Result<(), ImportError> { - self.backend_data - .gpus - .renderer::( - &self.backend_data.primary_gpu, - &self.backend_data.primary_gpu, - ) - .and_then(|mut renderer| renderer.import_dmabuf(&dmabuf, None)) - .map(|_| ()) - .map_err(|_| ImportError::Failed) - } -} - -delegate_dmabuf!(Navda); - -/// -/// Run the compositor under the udev backend -/// -pub fn run_udev(log: Logger) { - let mut event_loop = EventLoop::try_new().unwrap(); - let mut display = Display::new().unwrap(); - - /* - * Initialize session - */ - let (session, notifier) = match LibSeatSession::new(log.clone()) { - Ok(ret) => ret, - Err(err) => { - slog::crit!(log, "Could not initialize a session: {}", err); - return; - } - }; - - /* - * Initialize the compositor - */ - let primary_gpu = if let Ok(var) = std::env::var("ANVIL_DRM_DEVICE") { - DrmNode::from_path(var).expect("Invalid drm device path") - } else { - primary_gpu(&session.seat()) - .unwrap() - .and_then(|x| { - DrmNode::from_path(x) - .ok()? - .node_with_type(NodeType::Render)? - .ok() - }) - .unwrap_or_else(|| { - all_gpus(&session.seat()) - .unwrap() - .into_iter() - .find_map(|x| DrmNode::from_path(x).ok()) - .expect("No GPU!") - }) - }; - slog::info!(log, "Using {} as primary gpu.", primary_gpu); - - let mut gpus = GpuManager::new(EglGlesBackend::default(), log.clone()).unwrap(); - let mut renderer = gpus - .renderer::(&primary_gpu, &primary_gpu) - .unwrap(); - - // init dmabuf support with format list from our primary gpu - // TODO: This does not necessarily depend on egl, but mesa makes no use of it without wl_drm right now - let dmabuf_state = { - slog::info!( - log, - "Trying to initialize EGL Hardware Acceleration via {:?}", - primary_gpu - ); - - if renderer.bind_wl_display(&display.handle()).is_ok() { - slog::info!(log, "EGL hardware-acceleration enabled"); - let dmabuf_formats = renderer.dmabuf_formats().cloned().collect::>(); - let mut state = DmabufState::new(); - let global = state.create_global::, _>( - &display.handle(), - dmabuf_formats, - log.clone(), - ); - Some((state, global)) - } else { - None - } - }; - - let data = UdevData { - dh: display.handle(), - dmabuf_state, - session, - primary_gpu, - gpus, - backends: HashMap::new(), - pointer_image: Cursor::load(&log), - pointer_images: Vec::new(), - pointer_element: PointerElement::default(), - logger: log.clone(), - }; - let mut state = Navda::new(&mut display, event_loop.handle(), data, log.clone()); - - /* - * Initialize the udev backend - */ - let udev_backend = match UdevBackend::new(&state.seat_name, log.clone()) { - Ok(ret) => ret, - Err(err) => { - slog::crit!(log, "Failed to initialize udev backend"; "error" => err); - return; - } - }; - - /* - * Initialize a fake output (we render one screen to every device in this example) - */ - - /* - * Initialize libinput backend - */ - let mut libinput_context = Libinput::new_with_udev::>( - state.backend_data.session.clone().into(), - ); - libinput_context.udev_assign_seat(&state.seat_name).unwrap(); - let libinput_backend = LibinputInputBackend::new(libinput_context.clone(), log.clone()); - - /* - * Bind all our objects that get driven by the event loop - */ - event_loop - .handle() - .insert_source(libinput_backend, move |event, _, data| { - let dh = data.state.backend_data.dh.clone(); - data.state.process_input_event(&dh, event) - }) - .unwrap(); - let handle = event_loop.handle(); - let log2 = log.clone(); - event_loop - .handle() - .insert_source(notifier, move |event, &mut (), data| match event { - SessionEvent::PauseSession => { - libinput_context.suspend(); - for backend in data.state.backend_data.backends.values() { - backend.event_dispatcher.as_source_ref().pause(); - } - } - SessionEvent::ActivateSession => { - if let Err(err) = libinput_context.resume() { - slog::error!(log2, "Failed to resume libinput context: {:?}", err); - } - for (node, backend) in data - .state - .backend_data - .backends - .iter() - .map(|(handle, backend)| (*handle, backend)) - { - backend.event_dispatcher.as_source_ref().activate(); - let surfaces = backend.surfaces.borrow(); - for surface in surfaces.values() { - if let Err(err) = surface.borrow().surface.surface().reset_state() { - slog::warn!(log2, "Failed to reset drm surface state: {}", err); - } - } - handle.insert_idle(move |data| data.state.render(node, None)); - } - } - }) - .unwrap(); - for (dev, path) in udev_backend.device_list() { - state.device_added(&mut display, dev, path.into()) - } - - event_loop - .handle() - .insert_source(udev_backend, move |event, _, data| match event { - UdevEvent::Added { device_id, path } => { - data.state.device_added(&mut data.display, device_id, path) - } - UdevEvent::Changed { device_id } => { - data.state.device_changed(&mut data.display, device_id) - } - UdevEvent::Removed { device_id } => data.state.device_removed(device_id), - }) - .unwrap(); - - /* - * Start XWayland - */ - if let Err(e) = state.xwayland.start( - state.handle.clone(), - None, - std::iter::empty::<(OsString, OsString)>(), - |_| {}, - ) { - slog::error!(log, "Failed to start XWayland: {}", e); - } - - /* - * And run our main loop - */ - - while state.running.load(Ordering::SeqCst) { - let mut calloop_data = CalloopData { state, display }; - // 60 FPS - let result = event_loop.dispatch(Some(Duration::from_millis(16)), &mut calloop_data); - CalloopData { state, display } = calloop_data; - - if result.is_err() { - state.running.store(false, Ordering::SeqCst); - } else { - state.space.refresh(); - state.popups.cleanup(); - display.flush_clients().unwrap(); - } - } -} - -pub type RenderSurface = - GbmBufferedSurface, Option>; - -struct SurfaceData { - dh: DisplayHandle, - device_id: DrmNode, - render_node: DrmNode, - surface: RenderSurface, - global: Option, - damage_tracked_renderer: DamageTrackedRenderer, -} - -impl Drop for SurfaceData { - fn drop(&mut self) { - if let Some(global) = self.global.take() { - self.dh.remove_global::>(global); - } - } -} - -struct BackendData { - surfaces: Rc>>>>, - gbm: GbmDevice, - registration_token: RegistrationToken, - event_dispatcher: Dispatcher<'static, DrmDevice, CalloopData>, -} - -fn scan_connectors( - device_id: DrmNode, - device: &DrmDevice, - gbm: &GbmDevice, - display: &mut Display>, - space: &mut Space, - #[cfg(feature = "debug")] fps_texture: &MultiTexture, - logger: &::slog::Logger, -) -> HashMap>> { - // Get a set of all modesetting resource handles (excluding planes): - let res_handles = device.resource_handles().unwrap(); - - // Find all connected output ports. - let connector_infos: Vec = res_handles - .connectors() - .iter() - .map(|conn| device.get_connector(*conn, true).unwrap()) - .filter(|conn| conn.state() == ConnectorState::Connected) - .inspect(|conn| slog::info!(logger, "Connected: {:?}", conn.interface())) - .collect(); - - let mut backends = HashMap::new(); - - let (render_node, formats) = { - let display = EGLDisplay::new(gbm.clone(), logger.clone()).unwrap(); - let node = match EGLDevice::device_for_display(&display) - .ok() - .and_then(|x| x.try_get_render_node().ok().flatten()) - { - Some(node) => node, - None => return HashMap::new(), - }; - let context = EGLContext::new(&display, logger.clone()).unwrap(); - (node, context.dmabuf_render_formats().clone()) - }; - - // very naive way of finding good crtc/encoder/connector combinations. This problem is np-complete - for connector_info in connector_infos { - let encoder_infos = connector_info - .encoders() - .iter() - .flat_map(|encoder_handle| device.get_encoder(*encoder_handle)) - .collect::>(); - - let crtcs = encoder_infos - .iter() - .flat_map(|encoder_info| res_handles.filter_crtcs(encoder_info.possible_crtcs())); - - for crtc in crtcs { - // Skip CRTCs used by previous connectors. - let entry = match backends.entry(crtc) { - Entry::Vacant(entry) => entry, - Entry::Occupied(_) => continue, - }; - - slog::info!( - logger, - "Trying to setup connector {:?}-{} with crtc {:?}", - connector_info.interface(), - connector_info.interface_id(), - crtc, - ); - - let mode = connector_info.modes()[0]; - let surface = match device.create_surface(crtc, mode, &[connector_info.handle()]) { - Ok(surface) => surface, - Err(err) => { - slog::warn!(logger, "Failed to create drm surface: {}", err); - continue; - } - }; - - let gbm_surface = match GbmBufferedSurface::new( - surface, - gbm.clone(), - formats.clone(), - logger.clone(), - ) { - Ok(renderer) => renderer, - Err(err) => { - slog::warn!(logger, "Failed to create rendering surface: {}", err); - continue; - } - }; - - let size = mode.size(); - let mode = Mode { - size: (size.0 as i32, size.1 as i32).into(), - refresh: mode.vrefresh() as i32 * 1000, - }; - - let interface_short_name = match connector_info.interface() { - connector::Interface::DVII => Cow::Borrowed("DVI-I"), - connector::Interface::DVID => Cow::Borrowed("DVI-D"), - connector::Interface::DVIA => Cow::Borrowed("DVI-A"), - connector::Interface::SVideo => Cow::Borrowed("S-VIDEO"), - connector::Interface::DisplayPort => Cow::Borrowed("DP"), - connector::Interface::HDMIA => Cow::Borrowed("HDMI-A"), - connector::Interface::HDMIB => Cow::Borrowed("HDMI-B"), - connector::Interface::EmbeddedDisplayPort => Cow::Borrowed("eDP"), - other => Cow::Owned(format!("{:?}", other)), - }; - - let output_name = format!("{}-{}", interface_short_name, connector_info.interface_id()); - - let (phys_w, phys_h) = connector_info.size().unwrap_or((0, 0)); - let output = Output::new( - output_name, - PhysicalProperties { - size: (phys_w as i32, phys_h as i32).into(), - subpixel: Subpixel::Unknown, - make: "Smithay".into(), - model: "Generic DRM".into(), - }, - None, - ); - let global = output.create_global::>(&display.handle()); - let position = ( - space - .outputs() - .fold(0, |acc, o| acc + space.output_geometry(o).unwrap().size.w), - 0, - ) - .into(); - output.change_current_state(Some(mode), None, None, Some(position)); - output.set_preferred(mode); - space.map_output(&output, position); - - output - .user_data() - .insert_if_missing(|| UdevOutputId { crtc, device_id }); - - let damage_tracked_renderer = DamageTrackedRenderer::from_output(&output); - - entry.insert(Rc::new(RefCell::new(SurfaceData { - dh: display.handle(), - device_id, - render_node, - surface: gbm_surface, - global: Some(global), - damage_tracked_renderer, - }))); - - break; - } - } - - backends -} - -impl Navda { - fn device_added(&mut self, display: &mut Display, device_id: dev_t, path: PathBuf) { - // Try to open the device - let open_flags = OFlag::O_RDWR | OFlag::O_CLOEXEC | OFlag::O_NOCTTY | OFlag::O_NONBLOCK; - let device_fd = self.backend_data.session.open(&path, open_flags).ok(); - let devices = device_fd - .map(|fd| DrmDeviceFd::new(unsafe { DeviceFd::from_raw_fd(fd) }, self.log.clone())) - .map(|fd| { - ( - DrmDevice::new(fd.clone(), true, self.log.clone()), - GbmDevice::new(fd), - ) - }); - - // Report device open failures. - let (device, gbm) = match devices { - Some((Ok(drm), Ok(gbm))) => (drm, gbm), - Some((Err(err), _)) => { - slog::warn!( - self.log, - "Skipping device {:?}, because of drm error: {}", - device_id, - err - ); - return; - } - Some((_, Err(err))) => { - // TODO try DumbBuffer allocator in this case - slog::warn!( - self.log, - "Skipping device {:?}, because of gbm error: {}", - device_id, - err - ); - return; - } - None => return, - }; - - let node = match DrmNode::from_dev_id(device_id) { - Ok(node) => node, - Err(err) => { - slog::warn!( - self.log, - "Failed to access drm node for {}: {}", - device_id, - err - ); - return; - } - }; - let backends = Rc::new(RefCell::new(scan_connectors( - node, - &device, - &gbm, - display, - &mut self.space, - &self.log, - ))); - - let event_dispatcher = Dispatcher::new( - device, - move |event, metadata, data: &mut CalloopData<_>| match event { - DrmEvent::VBlank(crtc) => { - data.state.frame_finish(node, crtc, metadata); - } - DrmEvent::Error(error) => { - slog::error!(data.state.log, "{:?}", error); - } - }, - ); - let registration_token = self - .handle - .register_dispatcher(event_dispatcher.clone()) - .unwrap(); - - for backend in backends.borrow_mut().values() { - // render first frame - slog::trace!(self.log, "Scheduling frame"); - schedule_initial_render( - &mut self.backend_data.gpus, - backend.clone(), - &self.handle, - self.log.clone(), - ); - } - - self.backend_data.backends.insert( - node, - BackendData { - registration_token, - event_dispatcher, - surfaces: backends, - gbm, - }, - ); - } - - fn device_changed(&mut self, display: &mut Display, device: dev_t) { - let node = match DrmNode::from_dev_id(device).ok() { - Some(node) => node, - None => return, // we already logged a warning on device_added - }; - - //quick and dirty, just re-init all backends - if let Some(ref mut backend_data) = self.backend_data.backends.get_mut(&node) { - let logger = self.log.clone(); - let loop_handle = self.handle.clone(); - - // scan_connectors will recreate the outputs (and sadly also reset the scales) - for output in self - .space - .outputs() - .filter(|o| { - o.user_data() - .get::() - .map(|id| id.device_id == node) - .unwrap_or(false) - }) - .cloned() - .collect::>() - .into_iter() - { - self.space.unmap_output(&output); - } - - let source = backend_data.event_dispatcher.as_source_mut(); - let mut backends = backend_data.surfaces.borrow_mut(); - *backends = scan_connectors( - node, - &source, - &backend_data.gbm, - display, - &mut self.space, - #[cfg(feature = "debug")] - &self.backend_data.fps_texture, - &logger, - ); - - // fixup window coordinates - shell::fixup_positions(&mut self.space); - - for surface in backends.values() { - let logger = logger.clone(); - // render first frame - schedule_initial_render( - &mut self.backend_data.gpus, - surface.clone(), - &loop_handle, - logger, - ); - } - } - } - - fn device_removed(&mut self, device: dev_t) { - let node = match DrmNode::from_dev_id(device).ok() { - Some(node) => node, - None => return, // we already logged a warning on device_added - }; - // drop the backends on this side - if let Some(backend_data) = self.backend_data.backends.remove(&node) { - // drop surfaces - backend_data.surfaces.borrow_mut().clear(); - slog::debug!(self.log, "Surfaces dropped"); - - for output in self - .space - .outputs() - .filter(|o| { - o.user_data() - .get::() - .map(|id| id.device_id == node) - .unwrap_or(false) - }) - .cloned() - .collect::>() - .into_iter() - { - self.space.unmap_output(&output); - } - shell::fixup_positions(&mut self.space); - - self.handle.remove(backend_data.registration_token); - let _device = backend_data.event_dispatcher.into_source_inner(); - - slog::debug!(self.log, "Dropping device"); - } - } - - fn frame_finish( - &mut self, - dev_id: DrmNode, - crtc: crtc::Handle, - metadata: &mut Option, - ) { - let device_backend = match self.backend_data.backends.get_mut(&dev_id) { - Some(backend) => backend, - None => { - slog::error!( - self.log, - "Trying to finish frame on non-existent backend {}", - dev_id - ); - return; - } - }; - - let surfaces = device_backend.surfaces.borrow(); - let surface = match surfaces.get(&crtc) { - Some(surface) => surface, - None => { - slog::error!( - self.log, - "Trying to finish frame on non-existent crtc {:?}", - crtc - ); - return; - } - }; - - let mut surface = surface.borrow_mut(); - - let output = if let Some(output) = self.space.outputs().find(|o| { - o.user_data().get::() - == Some(&UdevOutputId { - device_id: surface.device_id, - crtc, - }) - }) { - output.clone() - } else { - // somehow we got called with an invalid output - return; - }; - - let schedule_render = match surface - .surface - .frame_submitted() - .map_err(Into::::into) - { - Ok(user_data) => { - if let Some(mut feedback) = user_data.flatten() { - let tp = metadata.as_ref().and_then(|metadata| match metadata.time { - smithay::backend::drm::DrmEventTime::Monotonic(tp) => Some(tp), - smithay::backend::drm::DrmEventTime::Realtime(_) => None, - }); - let seq = metadata - .as_ref() - .map(|metadata| metadata.sequence) - .unwrap_or(0); - - let (clock, flags) = if let Some(tp) = tp { - ( - tp.into(), - wp_presentation_feedback::Kind::Vsync - | wp_presentation_feedback::Kind::HwClock - | wp_presentation_feedback::Kind::HwCompletion, - ) - } else { - (self.clock.now(), wp_presentation_feedback::Kind::Vsync) - }; - - feedback.presented( - clock, - output - .current_mode() - .map(|mode| mode.refresh as u32) - .unwrap_or_default(), - seq as u64, - flags, - ); - } - - true - } - Err(err) => { - slog::warn!(self.log, "Error during rendering: {:?}", err); - match err { - SwapBuffersError::AlreadySwapped => true, - SwapBuffersError::TemporaryFailure(err) => matches!( - err.downcast_ref::(), - Some(&DrmError::DeviceInactive) - | Some(&DrmError::Access { - source: drm::SystemError::PermissionDenied, - .. - }) - ), - SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err), - } - } - }; - - if schedule_render { - let output_refresh = match output.current_mode() { - Some(mode) => mode.refresh, - None => return, - }; - // What are we trying to solve by introducing a delay here: - // - // Basically it is all about latency of client provided buffers. - // A client driven by frame callbacks will wait for a frame callback - // to repaint and submit a new buffer. As we send frame callbacks - // as part of the repaint in the compositor the latency would always - // be approx. 2 frames. By introducing a delay before we repaint in - // the compositor we can reduce the latency to approx. 1 frame + the - // remaining duration from the repaint to the next VBlank. - // - // With the delay it is also possible to further reduce latency if - // the client is driven by presentation feedback. As the presentation - // feedback is directly sent after a VBlank the client can submit a - // new buffer during the repaint delay that can hit the very next - // VBlank, thus reducing the potential latency to below one frame. - // - // Choosing a good delay is a topic on its own so we just implement - // a simple strategy here. We just split the duration between two - // VBlanks into two steps, one for the client repaint and one for the - // compositor repaint. Theoretically the repaint in the compositor should - // be faster so we give the client a bit more time to repaint. On a typical - // modern system the repaint in the compositor should not take more than 2ms - // so this should be safe for refresh rates up to at least 120 Hz. For 120 Hz - // this results in approx. 3.33ms time for repainting in the compositor. - // A too big delay could result in missing the next VBlank in the compositor. - // - // A more complete solution could work on a sliding window analyzing past repaints - // and do some prediction for the next repaint. - let repaint_delay = - Duration::from_millis(((1_000_000f32 / output_refresh as f32) * 0.6f32) as u64); - - let timer = if self.backend_data.primary_gpu != surface.render_node { - // However, if we need to do a copy, that might not be enough. - // (And without actual comparision to previous frames we cannot really know.) - // So lets ignore that in those cases to avoid thrashing performance. - slog::trace!( - self.log, - "scheduling repaint timer immediately on {:?}", - crtc - ); - Timer::immediate() - } else { - slog::trace!( - self.log, - "scheduling repaint timer with delay {:?} on {:?}", - repaint_delay, - crtc - ); - Timer::from_duration(repaint_delay) - }; - - self.handle - .insert_source(timer, move |_, _, data| { - data.state.render(dev_id, Some(crtc)); - TimeoutAction::Drop - }) - .expect("failed to schedule frame timer"); - } - } - - // If crtc is `Some()`, render it, else render all crtcs - fn render(&mut self, dev_id: DrmNode, crtc: Option) { - let device_backend = match self.backend_data.backends.get_mut(&dev_id) { - Some(backend) => backend, - None => { - slog::error!( - self.log, - "Trying to render on non-existent backend {}", - dev_id - ); - return; - } - }; - // setup two iterators on the stack, one over all surfaces for this backend, and - // one containing only the one given as argument. - // They make a trait-object to dynamically choose between the two - let surfaces = device_backend.surfaces.borrow(); - let mut surfaces_iter = surfaces.iter(); - let mut option_iter = crtc - .iter() - .flat_map(|crtc| surfaces.get(crtc).map(|surface| (crtc, surface))); - - let to_render_iter: &mut dyn Iterator>)> = - if crtc.is_some() { - &mut option_iter - } else { - &mut surfaces_iter - }; - - for (&crtc, surface) in to_render_iter { - // TODO get scale from the rendersurface when supporting HiDPI - let frame = self - .backend_data - .pointer_image - .get_image(1 /*scale*/, self.clock.now().try_into().unwrap()); - let primary_gpu = self.backend_data.primary_gpu; - let mut renderer = self - .backend_data - .gpus - .renderer::(&primary_gpu, &surface.borrow().render_node) - .unwrap(); - let pointer_images = &mut self.backend_data.pointer_images; - let pointer_image = pointer_images - .iter() - .find_map(|(image, texture)| { - if image == &frame { - Some(texture.clone()) - } else { - None - } - }) - .unwrap_or_else(|| { - let texture = TextureBuffer::from_memory( - &mut renderer, - &frame.pixels_rgba, - (frame.width as i32, frame.height as i32), - false, - 1, - Transform::Normal, - None, - ) - .expect("Failed to import cursor bitmap"); - pointer_images.push((frame, texture.clone())); - texture - }); - - let output = if let Some(output) = self.space.outputs().find(|o| { - o.user_data().get::() - == Some(&UdevOutputId { - device_id: surface.borrow().device_id, - crtc, - }) - }) { - output.clone() - } else { - // somehow we got called with an invalid output - continue; - }; - - let result = render_surface( - &mut surface.borrow_mut(), - &mut renderer, - &self.space, - &output, - self.seat.input_method().unwrap(), - self.pointer_location, - &pointer_image, - &mut self.backend_data.pointer_element, - &self.dnd_icon, - &mut self.cursor_status.lock().unwrap(), - &self.clock, - &self.log, - ); - let reschedule = match &result { - Ok(has_rendered) => !has_rendered, - Err(err) => { - slog::warn!(self.log, "Error during rendering: {:?}", err); - match err { - SwapBuffersError::AlreadySwapped => false, - SwapBuffersError::TemporaryFailure(err) => !matches!( - err.downcast_ref::(), - Some(&DrmError::DeviceInactive) - | Some(&DrmError::Access { - source: drm::SystemError::PermissionDenied, - .. - }) - ), - SwapBuffersError::ContextLost(err) => { - panic!("Rendering loop lost: {}", err) - } - } - } - }; - - if reschedule { - let output_refresh = match output.current_mode() { - Some(mode) => mode.refresh, - None => return, - }; - // If reschedule is true we either hit a temporary failure or more likely rendering - // did not cause any damage on the output. In this case we just re-schedule a repaint - // after approx. one frame to re-test for damage. - let reschedule_duration = - Duration::from_millis((1_000_000f32 / output_refresh as f32) as u64); - slog::trace!( - self.log, - "reschedule repaint timer with delay {:?} on {:?}", - reschedule_duration, - crtc, - ); - let timer = Timer::from_duration(reschedule_duration); - self.handle - .insert_source(timer, move |_, _, data| { - data.state.render(dev_id, Some(crtc)); - TimeoutAction::Drop - }) - .expect("failed to schedule frame timer"); - } - } - } -} - -fn render_surface<'a>( - surface: &'a mut SurfaceData, - renderer: &mut UdevRenderer<'a>, - space: &Space, - output: &Output, - input_method: &InputMethodHandle, - pointer_location: Point, - pointer_image: &TextureBuffer, - pointer_element: &mut PointerElement, - dnd_icon: &Option, - cursor_status: &mut CursorImageStatus, - clock: &Clock, - logger: &slog::Logger, -) -> Result { - let output_geometry = space.output_geometry(output).unwrap(); - let scale = Scale::from(output.current_scale().fractional_scale()); - - let (dmabuf, age) = surface.surface.next_buffer()?; - renderer.bind(dmabuf)?; - - let mut elements: Vec> = Vec::new(); - // draw input method surface if any - let rectangle = input_method.coordinates(); - let position = Point::from(( - rectangle.loc.x + rectangle.size.w, - rectangle.loc.y + rectangle.size.h, - )); - input_method.with_surface(|surface| { - elements.extend(AsRenderElements::>::render_elements( - &SurfaceTree::from_surface(surface), - renderer, - position.to_physical_precise_round(scale), - scale, - )); - }); - - if output_geometry.to_f64().contains(pointer_location) { - let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = cursor_status { - compositor::with_states(surface, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .hotspot - }) - } else { - (0, 0).into() - }; - let cursor_pos = pointer_location - output_geometry.loc.to_f64() - cursor_hotspot.to_f64(); - let cursor_pos_scaled = cursor_pos.to_physical(scale).to_i32_round(); - - // set cursor - pointer_element.set_texture(pointer_image.clone()); - - // draw the cursor as relevant - { - // reset the cursor if the surface is no longer alive - let mut reset = false; - if let CursorImageStatus::Surface(ref surface) = *cursor_status { - reset = !surface.alive(); - } - if reset { - *cursor_status = CursorImageStatus::Default; - } - - pointer_element.set_status(cursor_status.clone()); - } - - elements.extend(pointer_element.render_elements(renderer, cursor_pos_scaled, scale)); - - // draw the dnd icon if applicable - { - if let Some(wl_surface) = dnd_icon.as_ref() { - if wl_surface.alive() { - elements.extend(AsRenderElements::>::render_elements( - &SurfaceTree::from_surface(wl_surface), - renderer, - cursor_pos_scaled, - scale, - )); - } - } - } - } - // and draw to our buffer - let (rendered, states) = render_output( - output, - space, - &elements, - renderer, - &mut surface.damage_tracked_renderer, - age.into(), - logger, - ) - .map(|(damage, states)| (damage.is_some(), states)) - .map_err(|err| match err { - DamageTrackedRendererError::Rendering(err) => SwapBuffersError::from(err), - _ => unreachable!(), - })?; - - post_repaint(output, &states, space, clock.now()); - - if rendered { - let output_presentation_feedback = take_presentation_feedback(output, space, &states); - surface - .surface - .queue_buffer(Some(output_presentation_feedback)) - .map_err(Into::::into)?; - } - - Ok(rendered) -} - -fn schedule_initial_render( - gpus: &mut GpuManager>, - surface: Rc>, - evt_handle: &LoopHandle<'static, CalloopData>, - logger: ::slog::Logger, -) { - let node = surface.borrow().render_node; - let result = { - let mut renderer = gpus.renderer::(&node, &node).unwrap(); - let mut surface = surface.borrow_mut(); - initial_render(&mut surface.surface, &mut renderer) - }; - if let Err(err) = result { - match err { - SwapBuffersError::AlreadySwapped => {} - SwapBuffersError::TemporaryFailure(err) => { - // TODO dont reschedule after 3(?) retries - slog::warn!(logger, "Failed to submit page_flip: {}", err); - let handle = evt_handle.clone(); - evt_handle.insert_idle(move |data| { - schedule_initial_render( - &mut data.state.backend_data.gpus, - surface, - &handle, - logger, - ) - }); - } - SwapBuffersError::ContextLost(err) => panic!("Rendering loop lost: {}", err), - } - } -} - -fn initial_render( - surface: &mut RenderSurface, - renderer: &mut UdevRenderer<'_>, -) -> Result<(), SwapBuffersError> { - let (dmabuf, _) = surface.next_buffer()?; - renderer.bind(dmabuf)?; - // Does not matter if we render an empty frame - let mut frame = renderer - .render((1, 1).into(), Transform::Normal) - .map_err(Into::::into)?; - - frame.clear(CLEAR_COLOR, &[Rectangle::from_loc_and_size((0, 0), (1, 1))])?; - frame.finish().map_err(Into::::into)?; - surface.queue_buffer(None)?; - surface.reset_buffers(); - Ok(()) -} diff --git a/src/compositor/backend/winit.rs b/src/compositor/backend/winit.rs deleted file mode 100644 index 02fe4f0..0000000 --- a/src/compositor/backend/winit.rs +++ /dev/null @@ -1,346 +0,0 @@ -use std::{ - ffi::OsString, - sync::{atomic::Ordering, Mutex}, - time::Duration, -}; - -use slog::Logger; - -use smithay::{ - backend::{ - allocator::dmabuf::Dmabuf, - renderer::{ - damage::{DamageTrackedRenderer, DamageTrackedRendererError}, - element::AsRenderElements, - gles2::{Gles2Renderer, Gles2Texture}, - ImportDma, ImportEgl, - }, - winit::{self, WinitEvent, WinitGraphicsBackend}, - SwapBuffersError, - }, - delegate_dmabuf, - input::pointer::{CursorImageAttributes, CursorImageStatus}, - output::{Mode, Output, PhysicalProperties, Subpixel}, - reexports::{ - calloop::EventLoop, - wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, - wayland_server::{protocol::wl_surface, Display}, - }, - utils::{IsAlive, Point, Scale, Transform}, - wayland::{ - compositor, - dmabuf::{DmabufGlobal, DmabufHandler, DmabufState, ImportError}, - input_method::InputMethodSeat, - }, -}; - -use crate::compositor::{ - drawing::PointerElement, - render::{render_output, CustomRenderElements}, - shell, - state::{post_repaint, take_presentation_feedback, CalloopData, Navda}, -}; - -use super::Backend; - -pub const OUTPUT_NAME: &str = "winit"; - -pub struct WinitData { - backend: WinitGraphicsBackend, - damage_tracked_renderer: DamageTrackedRenderer, - dmabuf_state: Option<(DmabufState, DmabufGlobal)>, - full_redraw: u8, -} - -impl DmabufHandler for Navda { - fn dmabuf_state(&mut self) -> &mut DmabufState { - &mut self.backend_data.dmabuf_state.as_mut().unwrap().0 - } - - fn dmabuf_imported( - &mut self, - _global: &DmabufGlobal, - dmabuf: Dmabuf, - ) -> Result<(), ImportError> { - self.backend_data - .backend - .renderer() - .import_dmabuf(&dmabuf, None) - .map(|_| ()) - .map_err(|_| ImportError::Failed) - } -} -delegate_dmabuf!(Navda); - -impl Backend for WinitData { - fn seat_name(&self) -> String { - String::from("winit") - } - fn reset_buffers(&mut self, _output: &Output) { - self.full_redraw = 4; - } - fn early_import(&mut self, _surface: &wl_surface::WlSurface) {} -} - -pub fn run_winit(log: Logger) { - let mut event_loop = EventLoop::try_new().unwrap(); - let mut display = Display::new().unwrap(); - - let (mut backend, mut winit) = match winit::init::(log.clone()) { - Ok(ret) => ret, - Err(err) => { - slog::crit!(log, "Failed to initialize Winit backend: {}", err); - return; - } - }; - let size = backend.window_size().physical_size; - - let mode = Mode { - size, - refresh: 60_000, - }; - let output = Output::new( - OUTPUT_NAME.to_string(), - PhysicalProperties { - size: (0, 0).into(), - subpixel: Subpixel::Unknown, - make: "Smithay".into(), - model: "Winit".into(), - }, - log.clone(), - ); - let _global = output.create_global::>(&display.handle()); - output.change_current_state( - Some(mode), - Some(Transform::Flipped180), - None, - Some((0, 0).into()), - ); - output.set_preferred(mode); - - let data = { - let dmabuf_state = if backend - .renderer() - .bind_wl_display(&display.handle()) - .is_ok() - { - slog::info!(log, "EGL hardware-acceleration enabled"); - let dmabuf_formats = backend - .renderer() - .dmabuf_formats() - .cloned() - .collect::>(); - let mut state = DmabufState::new(); - let global = state.create_global::, _>( - &display.handle(), - dmabuf_formats, - log.clone(), - ); - Some((state, global)) - } else { - None - }; - - let damage_tracked_renderer = DamageTrackedRenderer::from_output(&output); - - WinitData { - backend, - damage_tracked_renderer, - dmabuf_state, - full_redraw: 0, - } - }; - let mut state = Navda::new(&mut display, event_loop.handle(), data, log.clone()); - state.space.map_output(&output, (0, 0)); - - if let Err(e) = state.xwayland.start( - state.handle.clone(), - None, - std::iter::empty::<(OsString, OsString)>(), - |_| {}, - ) { - slog::error!(log, "Failed to start XWayland: {}", e); - } - - slog::info!(log, "Initialization completed, starting the main loop."); - - let mut pointer_element = PointerElement::::default(); - - while state.running.load(Ordering::SeqCst) { - if winit - .dispatch_new_events(|event| match event { - WinitEvent::Resized { size, .. } => { - // We only have one output - let output = state.space.outputs().next().unwrap().clone(); - state.space.map_output(&output, (0, 0)); - let mode = Mode { - size, - refresh: 60_000, - }; - output.change_current_state(Some(mode), None, None, None); - output.set_preferred(mode); - shell::fixup_positions(&mut state.space); - } - WinitEvent::Input(event) => { - state.process_input_event_windowed(&display.handle(), event, OUTPUT_NAME) - } - _ => (), - }) - .is_err() - { - state.running.store(false, Ordering::SeqCst); - break; - } - - // drawing logic - { - let backend = &mut state.backend_data.backend; - - let mut cursor_guard = state.cursor_status.lock().unwrap(); - - // draw the cursor as relevant - // reset the cursor if the surface is no longer alive - let mut reset = false; - if let CursorImageStatus::Surface(ref surface) = *cursor_guard { - reset = !surface.alive(); - } - if reset { - *cursor_guard = CursorImageStatus::Default; - } - let cursor_visible = !matches!(*cursor_guard, CursorImageStatus::Surface(_)); - - pointer_element.set_status(cursor_guard.clone()); - - let full_redraw = &mut state.backend_data.full_redraw; - *full_redraw = full_redraw.saturating_sub(1); - let space = &mut state.space; - let damage_tracked_renderer = &mut state.backend_data.damage_tracked_renderer; - - let input_method = state.seat.input_method().unwrap(); - let dnd_icon = state.dnd_icon.as_ref(); - - let scale = Scale::from(output.current_scale().fractional_scale()); - let cursor_hotspot = if let CursorImageStatus::Surface(ref surface) = *cursor_guard { - compositor::with_states(surface, |states| { - states - .data_map - .get::>() - .unwrap() - .lock() - .unwrap() - .hotspot - }) - } else { - (0, 0).into() - }; - let cursor_pos = state.pointer_location - cursor_hotspot.to_f64(); - let cursor_pos_scaled = cursor_pos.to_physical(scale).to_i32_round(); - - let render_res = backend.bind().and_then(|_| { - let age = if *full_redraw > 0 { - 0 - } else { - backend.buffer_age().unwrap_or(0) - }; - - let renderer = backend.renderer(); - - let mut elements = Vec::>::new(); - - elements.extend(pointer_element.render_elements( - renderer, - cursor_pos_scaled, - scale, - )); - - // draw input method surface if any - let rectangle = input_method.coordinates(); - let position = Point::from(( - rectangle.loc.x + rectangle.size.w, - rectangle.loc.y + rectangle.size.h, - )); - input_method.with_surface(|surface| { - elements.extend(AsRenderElements::::render_elements( - &smithay::desktop::space::SurfaceTree::from_surface(surface), - renderer, - position.to_physical_precise_round(scale), - scale, - )); - }); - - // draw the dnd icon if any - if let Some(surface) = dnd_icon { - if surface.alive() { - elements.extend(AsRenderElements::::render_elements( - &smithay::desktop::space::SurfaceTree::from_surface(surface), - renderer, - cursor_pos_scaled, - scale, - )); - } - } - - render_output( - &output, - space, - &elements, - renderer, - damage_tracked_renderer, - age, - &log, - ) - .map_err(|err| match err { - DamageTrackedRendererError::Rendering(err) => err.into(), - _ => unreachable!(), - }) - }); - - match render_res { - Ok((damage, states)) => { - let has_rendered = damage.is_some(); - if let Some(damage) = damage { - if let Err(err) = backend.submit(Some(&*damage)) { - slog::warn!(log, "Failed to submit buffer: {}", err); - } - } - backend.window().set_cursor_visible(cursor_visible); - - // Send frame events so that client start drawing their next frame - let time = state.clock.now(); - post_repaint(&output, &states, &state.space, time); - - if has_rendered { - let mut output_presentation_feedback = - take_presentation_feedback(&output, &state.space, &states); - output_presentation_feedback.presented( - time, - output - .current_mode() - .map(|mode| mode.refresh as u32) - .unwrap_or_default(), - 0, - wp_presentation_feedback::Kind::Vsync, - ) - } - } - Err(SwapBuffersError::ContextLost(err)) => { - slog::error!(log, "Critical Rendering Error: {}", err); - state.running.store(false, Ordering::SeqCst); - } - Err(err) => slog::warn!(log, "Rendering error: {}", err), - } - } - - let mut calloop_data = CalloopData { state, display }; - let result = event_loop.dispatch(Some(Duration::from_millis(1)), &mut calloop_data); - CalloopData { state, display } = calloop_data; - - if result.is_err() { - state.running.store(false, Ordering::SeqCst); - } else { - state.space.refresh(); - state.popups.cleanup(); - display.flush_clients().unwrap(); - } - } -} diff --git a/src/compositor/components/cursor.rs b/src/compositor/components/cursor.rs deleted file mode 100644 index 7b37172..0000000 --- a/src/compositor/components/cursor.rs +++ /dev/null @@ -1,94 +0,0 @@ -use std::{io::Read, time::Duration}; - -use xcursor::{ - parser::{parse_xcursor, Image}, - CursorTheme, -}; - -static FALLBACK_CURSOR_DATA: &[u8] = include_bytes!("../../../resources/avdanos-cursor-64.rgba"); - -pub struct Cursor { - icons: Vec, - size: u32, -} - -impl Cursor { - pub fn load(log: &::slog::Logger) -> Cursor { - // TODO: Re-add the following, - // for now, we're only using the AvdanOS cursor. - // let name = std::env::var("XCURSOR_THEME") - // .ok() - // .unwrap_or_else(|| "default".into()); - let size = /* std::env::var("XCURSOR_SIZE") - .ok() - .and_then(|s| s.parse().ok()) - .unwrap_or(*/ 64; //); - - // let theme = CursorTheme::load(&name); - let icons = /* load_icon(&theme) */ - // .map_err(|err| slog::warn!(log, "Unable to load xcursor: {}, using fallback cursor", err)) - // .unwrap_or_else(|_| { - vec![Image { - size: 64, - width: 64, - height: 64, - xhot: 5, - yhot: 6, - delay: 1, - pixels_rgba: Vec::from(FALLBACK_CURSOR_DATA), - pixels_argb: vec![], //unused - }]; - // }); - - Cursor { icons, size } - } - - pub fn get_image(&self, scale: u32, time: Duration) -> Image { - let size = self.size * scale; - frame(time.as_millis() as u32, size, &self.icons) - } -} - -fn nearest_images(size: u32, images: &[Image]) -> impl Iterator { - // Follow the nominal size of the cursor to choose the nearest - let nearest_image = images - .iter() - .min_by_key(|image| (size as i32 - image.size as i32).abs()) - .unwrap(); - - images.iter().filter(move |image| { - image.width == nearest_image.width && image.height == nearest_image.height - }) -} - -fn frame(mut millis: u32, size: u32, images: &[Image]) -> Image { - let total = nearest_images(size, images).fold(0, |acc, image| acc + image.delay); - millis %= total; - - for img in nearest_images(size, images) { - if millis < img.delay { - return img.clone(); - } - millis -= img.delay; - } - - unreachable!() -} - -#[derive(thiserror::Error, Debug)] -enum Error { - #[error("Theme has no default cursor")] - NoDefaultCursor, - #[error("Error opening xcursor file: {0}")] - File(#[from] std::io::Error), - #[error("Failed to parse XCursor file")] - Parse, -} - -fn load_icon(theme: &CursorTheme) -> Result, Error> { - let icon_path = theme.load_icon("default").ok_or(Error::NoDefaultCursor)?; - let mut cursor_file = std::fs::File::open(&icon_path)?; - let mut cursor_data = Vec::new(); - cursor_file.read_to_end(&mut cursor_data)?; - parse_xcursor(&cursor_data).ok_or(Error::Parse) -} diff --git a/src/compositor/components/mod.rs b/src/compositor/components/mod.rs deleted file mode 100644 index 6ef2abd..0000000 --- a/src/compositor/components/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -//! -//! Custom components. -//! -mod cursor; -pub use cursor::Cursor; diff --git a/src/compositor/drawing.rs b/src/compositor/drawing.rs deleted file mode 100644 index 3bb9ce7..0000000 --- a/src/compositor/drawing.rs +++ /dev/null @@ -1,88 +0,0 @@ -use smithay::{ - backend::renderer::{ - element::{ - surface::WaylandSurfaceRenderElement, - texture::{TextureBuffer, TextureRenderElement}, - AsRenderElements, - }, - ImportAll, Renderer, Texture, - }, - input::pointer::CursorImageStatus, - render_elements, - utils::{Physical, Point, Scale}, -}; - -pub static CLEAR_COLOR: [f32; 4] = [0.8, 0.8, 0.9, 1.0]; -pub struct PointerElement { - texture: Option>, - status: CursorImageStatus, -} - -impl Default for PointerElement { - fn default() -> Self { - Self { - texture: Default::default(), - status: CursorImageStatus::Default, - } - } -} - -impl PointerElement { - pub fn set_status(&mut self, status: CursorImageStatus) { - self.status = status; - } - - pub fn set_texture(&mut self, texture: TextureBuffer) { - self.texture = Some(texture); - } -} - -render_elements! { - pub PointerRenderElement where - R: ImportAll; - Surface=WaylandSurfaceRenderElement, - Texture=TextureRenderElement<::TextureId>, -} - -impl AsRenderElements for PointerElement -where - R: Renderer + ImportAll, -{ - type RenderElement = PointerRenderElement; - fn render_elements( - &self, - renderer: &mut R, - location: Point, - scale: Scale, - ) -> Vec - where - E: From>, - { - match &self.status { - CursorImageStatus::Hidden => vec![], - CursorImageStatus::Default => { - if let Some(texture) = self.texture.as_ref() { - vec![PointerRenderElement::::from( - TextureRenderElement::from_texture_buffer( - location.to_f64(), - texture, - None, - None, - None, - ), - ) - .into()] - } else { - vec![] - } - } - CursorImageStatus::Surface(surface) => { - let elements: Vec> = - smithay::backend::renderer::element::surface::render_elements_from_surface_tree( - renderer, surface, location, scale, None, - ); - elements.into_iter().map(E::from).collect() - } - } - } -} diff --git a/src/compositor/focus.rs b/src/compositor/focus.rs deleted file mode 100644 index c892a91..0000000 --- a/src/compositor/focus.rs +++ /dev/null @@ -1,204 +0,0 @@ -use smithay::{ - backend::input::KeyState, - desktop::{LayerSurface, PopupKind}, - input::{ - keyboard::{self, KeyboardTarget, KeysymHandle}, - pointer::{self, PointerTarget}, - Seat, - }, - reexports::{ - wayland_server::{backend::ObjectId, protocol::wl_surface::WlSurface, Resource}, - winit::event::ModifiersState, - }, - utils::{IsAlive, Serial}, - wayland::seat::WaylandFocus, -}; - -use super::{backend::Backend, shell::AvWindow, state::Navda}; - -#[derive(Debug, Clone, PartialEq)] -pub enum FocusTarget { - Window(AvWindow), - LayerSurface(LayerSurface), - Popup(PopupKind), -} - -impl IsAlive for FocusTarget { - fn alive(&self) -> bool { - match self { - Self::Window(w) => w.alive(), - Self::LayerSurface(l) => l.alive(), - Self::Popup(p) => p.alive(), - } - } -} - -// TODO(@Sammy99jsp) sort this out please with macros.... -impl PointerTarget> for FocusTarget { - fn enter( - &self, - seat: &Seat>, - data: &mut Navda, - event: &pointer::MotionEvent, - ) { - match self { - FocusTarget::Window(w) => PointerTarget::enter(w, seat, data, event), - FocusTarget::LayerSurface(l) => PointerTarget::enter(l, seat, data, event), - FocusTarget::Popup(p) => PointerTarget::enter(p.wl_surface(), seat, data, event), - } - } - fn motion( - &self, - seat: &Seat>, - data: &mut Navda, - event: &pointer::MotionEvent, - ) { - match self { - FocusTarget::Window(w) => PointerTarget::motion(w, seat, data, event), - FocusTarget::LayerSurface(l) => PointerTarget::motion(l, seat, data, event), - FocusTarget::Popup(p) => PointerTarget::motion(p.wl_surface(), seat, data, event), - } - } - fn button( - &self, - seat: &Seat>, - data: &mut Navda, - event: &pointer::ButtonEvent, - ) { - match self { - FocusTarget::Window(w) => PointerTarget::button(w, seat, data, event), - FocusTarget::LayerSurface(l) => PointerTarget::button(l, seat, data, event), - FocusTarget::Popup(p) => PointerTarget::button(p.wl_surface(), seat, data, event), - } - } - fn axis(&self, seat: &Seat>, data: &mut Navda, frame: pointer::AxisFrame) { - match self { - FocusTarget::Window(w) => PointerTarget::axis(w, seat, data, frame), - FocusTarget::LayerSurface(l) => PointerTarget::axis(l, seat, data, frame), - FocusTarget::Popup(p) => PointerTarget::axis(p.wl_surface(), seat, data, frame), - } - } - fn leave(&self, seat: &Seat>, data: &mut Navda, serial: Serial, time: u32) { - match self { - FocusTarget::Window(w) => PointerTarget::leave(w, seat, data, serial, time), - FocusTarget::LayerSurface(l) => PointerTarget::leave(l, seat, data, serial, time), - FocusTarget::Popup(p) => PointerTarget::leave(p.wl_surface(), seat, data, serial, time), - } - } - - fn relative_motion( - &self, - seat: &Seat>, - data: &mut Navda, - event: &pointer::RelativeMotionEvent, - ) { - match self { - Self::Window(l) => PointerTarget::relative_motion(l, seat, data, event), - Self::LayerSurface(l) => PointerTarget::relative_motion(l, seat, data, event), - Self::Popup(p) => PointerTarget::relative_motion(p.wl_surface(), seat, data, event), - } - } -} - -impl KeyboardTarget> for FocusTarget { - fn enter( - &self, - seat: &Seat>, - data: &mut Navda, - keys: Vec>, - serial: Serial, - ) { - match self { - FocusTarget::Window(w) => KeyboardTarget::enter(w, seat, data, keys, serial), - FocusTarget::LayerSurface(l) => KeyboardTarget::enter(l, seat, data, keys, serial), - FocusTarget::Popup(p) => { - KeyboardTarget::enter(p.wl_surface(), seat, data, keys, serial) - } - } - } - fn leave(&self, seat: &Seat>, data: &mut Navda, serial: Serial) { - match self { - FocusTarget::Window(w) => KeyboardTarget::leave(w, seat, data, serial), - FocusTarget::LayerSurface(l) => KeyboardTarget::leave(l, seat, data, serial), - FocusTarget::Popup(p) => KeyboardTarget::leave(p.wl_surface(), seat, data, serial), - } - } - fn key( - &self, - seat: &Seat>, - data: &mut Navda, - key: KeysymHandle<'_>, - state: KeyState, - serial: Serial, - time: u32, - ) { - match self { - FocusTarget::Window(w) => KeyboardTarget::key(w, seat, data, key, state, serial, time), - FocusTarget::LayerSurface(l) => { - KeyboardTarget::key(l, seat, data, key, state, serial, time) - } - FocusTarget::Popup(p) => { - KeyboardTarget::key(p.wl_surface(), seat, data, key, state, serial, time) - } - } - } - fn modifiers( - &self, - seat: &Seat>, - data: &mut Navda, - modifiers: keyboard::ModifiersState, - serial: Serial, - ) { - match self { - FocusTarget::Window(w) => KeyboardTarget::modifiers(w, seat, data, modifiers, serial), - FocusTarget::LayerSurface(l) => { - KeyboardTarget::modifiers(l, seat, data, modifiers, serial) - } - FocusTarget::Popup(p) => { - KeyboardTarget::modifiers(p.wl_surface(), seat, data, modifiers, serial) - } - } - } -} - -impl WaylandFocus for FocusTarget { - fn wl_surface(&self) -> Option { - match self { - FocusTarget::Window(w) => w.wl_surface(), - FocusTarget::LayerSurface(l) => Some(l.wl_surface().clone()), - FocusTarget::Popup(p) => Some(p.wl_surface().clone()), - } - } - fn same_client_as(&self, object_id: &ObjectId) -> bool { - match self { - FocusTarget::Window(AvWindow::Wayland(w)) => w.same_client_as(object_id), - FocusTarget::Window(AvWindow::X11(w)) => w.same_client_as(object_id), - FocusTarget::LayerSurface(l) => l.wl_surface().id().same_client_as(object_id), - FocusTarget::Popup(p) => p.wl_surface().id().same_client_as(object_id), - } - } -} - -impl From for WlSurface { - fn from(target: FocusTarget) -> Self { - target.wl_surface().unwrap() - } -} - -impl From for FocusTarget { - fn from(w: AvWindow) -> Self { - FocusTarget::Window(w) - } -} - -impl From for FocusTarget { - fn from(l: LayerSurface) -> Self { - FocusTarget::LayerSurface(l) - } -} - -impl From for FocusTarget { - fn from(p: PopupKind) -> Self { - FocusTarget::Popup(p) - } -} diff --git a/src/compositor/handlers/input.rs b/src/compositor/handlers/input.rs deleted file mode 100644 index 2638e6f..0000000 --- a/src/compositor/handlers/input.rs +++ /dev/null @@ -1,126 +0,0 @@ -//! -//! Collection of input handlers/Smithay delegations: -//! * [Data Device](https://wayland.freedesktop.org/docs/html/ch04.html#:~:text=Data%20devices%20glue%20data%20sources%20and%20offers%20together) Handler -//! * [Drag-and-drop](https://wayland.freedesktop.org/docs/html/ch04.html#:~:text=Drag%20and%20Drop) -//! * [Primary Selection](https://wiki.archlinux.org/title/Clipboard#:~:text=PRIMARY) Handler -//! * [Seat](https://wayland-book.com/seat.html) Handler -//! * [Tablet Manager (Tablet Protocol)](https://wayland.app/protocols/tablet-unstable-v2) -//! * [Text Input Manager (Text Input Protocol)](https://wayland.app/protocols/text-input-unstable-v3) -//! * [Input Method Manager (Input Method Protocol)](https://wayland.app/protocols/input-method-unstable-v1) -//! * [Keyboard Shortcut Inhibit State (Keyboard Shortcut Inhibit Protocol)](https://wayland.app/protocols/keyboard-shortcuts-inhibit-unstable-v1) -//! -//! - -use std::os::unix::prelude::OwnedFd; - -use smithay::{ - delegate_data_device, delegate_input_method_manager, delegate_keyboard_shortcuts_inhibit, - delegate_primary_selection, delegate_seat, delegate_tablet_manager, - delegate_text_input_manager, delegate_virtual_keyboard_manager, - input::{pointer::CursorImageStatus, Seat, SeatHandler, SeatState}, - reexports::wayland_server::{ - protocol::{wl_data_source::WlDataSource, wl_surface::WlSurface}, - Resource, - }, - wayland::{ - data_device::{ - set_data_device_focus, ClientDndGrabHandler, DataDeviceHandler, DataDeviceState, - ServerDndGrabHandler, - }, - keyboard_shortcuts_inhibit::{ - KeyboardShortcutsInhibitHandler, KeyboardShortcutsInhibitState, - KeyboardShortcutsInhibitor, - }, - primary_selection::{set_primary_focus, PrimarySelectionHandler, PrimarySelectionState}, - seat::WaylandFocus, - }, -}; - -use crate::compositor::{backend::Backend, focus::FocusTarget, state::Navda}; - -impl SeatHandler for Navda { - type KeyboardFocus = FocusTarget; - type PointerFocus = FocusTarget; - - fn seat_state(&mut self) -> &mut SeatState { - &mut self.seat_state - } - - fn focus_changed(&mut self, seat: &Seat, target: Option<&FocusTarget>) { - let dh = &self.display_handle; - - let focus = target - .and_then(WaylandFocus::wl_surface) - .and_then(|s| dh.get_client(s.id()).ok()); - set_data_device_focus(dh, seat, focus.clone()); - set_primary_focus(dh, seat, focus); - } - fn cursor_image(&mut self, _seat: &Seat, image: CursorImageStatus) { - *self.cursor_status.lock().unwrap() = image; - } -} -delegate_seat!(@ Navda); - -// Drag-and-drop -impl DataDeviceHandler for Navda { - fn data_device_state(&self) -> &DataDeviceState { - &self.data_device_state - } - fn send_selection(&mut self, _mime_type: String, _fd: OwnedFd) { - todo!("Navda doesn't do server-side selections"); - } -} -impl ClientDndGrabHandler for Navda { - fn started( - &mut self, - _source: Option, - icon: Option, - _seat: Seat, - ) { - self.dnd_icon = icon; - } - - fn dropped(&mut self, _seat: Seat) { - self.dnd_icon = None; - } -} -impl ServerDndGrabHandler for Navda { - fn send(&mut self, _mime_type: String, _fd: OwnedFd) { - todo!("Navda doesn't do server-side grabs"); - } -} - -delegate_data_device!(@ Navda); - -// Primary Selection - -impl PrimarySelectionHandler for Navda { - fn primary_selection_state(&self) -> &PrimarySelectionState { - &self.primary_selection_state - } -} - -delegate_primary_selection!(@ Navda); - -delegate_tablet_manager!(@ Navda); - -delegate_text_input_manager!(@ Navda); - -delegate_input_method_manager!(@ Navda); - -impl KeyboardShortcutsInhibitHandler for Navda { - fn keyboard_shortcuts_inhibit_state(&mut self) -> &mut KeyboardShortcutsInhibitState { - &mut self.keyboard_shortcuts_inhibit_state - } - - fn new_inhibitor(&mut self, inhibitor: KeyboardShortcutsInhibitor) { - // Just grant the wish for everyone - inhibitor.activate(); - } -} - -delegate_keyboard_shortcuts_inhibit!(@ Navda); - -delegate_virtual_keyboard_manager!(@ Navda); - -// TODO(Sammy99jsp) delegate_relative_pointer. diff --git a/src/compositor/handlers/mod.rs b/src/compositor/handlers/mod.rs deleted file mode 100644 index 39fa28d..0000000 --- a/src/compositor/handlers/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -mod input; -mod output; -mod xdg; diff --git a/src/compositor/handlers/output.rs b/src/compositor/handlers/output.rs deleted file mode 100644 index 4c78e83..0000000 --- a/src/compositor/handlers/output.rs +++ /dev/null @@ -1,71 +0,0 @@ -//! -//! Collection of handlers for: -//! * [Fractional Scaling Protocol](https://wayland.app/protocols/fractional-scale-v1) -//! * [Presentation Time Protocol](https://wayland.app/protocols/presentation-time) -//! - -use smithay::{ - delegate_fractional_scale, delegate_output, delegate_presentation, - desktop::utils::surface_primary_scanout_output, - wayland::{ - compositor::{get_parent, with_states}, - fractional_scale::{with_fractional_scale, FractionScaleHandler}, - }, -}; - -use crate::compositor::{backend::Backend, state::Navda}; - -impl FractionScaleHandler for Navda { - fn new_fractional_scale( - &mut self, - surface: smithay::reexports::wayland_server::protocol::wl_surface::WlSurface, - ) { - // From Smithay/anvil: - // " Here we can set the initial fractional scale - // - // First we look if the surface already has a primary scan-out output, if not - // we test if the surface is a subsurface and try to use the primary scan-out output - // of the root surface. If the root also has no primary scan-out output we just try - // to use the first output of the toplevel. - // If the surface is the root we also try to use the first output of the toplevel. - // - // If all the above tests do not lead to a output we just use the first output - // of the space (which in case of anvil will also be the output a toplevel will - // initially be placed on) " - let mut root = surface.clone(); - while let Some(parent) = get_parent(&root) { - root = parent; - } - - with_states(&surface, |states| { - let primary_scanout_output = surface_primary_scanout_output(&surface, states) - .or_else(|| { - if root != surface { - with_states(&root, |states| { - surface_primary_scanout_output(&root, states).or_else(|| { - self.window_for_surface(&root).and_then(|window| { - self.space.outputs_for_element(&window).first().cloned() - }) - }) - }) - } else { - self.window_for_surface(&root).and_then(|window| { - self.space.outputs_for_element(&window).first().cloned() - }) - } - }) - .or_else(|| self.space.outputs().next().cloned()); - if let Some(output) = primary_scanout_output { - with_fractional_scale(states, |fractional_scale| { - fractional_scale.set_preferred_scale(output.current_scale().fractional_scale()); - }); - } - }); - } -} - -delegate_fractional_scale!(@ Navda); - -delegate_presentation!(@ Navda); - -delegate_output!(@ Navda); diff --git a/src/compositor/handlers/xdg.rs b/src/compositor/handlers/xdg.rs deleted file mode 100644 index 1a3a942..0000000 --- a/src/compositor/handlers/xdg.rs +++ /dev/null @@ -1,3 +0,0 @@ -//! -//! See `crate::compositor::shell::xdg` -//! diff --git a/src/compositor/input/mod.rs b/src/compositor/input/mod.rs deleted file mode 100644 index 60662ba..0000000 --- a/src/compositor/input/mod.rs +++ /dev/null @@ -1,922 +0,0 @@ -use std::{iter, process::Command, sync::atomic}; - -use smithay::{ - backend::{ - input::{ - self, AbsolutePositionEvent, Axis, AxisSource, Device, DeviceCapability, Event, - InputBackend, InputEvent, KeyState, KeyboardKeyEvent, PointerAxisEvent, - PointerButtonEvent, PointerMotionEvent, ProximityState, TabletToolEvent, - TabletToolTipEvent, TabletToolTipState, - }, - session::Session, - }, - desktop::{layer_map_for_output, WindowSurfaceType}, - input::{ - keyboard::{FilterResult, ModifiersState}, - pointer::{AxisFrame, ButtonEvent, MotionEvent}, - }, - output::{self, Output}, - reexports::wayland_server::{protocol::wl_pointer, DisplayHandle}, - utils::{Logical, Point, Serial, Transform, SERIAL_COUNTER as SCOUNTER}, - wayland::{ - compositor::with_states, - input_method::InputMethodSeat, - keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitorSeat, - seat::WaylandFocus, - shell::wlr_layer::{KeyboardInteractivity, Layer as WlrLayer, LayerSurfaceCachedState}, - tablet_manager::{TabletDescriptor, TabletSeatTrait}, - }, -}; -use xkbcommon::xkb::{ - self, Keysym, - keysyms::{ - KEY_BackSpace, KEY_q, KEY_XF86Switch_VT_1, KEY_XF86Switch_VT_12, KEY_Return, KEY_1, KEY_9, KEY_M, KEY_P, KEY_W, KEY_R - } -}; - -use super::{ - backend::{Backend, UdevData}, - focus::FocusTarget, - shell::{self, AvWindow, FullscreenSurface}, - state::Navda, -}; - -impl Navda { - fn process_common_key_action(&mut self, action: KeyAction) { - match action { - KeyAction::None => (), - - KeyAction::Quit => { - slog::info!(self.log, "Quitting."); - self.running.store(false, atomic::Ordering::SeqCst); - } - - KeyAction::Run(cmd) => { - slog::info!(self.log, "Starting program"; "cmd" => cmd.clone()); - - if let Err(e) = Command::new(&cmd) - .envs( - iter::once(("WAYLAND_DISPLAY", self.socket_name.clone())) - .chain(self.x_display.map(|v| ("DISPLAY", format!(":{}", v)))), - ) - .spawn() - { - slog::error!(self.log, - "Failed to start program"; - "cmd" => cmd, - "err" => format!("{:?}", e) - ); - } - } - _ => unreachable!( - "Common key action handler encountered backend specific action {:?}", - action - ), - } - } - - fn keyboard_key_to_action(&mut self, evt: B::KeyboardKeyEvent) -> KeyAction { - let keycode = evt.key_code(); - let state = evt.state(); - slog::debug!(self.log, "key"; "keycode" => keycode, "state" => format!("{:?}", state)); - let serial = SCOUNTER.next_serial(); - let log = self.log.clone(); - let time = Event::time_msec(&evt); - let mut suppressed_keys = self.suppressed_keys.clone(); - let keyboard = self.seat.get_keyboard().unwrap(); - - for layer in self.layer_shell_state.layer_surfaces().rev() { - let data = with_states(layer.wl_surface(), |states| { - *states.cached_state.current::() - }); - if data.keyboard_interactivity == KeyboardInteractivity::Exclusive - && (data.layer == WlrLayer::Top || data.layer == WlrLayer::Overlay) - { - let surface = self.space.outputs().find_map(|o| { - let map = layer_map_for_output(o); - let cloned = map.layers().find(|l| l.layer_surface() == &layer).cloned(); - cloned - }); - if let Some(surface) = surface { - keyboard.set_focus(self, Some(surface.into()), serial); - keyboard.input::<(), _>(self, keycode, state, serial, time, |_, _, _| { - FilterResult::Forward - }); - return KeyAction::None; - }; - } - } - - let inhibited = self - .space - .element_under(self.pointer_location) - .and_then(|(window, _)| { - let surface = window.wl_surface()?; - self.seat.keyboard_shortcuts_inhibitor_for_surface(&surface) - }) - .map(|inhibitor| inhibitor.is_active()) - .unwrap_or(false); - - let action = keyboard - .input( - self, - keycode, - state, - serial, - time, - |_, modifiers, handle| { - let keysym = handle.modified_sym(); - - slog::debug!(log, "keysym"; - "state" => format!("{:?}", state), - "mods" => format!("{:?}", modifiers), - "keysym" => ::xkbcommon::xkb::keysym_get_name(keysym.into()) - ); - - // If the key is pressed and triggered a action - // we will not forward the key to the client. - // Additionally add the key to the suppressed keys - // so that we can decide on a release if the key - // should be forwarded to the client or not. - if let KeyState::Pressed = state { - if !inhibited { - let action = process_keyboard_shortcut(*modifiers, keysym); - - if action.is_some() { - suppressed_keys.push(keysym); - } - - action - .map(FilterResult::Intercept) - .unwrap_or(FilterResult::Forward) - } else { - FilterResult::Forward - } - } else { - let suppressed = suppressed_keys.contains(&keysym); - if suppressed { - suppressed_keys.retain(|k| *k != keysym); - FilterResult::Intercept(KeyAction::None) - } else { - FilterResult::Forward - } - } - }, - ) - .unwrap_or(KeyAction::None); - - self.suppressed_keys = suppressed_keys; - action - } - - fn on_pointer_button(&mut self, evt: B::PointerButtonEvent) { - let serial = SCOUNTER.next_serial(); - let button = evt.button_code(); - - let state = wl_pointer::ButtonState::from(evt.state()); - - if wl_pointer::ButtonState::Pressed == state { - self.update_keyboard_focus(serial); - }; - self.seat.get_pointer().unwrap().button( - self, - &ButtonEvent { - button, - state: state.try_into().unwrap(), - serial, - time: evt.time_msec(), - }, - ); - } - - fn update_keyboard_focus(&mut self, serial: Serial) { - let pointer = self.seat.get_pointer().unwrap(); - let keyboard = self.seat.get_keyboard().unwrap(); - let input_method = self.seat.input_method().unwrap(); - // change the keyboard focus unless the pointer or keyboard is grabbed - // We test for any matching surface type here but always use the root - // (in case of a window the toplevel) surface for the focus. - // So for example if a user clicks on a subsurface or popup the toplevel - // will receive the keyboard focus. Directly assigning the focus to the - // matching surface leads to issues with clients dismissing popups and - // subsurface menus (for example firefox-wayland). - // see here for a discussion about that issue: - // https://gitlab.freedesktop.org/wayland/wayland/-/issues/294 - if !pointer.is_grabbed() && (!keyboard.is_grabbed() || input_method.keyboard_grabbed()) { - let output = self - .space - .output_under(self.pointer_location) - .next() - .cloned(); - if let Some(output) = output.as_ref() { - let output_geo = self.space.output_geometry(output).unwrap(); - if let Some(window) = output - .user_data() - .get::() - .and_then(|f| f.get()) - { - if let Some((_, point)) = window.surface_under( - self.pointer_location - output_geo.loc.to_f64(), - WindowSurfaceType::ALL, - ) { - input_method.set_point(&point); - if let AvWindow::X11(surf) = &window { - self.xwm.as_mut().unwrap().raise_window(surf).unwrap(); - } - keyboard.set_focus(self, Some(window.into()), serial); - return; - } - } - - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, self.pointer_location) - .or_else(|| layers.layer_under(WlrLayer::Top, self.pointer_location)) - { - if layer.can_receive_keyboard_focus() { - if let Some((_, point)) = layer.surface_under( - self.pointer_location - - output_geo.loc.to_f64() - - layers.layer_geometry(layer).unwrap().loc.to_f64(), - WindowSurfaceType::ALL, - ) { - input_method.set_point(&point); - keyboard.set_focus(self, Some(layer.clone().into()), serial); - return; - } - } - } - } - - if let Some((window, point)) = self - .space - .element_under(self.pointer_location) - .map(|(w, p)| (w.clone(), p)) - { - self.space.raise_element(&window, true); - input_method.set_point(&point); - keyboard.set_focus(self, Some(window.clone().into()), serial); - #[cfg(feature = "xwayland")] - if let WindowElement::X11(surf) = &window { - self.xwm.as_mut().unwrap().raise_window(surf).unwrap(); - } - return; - } - - if let Some(output) = output.as_ref() { - let output_geo = self.space.output_geometry(output).unwrap(); - let layers = layer_map_for_output(output); - if let Some(layer) = layers - .layer_under(WlrLayer::Bottom, self.pointer_location) - .or_else(|| layers.layer_under(WlrLayer::Background, self.pointer_location)) - { - if layer.can_receive_keyboard_focus() { - if let Some((_, point)) = layer.surface_under( - self.pointer_location - - output_geo.loc.to_f64() - - layers.layer_geometry(layer).unwrap().loc.to_f64(), - WindowSurfaceType::ALL, - ) { - input_method.set_point(&point); - keyboard.set_focus(self, Some(layer.clone().into()), serial); - } - } - } - }; - } - } - - pub fn surface_under(&self) -> Option<(FocusTarget, Point)> { - let pos = self.pointer_location; - let output = self.space.outputs().find(|o| { - let geometry = self.space.output_geometry(o).unwrap(); - geometry.contains(pos.to_i32_round()) - })?; - let output_geo = self.space.output_geometry(output).unwrap(); - let layers = layer_map_for_output(output); - - let mut under = None; - if let Some(window) = output - .user_data() - .get::() - .and_then(|f| f.get()) - { - under = Some((window.into(), output_geo.loc)); - } else if let Some(layer) = layers - .layer_under(WlrLayer::Overlay, pos) - .or_else(|| layers.layer_under(WlrLayer::Top, pos)) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - under = Some((layer.clone().into(), output_geo.loc + layer_loc)) - } else if let Some((window, location)) = self.space.element_under(pos) { - under = Some((window.clone().into(), location)); - } else if let Some(layer) = layers - .layer_under(WlrLayer::Bottom, pos) - .or_else(|| layers.layer_under(WlrLayer::Background, pos)) - { - let layer_loc = layers.layer_geometry(layer).unwrap().loc; - under = Some((layer.clone().into(), output_geo.loc + layer_loc)); - }; - under - } - - fn on_pointer_axis(&mut self, _dh: &DisplayHandle, evt: B::PointerAxisEvent) { - let horizontal_amount = evt - .amount(input::Axis::Horizontal) - .unwrap_or_else(|| evt.amount_discrete(input::Axis::Horizontal).unwrap_or(0.0) * 3.0); - let vertical_amount = evt - .amount(input::Axis::Vertical) - .unwrap_or_else(|| evt.amount_discrete(input::Axis::Vertical).unwrap_or(0.0) * 3.0); - let horizontal_amount_discrete = evt.amount_discrete(input::Axis::Horizontal); - let vertical_amount_discrete = evt.amount_discrete(input::Axis::Vertical); - - { - let mut frame = AxisFrame::new(evt.time_msec()).source(evt.source()); - if horizontal_amount != 0.0 { - frame = frame.value(Axis::Horizontal, horizontal_amount); - if let Some(discrete) = horizontal_amount_discrete { - frame = frame.discrete(Axis::Horizontal, discrete as i32); - } - } else if evt.source() == AxisSource::Finger { - frame = frame.stop(Axis::Horizontal); - } - if vertical_amount != 0.0 { - frame = frame.value(Axis::Vertical, vertical_amount); - if let Some(discrete) = vertical_amount_discrete { - frame = frame.discrete(Axis::Vertical, discrete as i32); - } - } else if evt.source() == AxisSource::Finger { - frame = frame.stop(Axis::Vertical); - } - self.seat.get_pointer().unwrap().axis(self, frame); - } - } -} - -impl Navda { - pub fn process_input_event_windowed( - &mut self, - dh: &DisplayHandle, - event: InputEvent, - output_name: &str, - ) { - match event { - InputEvent::Keyboard { event } => match self.keyboard_key_to_action::(event) { - KeyAction::ScaleUp => { - let output = self - .space - .outputs() - .find(|o| o.name() == output_name) - .unwrap() - .clone(); - - let current_scale = output.current_scale().fractional_scale(); - let new_scale = current_scale + 0.25; - output.change_current_state( - None, - None, - Some(output::Scale::Fractional(new_scale)), - None, - ); - - shell::fixup_positions(&mut self.space); - self.backend_data.reset_buffers(&output); - } - - KeyAction::ScaleDown => { - let output = self - .space - .outputs() - .find(|o| o.name() == output_name) - .unwrap() - .clone(); - - let current_scale = output.current_scale().fractional_scale(); - let new_scale = f64::max(1.0, current_scale - 0.25); - output.change_current_state( - None, - None, - Some(output::Scale::Fractional(new_scale)), - None, - ); - - shell::fixup_positions(&mut self.space); - self.backend_data.reset_buffers(&output); - } - - KeyAction::RotateOutput => { - let output = self - .space - .outputs() - .find(|o| o.name() == output_name) - .unwrap() - .clone(); - - let current_transform = output.current_transform(); - let new_transform = match current_transform { - Transform::Normal => Transform::_90, - Transform::_90 => Transform::_180, - Transform::_180 => Transform::_270, - Transform::_270 => Transform::Normal, - _ => Transform::Normal, - }; - output.change_current_state(None, Some(new_transform), None, None); - shell::fixup_positions(&mut self.space); - self.backend_data.reset_buffers(&output); - } - - action => match action { - KeyAction::None - | KeyAction::Quit - | KeyAction::Run(_) - | KeyAction::TogglePreview => self.process_common_key_action(action), - - _ => slog::warn!( - self.log, - "Key action {:?} unsupported on on output {} backend.", - action, - output_name - ), - }, - }, - - InputEvent::PointerMotionAbsolute { event } => { - let output = self - .space - .outputs() - .find(|o| o.name() == output_name) - .unwrap() - .clone(); - self.on_pointer_move_absolute_windowed::(dh, event, &output) - } - InputEvent::PointerButton { event } => self.on_pointer_button::(event), - InputEvent::PointerAxis { event } => self.on_pointer_axis::(dh, event), - _ => (), // other events are not handled in anvil (yet) - } - } - - fn on_pointer_move_absolute_windowed( - &mut self, - _dh: &DisplayHandle, - evt: B::PointerMotionAbsoluteEvent, - output: &Output, - ) { - let output_geo = self.space.output_geometry(output).unwrap(); - - let pos = evt.position_transformed(output_geo.size) + output_geo.loc.to_f64(); - self.pointer_location = pos; - let serial = SCOUNTER.next_serial(); - - let under = self.surface_under(); - self.seat.get_pointer().unwrap().motion( - self, - under, - &MotionEvent { - location: pos, - serial, - time: evt.time_msec(), - }, - ); - } -} - -impl Navda { - pub fn process_input_event( - &mut self, - dh: &DisplayHandle, - event: InputEvent, - ) { - match event { - InputEvent::Keyboard { event, .. } => match self.keyboard_key_to_action::(event) { - KeyAction::VtSwitch(vt) => { - slog::info!(self.log, "Trying to switch to vt {}", vt); - if let Err(err) = self.backend_data.session.change_vt(vt) { - slog::error!(self.log, "Error switching to vt {}: {}", vt, err); - } - } - KeyAction::Screen(num) => { - let geometry = self - .space - .outputs() - .nth(num) - .map(|o| self.space.output_geometry(o).unwrap()); - - if let Some(geometry) = geometry { - let x = geometry.loc.x as f64 + geometry.size.w as f64 / 2.0; - let y = geometry.size.h as f64 / 2.0; - self.pointer_location = (x, y).into() - } - } - KeyAction::ScaleUp => { - let pos = self.pointer_location.to_i32_round(); - let output = self - .space - .outputs() - .find(|o| self.space.output_geometry(o).unwrap().contains(pos)) - .cloned(); - - if let Some(output) = output { - let (output_location, scale) = ( - self.space.output_geometry(&output).unwrap().loc, - output.current_scale().fractional_scale(), - ); - let new_scale = scale + 0.25; - output.change_current_state( - None, - None, - Some(output::Scale::Fractional(new_scale)), - None, - ); - - let rescale = scale as f64 / new_scale as f64; - let output_location = output_location.to_f64(); - let mut pointer_output_location = self.pointer_location - output_location; - pointer_output_location.x *= rescale; - pointer_output_location.y *= rescale; - self.pointer_location = output_location + pointer_output_location; - - shell::fixup_positions(&mut self.space); - let under = self.surface_under(); - if let Some(ptr) = self.seat.get_pointer() { - ptr.motion( - self, - under, - &MotionEvent { - location: self.pointer_location, - serial: SCOUNTER.next_serial(), - time: 0, - }, - ); - } - self.backend_data.reset_buffers(&output); - } - } - KeyAction::ScaleDown => { - let pos = self.pointer_location.to_i32_round(); - let output = self - .space - .outputs() - .find(|o| self.space.output_geometry(o).unwrap().contains(pos)) - .cloned(); - - if let Some(output) = output { - let (output_location, scale) = ( - self.space.output_geometry(&output).unwrap().loc, - output.current_scale().fractional_scale(), - ); - let new_scale = f64::max(1.0, scale - 0.25); - output.change_current_state( - None, - None, - Some(output::Scale::Fractional(new_scale)), - None, - ); - - let rescale = scale as f64 / new_scale as f64; - let output_location = output_location.to_f64(); - let mut pointer_output_location = self.pointer_location - output_location; - pointer_output_location.x *= rescale; - pointer_output_location.y *= rescale; - self.pointer_location = output_location + pointer_output_location; - - shell::fixup_positions(&mut self.space); - let under = self.surface_under(); - if let Some(ptr) = self.seat.get_pointer() { - ptr.motion( - self, - under, - &MotionEvent { - location: self.pointer_location, - serial: SCOUNTER.next_serial(), - time: 0, - }, - ); - } - self.backend_data.reset_buffers(&output); - } - } - KeyAction::RotateOutput => { - let pos = self.pointer_location.to_i32_round(); - let output = self - .space - .outputs() - .find(|o| self.space.output_geometry(o).unwrap().contains(pos)) - .cloned(); - - if let Some(output) = output { - let current_transform = output.current_transform(); - let new_transform = match current_transform { - Transform::Normal => Transform::_90, - Transform::_90 => Transform::_180, - Transform::_180 => Transform::_270, - Transform::_270 => Transform::Normal, - _ => Transform::Normal, - }; - output.change_current_state(None, Some(new_transform), None, None); - shell::fixup_positions(&mut self.space); - self.backend_data.reset_buffers(&output); - } - } - - action => match action { - KeyAction::None - | KeyAction::Quit - | KeyAction::Run(_) - | KeyAction::TogglePreview => self.process_common_key_action(action), - - _ => unreachable!(), - }, - }, - InputEvent::PointerMotion { event, .. } => self.on_pointer_move::(dh, event), - InputEvent::PointerMotionAbsolute { event, .. } => { - self.on_pointer_move_absolute::(dh, event) - } - InputEvent::PointerButton { event, .. } => self.on_pointer_button::(event), - InputEvent::PointerAxis { event, .. } => self.on_pointer_axis::(dh, event), - InputEvent::TabletToolAxis { event, .. } => self.on_tablet_tool_axis::(event), - InputEvent::TabletToolProximity { event, .. } => { - self.on_tablet_tool_proximity::(dh, event) - } - InputEvent::TabletToolTip { event, .. } => self.on_tablet_tool_tip::(event), - InputEvent::TabletToolButton { event, .. } => self.on_tablet_button::(event), - InputEvent::DeviceAdded { device } => { - if device.has_capability(DeviceCapability::TabletTool) { - self.seat - .tablet_seat() - .add_tablet::(dh, &TabletDescriptor::from(&device)); - } - } - InputEvent::DeviceRemoved { device } => { - if device.has_capability(DeviceCapability::TabletTool) { - let tablet_seat = self.seat.tablet_seat(); - - tablet_seat.remove_tablet(&TabletDescriptor::from(&device)); - - // If there are no tablets in seat we can remove all tools - if tablet_seat.count_tablets() == 0 { - tablet_seat.clear_tools(); - } - } - } - _ => { - // other events are not handled in anvil (yet) - } - } - } - - fn on_pointer_move( - &mut self, - _dh: &DisplayHandle, - evt: B::PointerMotionEvent, - ) { - let serial = SCOUNTER.next_serial(); - self.pointer_location += evt.delta(); - - // clamp to screen limits - // this event is never generated by winit - self.pointer_location = self.clamp_coords(self.pointer_location); - - let under = self.surface_under(); - if let Some(ptr) = self.seat.get_pointer() { - ptr.motion( - self, - under, - &MotionEvent { - location: self.pointer_location, - serial, - time: evt.time_msec(), - }, - ); - } - } - - fn on_pointer_move_absolute( - &mut self, - _dh: &DisplayHandle, - evt: B::PointerMotionAbsoluteEvent, - ) { - let serial = SCOUNTER.next_serial(); - - let max_x = self.space.outputs().fold(0, |acc, o| { - acc + self.space.output_geometry(o).unwrap().size.w - }); - - let max_h_output = self - .space - .outputs() - .max_by_key(|o| self.space.output_geometry(o).unwrap().size.h) - .unwrap(); - - let max_y = self.space.output_geometry(max_h_output).unwrap().size.h; - - self.pointer_location.x = evt.x_transformed(max_x); - self.pointer_location.y = evt.y_transformed(max_y); - - // clamp to screen limits - self.pointer_location = self.clamp_coords(self.pointer_location); - - let under = self.surface_under(); - if let Some(ptr) = self.seat.get_pointer() { - ptr.motion( - self, - under, - &MotionEvent { - location: self.pointer_location, - serial, - time: evt.time_msec(), - }, - ); - } - } - - fn on_tablet_tool_axis(&mut self, evt: B::TabletToolAxisEvent) { - let tablet_seat = self.seat.tablet_seat(); - - let output_geometry = self - .space - .outputs() - .next() - .map(|o| self.space.output_geometry(o).unwrap()); - - if let Some(rect) = output_geometry { - self.pointer_location = evt.position_transformed(rect.size) + rect.loc.to_f64(); - - let under = self.surface_under(); - let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&evt.device())); - let tool = tablet_seat.get_tool(&evt.tool()); - - if let (Some(tablet), Some(tool)) = (tablet, tool) { - if evt.pressure_has_changed() { - tool.pressure(evt.pressure()); - } - if evt.distance_has_changed() { - tool.distance(evt.distance()); - } - if evt.tilt_has_changed() { - tool.tilt(evt.tilt()); - } - if evt.slider_has_changed() { - tool.slider_position(evt.slider_position()); - } - if evt.rotation_has_changed() { - tool.rotation(evt.rotation()); - } - if evt.wheel_has_changed() { - tool.wheel(evt.wheel_delta(), evt.wheel_delta_discrete()); - } - - tool.motion( - self.pointer_location, - under.and_then(|(f, loc)| f.wl_surface().map(|s| (s, loc))), - &tablet, - SCOUNTER.next_serial(), - evt.time_msec(), - ); - } - } - } - - fn on_tablet_tool_proximity( - &mut self, - dh: &DisplayHandle, - evt: B::TabletToolProximityEvent, - ) { - use smithay::backend::input::TabletToolProximityEvent; - - let tablet_seat = self.seat.tablet_seat(); - - let output_geometry = self - .space - .outputs() - .next() - .map(|o| self.space.output_geometry(o).unwrap()); - - if let Some(rect) = output_geometry { - let tool = evt.tool(); - tablet_seat.add_tool::(dh, &tool); - - self.pointer_location = evt.position_transformed(rect.size) + rect.loc.to_f64(); - - let under = self.surface_under(); - let tablet = tablet_seat.get_tablet(&TabletDescriptor::from(&evt.device())); - let tool = tablet_seat.get_tool(&tool); - - if let (Some(under), Some(tablet), Some(tool)) = ( - under.and_then(|(f, loc)| f.wl_surface().map(|s| (s, loc))), - tablet, - tool, - ) { - match evt.state() { - ProximityState::In => tool.proximity_in( - self.pointer_location, - under, - &tablet, - SCOUNTER.next_serial(), - evt.time_msec(), - ), - ProximityState::Out => tool.proximity_out(evt.time_msec()), - } - } - } - } - - fn on_tablet_tool_tip(&mut self, evt: B::TabletToolTipEvent) { - let tool = self.seat.tablet_seat().get_tool(&evt.tool()); - - if let Some(tool) = tool { - match evt.tip_state() { - TabletToolTipState::Down => { - let serial = SCOUNTER.next_serial(); - tool.tip_down(serial, evt.time_msec()); - - // change the keyboard focus - self.update_keyboard_focus(serial); - } - TabletToolTipState::Up => { - tool.tip_up(evt.time_msec()); - } - } - } - } - - fn on_tablet_button(&mut self, evt: B::TabletToolButtonEvent) { - use smithay::backend::input::TabletToolButtonEvent; - - let tool = self.seat.tablet_seat().get_tool(&evt.tool()); - - if let Some(tool) = tool { - tool.button( - evt.button(), - evt.button_state(), - SCOUNTER.next_serial(), - evt.time_msec(), - ); - } - } - - fn clamp_coords(&self, pos: Point) -> Point { - if self.space.outputs().next().is_none() { - return pos; - } - - let (pos_x, pos_y) = pos.into(); - let max_x = self.space.outputs().fold(0, |acc, o| { - acc + self.space.output_geometry(o).unwrap().size.w - }); - let clamped_x = pos_x.max(0.0).min(max_x as f64); - let max_y = self - .space - .outputs() - .find(|o| { - let geo = self.space.output_geometry(o).unwrap(); - geo.contains((clamped_x as i32, 0)) - }) - .map(|o| self.space.output_geometry(o).unwrap().size.h); - - if let Some(max_y) = max_y { - let clamped_y = pos_y.max(0.0).min(max_y as f64); - (clamped_x, clamped_y).into() - } else { - (clamped_x, pos_y).into() - } - } -} - -/// Possible results of a keyboard action -#[derive(Debug)] -enum KeyAction { - /// Quit the compositor - Quit, - /// Trigger a vt-switch - VtSwitch(i32), - /// run a command - Run(String), - /// Switch the current screen - Screen(usize), - ScaleUp, - ScaleDown, - TogglePreview, - RotateOutput, - /// Do nothing more - None, -} - -fn process_keyboard_shortcut(modifiers: ModifiersState, keysym: u32) -> Option { - match (modifiers, keysym) { - (ModifiersState { ctrl: true, alt: true, .. }, KEY_BackSpace) - | (ModifiersState { logo: true, .. }, KEY_q) => Some(KeyAction::Quit), // ctrl+alt+backspace = quit, logo + q = quit - (_, keysym) if (KEY_XF86Switch_VT_1..=KEY_XF86Switch_VT_12).contains(&keysym) => { - // VTSwitch - Some(KeyAction::VtSwitch((keysym - KEY_XF86Switch_VT_1 + 1) as i32)) - } - (ModifiersState { logo: true, .. }, KEY_Return) => Some(KeyAction::Run("alacritty".into())), // run terminal - (ModifiersState { logo: true, .. }, keysym) if (KEY_1..=KEY_9).contains(&keysym) => { - Some(KeyAction::Screen((keysym - KEY_1) as usize)) - } - (ModifiersState { logo: true, shift: true, .. }, KEY_M) => Some(KeyAction::ScaleDown), - (ModifiersState { logo: true, shift: true, .. }, KEY_P) => Some(KeyAction::ScaleUp), - (ModifiersState { logo: true, shift: true, .. }, KEY_W) => Some(KeyAction::TogglePreview), - (ModifiersState { logo: true, shift: true, .. }, KEY_R) => Some(KeyAction::RotateOutput), - _ => None, - } -} diff --git a/src/compositor/mod.rs b/src/compositor/mod.rs deleted file mode 100644 index 71be5c8..0000000 --- a/src/compositor/mod.rs +++ /dev/null @@ -1,31 +0,0 @@ -use slog::{o, Drain, Logger}; - -use self::backend::{run_udev, run_winit}; - -mod backend; -mod components; -mod drawing; -mod focus; -mod handlers; -mod input; -mod render; -mod shell; -mod state; - -pub fn start() -> Result<(), Box> { - let plain = slog_term::PlainSyncDecorator::new(std::io::stdout()); - let log = Logger::root(slog_term::FullFormat::new(plain).build().fuse(), o!()); - - // Env variable NAVDA_BACKEND=udev will tell the compositor to launch under udev, - // otherwise winit backend. - if ::std::env::var("NAVDA_BACKEND") - .map(|s| s == "udev") - .unwrap_or_default() - { - run_udev(log); - } else { - run_winit(log); - } - - Ok(()) -} diff --git a/src/compositor/render.rs b/src/compositor/render.rs deleted file mode 100644 index 58a2602..0000000 --- a/src/compositor/render.rs +++ /dev/null @@ -1,103 +0,0 @@ -use smithay::{ - backend::renderer::{ - damage::{DamageTrackedRenderer, DamageTrackedRendererError, DamageTrackedRendererMode}, - element::{ - surface::WaylandSurfaceRenderElement, utils::CropRenderElement, AsRenderElements, - RenderElementStates, - }, - ImportAll, ImportMem, Renderer, - }, - desktop::{self, Space}, - output::Output, - render_elements, - utils::{Physical, Rectangle}, -}; - -use super::{ - drawing::{PointerRenderElement, CLEAR_COLOR}, - shell::{AvWindow, AvWindowRenderElement, FullscreenSurface}, -}; - -render_elements! { - pub CustomRenderElements where - R: ImportAll + ImportMem; - Pointer=PointerRenderElement, - Surface=WaylandSurfaceRenderElement, - Window=AvWindowRenderElement, -} - -render_elements! { - pub OutputRenderElements<'a, R> where - R: ImportAll + ImportMem; - Custom=&'a CustomRenderElements, -} - -/// -/// Big boi render function -- render everything: -/// all elements; to the output. -/// -pub fn render_output<'a, R>( - output: &Output, - space: &'a Space, - custom_elements: &'a [CustomRenderElements], - renderer: &mut R, - damage_tracked_renderer: &mut DamageTrackedRenderer, - age: usize, - // NOTE: review code for window preview for the - // "App view" thing. - // show_window_preview: bool, - log: &slog::Logger, -) -> Result< - (Option>>, RenderElementStates), - DamageTrackedRendererError, -> -where - R: Renderer + ImportAll + ImportMem, - R::TextureId: Clone + 'static, -{ - let output_scale = output.current_scale().fractional_scale().into(); - - if let Some(window) = output - .user_data() - .get::() - .and_then(|f| f.get()) - { - if let DamageTrackedRendererMode::Auto(renderer_output) = damage_tracked_renderer.mode() { - assert!(renderer_output == output); - } - - let window_render_elements = - AsRenderElements::::render_elements(&window, renderer, (0, 0).into(), output_scale); - - let render_elements = custom_elements - .iter() - .chain(window_render_elements.iter()) - .collect::>(); - - damage_tracked_renderer.render_output( - renderer, - age, - &render_elements, - CLEAR_COLOR, - log.clone(), - ) - } else { - let output_render_elements = custom_elements - .iter() - .map(OutputRenderElements::from) - .collect::>(); - - // SNIP : Window Previews - - desktop::space::render_output( - output, - renderer, - age, - [space], - &output_render_elements, - damage_tracked_renderer, - CLEAR_COLOR, - log.clone(), - ) - } -} diff --git a/src/compositor/shell/avwindow/mod.rs b/src/compositor/shell/avwindow/mod.rs deleted file mode 100644 index 8c056e2..0000000 --- a/src/compositor/shell/avwindow/mod.rs +++ /dev/null @@ -1,486 +0,0 @@ -//! -//! Contains various implementation for the -//! AvWindow abstraction over X11 (via XWayland) and -//! Wayland windows. -//! - -mod state; - -use std::time::Duration; - -use smithay::{ - backend::renderer::{ - element::{surface::WaylandSurfaceRenderElement, AsRenderElements}, - ImportAll, ImportMem, Renderer, Texture, - }, - desktop::{ - space::SpaceElement, - utils::{ - send_frames_surface_tree, take_presentation_feedback_surface_tree, - under_from_surface_tree, with_surfaces_surface_tree, OutputPresentationFeedback, - }, - Window, WindowSurfaceType, - }, - input::{ - keyboard::{KeyboardTarget, KeysymHandle}, - pointer::{self, PointerTarget}, - Seat, - }, - output::Output, - reexports::{ - wayland_protocols::wp::presentation_time::server::wp_presentation_feedback, - wayland_server::protocol::wl_surface::WlSurface, - }, - render_elements, - utils::{user_data::UserDataMap, IsAlive, Logical, Physical, Point, Rectangle, Scale, Serial}, - wayland::{ - compositor::SurfaceData as WlSurfaceData, seat::WaylandFocus, shell::xdg::ToplevelSurface, - }, - xwayland::X11Surface, -}; - -use crate::compositor::{backend::Backend, state::Navda}; - -/// -/// Abstraction over X11 and Wayland Windows. -/// -/// This would also later implement Tabbing, -/// and Tiling. -/// -#[derive(Debug, PartialEq, Clone)] -pub enum AvWindow { - /// - /// Wayland Desktop Window - /// - Wayland(Window), - - /// - /// XWayland 'Window' - /// - X11(X11Surface), -} - -impl AvWindow { - /// - /// Find top-most surface with type `window_type` - /// under this point. - /// - pub fn surface_under( - &self, - location: Point, - window_type: WindowSurfaceType, - ) -> Option<(WlSurface, Point)> { - match self { - Self::Wayland(w) => w.surface_under(location, window_type), - Self::X11(w) => w - .wl_surface() - .and_then(|s| under_from_surface_tree(&s, location, (0, 0), window_type)), - } - } - - /// - /// Run a function on all subsurfaces - /// of this window. - /// - pub fn with_surfaces(&self, processor: impl FnMut(&WlSurface, &WlSurfaceData) + Copy) { - match self { - Self::Wayland(w) => w.with_surfaces(processor), - Self::X11(w) => { - if let Some(surface) = w.wl_surface() { - with_surfaces_surface_tree(&surface, processor); - } - } - } - } - - /// - /// Send frame callback to surfaces in this window. - /// - pub fn send_frame( - &self, - output: &Output, - time: impl Into, - throttle: Option, - primary_scan_out_output: impl FnMut(&WlSurface, &WlSurfaceData) -> Option + Copy, - ) { - match self { - Self::Wayland(w) => w.send_frame(output, time, throttle, primary_scan_out_output), - Self::X11(w) => { - if let Some(surface) = w.wl_surface() { - send_frames_surface_tree( - &surface, - output, - time, - throttle, - primary_scan_out_output, - ); - } - } - } - } - - /// - /// Presentation feedback is an indication that a wl_surface's content - /// update is now visible to the user. - /// - /// See more on presentation time feedback - /// on [Wayland.app](https://wayland.app/protocols/presentation-time#wp_presentation_feedback). - /// - pub fn take_presentation_feedback( - &self, - output_feedback: &mut OutputPresentationFeedback, - primary_scan_out_output: impl FnMut(&WlSurface, &WlSurfaceData) -> Option + Copy, - presentation_feedback_flags: impl FnMut(&WlSurface, &WlSurfaceData) -> wp_presentation_feedback::Kind - + Copy, - ) { - match self { - Self::Wayland(w) => w.take_presentation_feedback( - output_feedback, - primary_scan_out_output, - presentation_feedback_flags, - ), - Self::X11(w) => { - if let Some(surface) = w.wl_surface() { - take_presentation_feedback_surface_tree( - &surface, - output_feedback, - primary_scan_out_output, - presentation_feedback_flags, - ) - } - } - } - } - - // Utility functions - - /// - /// Is this an X11 window running under XWayland ? - /// - pub fn is_x11(&self) -> bool { - matches!(self, Self::X11(_)) - } - - /// - /// Is this a Wayland window ? - /// - pub fn is_wayland(&self) -> bool { - matches!(self, Self::Wayland(_)) - } - - /// - /// Returns the underlying wl_surface object, - /// if any. - /// - pub fn wl_surface(&self) -> Option { - match self { - Self::Wayland(w) => w.wl_surface(), - Self::X11(w) => w.wl_surface(), - } - } - - /// - /// Shows user_map data — a type-based - /// key-value system for storing arbitrary info - /// to do with this window. - /// - pub fn user_data(&self) -> &UserDataMap { - match self { - Self::Wayland(w) => w.user_data(), - Self::X11(w) => w.user_data(), - } - } -} - -/// -/// Checks if the underlying window -/// is alive. -/// -impl IsAlive for AvWindow { - fn alive(&self) -> bool { - match self { - Self::Wayland(w) => w.alive(), - Self::X11(w) => w.alive(), - } - } -} - -// TODO(Sammy99jsp) Possibly automate `PointerTarget` and -// `KeyboardTarget` with custom `#[pass(PointerTarget)]` macro - -/// -/// Allowing for `AvWindow`s to be interacted -/// with the pointer. -/// -impl PointerTarget> for AvWindow { - // TODO: Server-Side Decoration Logic - fn enter( - &self, - seat: &Seat>, - data: &mut Navda, - event: &pointer::MotionEvent, - ) { - match self { - Self::Wayland(w) => PointerTarget::enter(w, seat, data, event), - Self::X11(w) => PointerTarget::enter(w, seat, data, event), - } - } - - /** - * TODO: - * `pass` macro idea: - * - * #[pass(PointerTarget)] - * fn enter( - * &self, - * seat : ..., - * data : ..., - * event: ..., - * ) - * - */ - - fn motion( - &self, - seat: &Seat>, - data: &mut Navda, - event: &pointer::MotionEvent, - ) { - match self { - Self::Wayland(w) => PointerTarget::motion(w, seat, data, &event), - Self::X11(w) => PointerTarget::motion(w, seat, data, event), - } - } - - fn button( - &self, - seat: &Seat>, - data: &mut Navda, - event: &pointer::ButtonEvent, - ) { - match self { - Self::Wayland(w) => PointerTarget::button(w, seat, data, &event), - Self::X11(w) => PointerTarget::button(w, seat, data, event), - } - } - - fn axis(&self, seat: &Seat>, data: &mut Navda, frame: pointer::AxisFrame) { - match self { - Self::Wayland(w) => PointerTarget::axis(w, seat, data, frame), - Self::X11(w) => PointerTarget::axis(w, seat, data, frame), - } - } - - fn leave(&self, seat: &Seat>, data: &mut Navda, serial: Serial, time: u32) { - match self { - Self::Wayland(w) => PointerTarget::leave(w, seat, data, serial, time), - Self::X11(w) => PointerTarget::leave(w, seat, data, serial, time), - } - } - - /// - /// https://wayland.app/protocols/relative-pointer-unstable-v1 - /// - fn relative_motion( - &self, - seat: &Seat>, - data: &mut Navda, - event: &pointer::RelativeMotionEvent, - ) { - match self { - Self::Wayland(w) => PointerTarget::relative_motion(w, seat, data, event), - Self::X11(w) => PointerTarget::relative_motion(w, seat, data, event), - } - } -} - -/// -/// Pass down keyboard events to window. -/// -impl KeyboardTarget> for AvWindow { - fn enter( - &self, - seat: &Seat>, - data: &mut Navda, - keys: Vec>, - serial: Serial, - ) { - match self { - Self::Wayland(w) => KeyboardTarget::enter(w, seat, data, keys, serial), - Self::X11(w) => KeyboardTarget::enter(w, seat, data, keys, serial), - } - } - - fn leave(&self, seat: &Seat>, data: &mut Navda, serial: Serial) { - match self { - Self::Wayland(w) => KeyboardTarget::leave(w, seat, data, serial), - Self::X11(w) => KeyboardTarget::leave(w, seat, data, serial), - } - } - - fn key( - &self, - seat: &Seat>, - data: &mut Navda, - key: KeysymHandle<'_>, - state: smithay::backend::input::KeyState, - serial: Serial, - time: u32, - ) { - // TODO(@Sammy99jsp): Custom keybinds for switching tabs. - - match self { - Self::Wayland(w) => KeyboardTarget::key(w, seat, data, key, state, serial, time), - Self::X11(w) => KeyboardTarget::key(w, seat, data, key, state, serial, time), - } - } - - fn modifiers( - &self, - seat: &Seat>, - data: &mut Navda, - modifiers: smithay::input::keyboard::ModifiersState, - serial: Serial, - ) { - // TODO(@Sammy99jsp): Custom keybinds for switching tabs. - match self { - AvWindow::Wayland(w) => KeyboardTarget::modifiers(w, seat, data, modifiers, serial), - AvWindow::X11(w) => KeyboardTarget::modifiers(w, seat, data, modifiers, serial), - } - } -} - -/// -/// Allows the AvWindow to be mapped -/// in the 2D Space. -/// -impl SpaceElement for AvWindow { - /// - /// Get the geometry for this window - /// (aka) rectangle. - /// - fn geometry(&self) -> Rectangle { - let geo = match self { - Self::Wayland(w) => SpaceElement::geometry(w), - Self::X11(w) => SpaceElement::geometry(w), - }; - // TODO(Sammy99jsp) : Custom logic here for window decorations - geo - } - - /// - /// Gets the bounding box for this window. - /// - fn bbox(&self) -> Rectangle { - let bbox = match self { - Self::Wayland(w) => SpaceElement::bbox(w), - Self::X11(w) => SpaceElement::bbox(w), - }; - // TODO(Sammy99jsp) : Custom logic here for window decorations. - bbox - } - - /// - /// Checks if a point is in this window's - /// input region. - /// - fn is_in_input_region(&self, point: &Point) -> bool { - // TODO(Sammy99jsp) : Custom logic here - // for window decorations. - - match self { - AvWindow::Wayland(w) => SpaceElement::is_in_input_region(w, point), - AvWindow::X11(w) => SpaceElement::is_in_input_region(w, point), - } - } - - /// - /// Set rendered state to active, if applicable. - /// - fn set_activate(&self, activated: bool) { - match self { - Self::Wayland(w) => SpaceElement::set_activate(w, activated), - Self::X11(w) => SpaceElement::set_activate(w, activated), - } - } - - /// - /// Called when the window enters/outputs between outputs (aka. displays). - /// - fn output_enter(&self, output: &Output, overlap: Rectangle) { - match self { - Self::Wayland(w) => SpaceElement::output_enter(w, output, overlap), - Self::X11(w) => SpaceElement::output_enter(w, output, overlap), - } - } - - /// - /// Called when a window leaves a display. - /// - fn output_leave(&self, output: &Output) { - match self { - Self::Wayland(w) => SpaceElement::output_leave(w, output), - Self::X11(w) => SpaceElement::output_leave(w, output), - } - } - - /// - /// Get z-index of underlying display. - /// - fn z_index(&self) -> u8 { - match self { - Self::Wayland(w) => SpaceElement::z_index(w), - Self::X11(w) => SpaceElement::z_index(w), - } - } - - /// - /// Updates window's state — periodically called. - /// - fn refresh(&self) { - match self { - Self::Wayland(w) => SpaceElement::refresh(w), - Self::X11(w) => SpaceElement::refresh(w), - } - } -} - -render_elements!( - pub AvWindowRenderElement where R : ImportAll + ImportMem; - Window=WaylandSurfaceRenderElement, -); - -impl AsRenderElements for AvWindow -where - R: Renderer + ImportAll + ImportMem, - ::TextureId: Texture + 'static, -{ - type RenderElement = AvWindowRenderElement; - - fn render_elements>( - &self, - renderer: &mut R, - location: Point, - scale: Scale, - ) -> Vec { - let window_bbox = self.bbox(); - - let window_geo = self.geometry(); - - let width = window_geo.size.w; - - let mut vec = match self { - Self::Wayland(xdg) => { - AsRenderElements::::render_elements(xdg, renderer, location, scale) - } - Self::X11(xdg) => { - AsRenderElements::::render_elements(xdg, renderer, location, scale) - } - }; - - // TODO(@Sammy99jsp) - // Custom rendering logic here for window decorations. - - vec.into_iter().map(C::from).collect() - } -} diff --git a/src/compositor/shell/avwindow/state.rs b/src/compositor/shell/avwindow/state.rs deleted file mode 100644 index 8b13789..0000000 --- a/src/compositor/shell/avwindow/state.rs +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/compositor/shell/grabs/grab_move.rs b/src/compositor/shell/grabs/grab_move.rs deleted file mode 100644 index 750c50a..0000000 --- a/src/compositor/shell/grabs/grab_move.rs +++ /dev/null @@ -1,78 +0,0 @@ -use smithay::{ - input::{ - pointer::{self, GrabStartData as PointerGrabStartData, PointerGrab}, - SeatHandler, - }, - utils::{Logical, Point}, -}; - -use crate::compositor::{backend::Backend, shell::avwindow::AvWindow, state::Navda}; - -pub struct MoveSurfaceGrab { - pub start_data: PointerGrabStartData>, - pub window: AvWindow, - pub initial_window_location: Point, -} - -impl PointerGrab> for MoveSurfaceGrab { - fn motion( - &mut self, - data: &mut Navda, - handle: &mut pointer::PointerInnerHandle<'_, Navda>, - focus: Option<( - as SeatHandler>::PointerFocus, - Point, - )>, - event: &pointer::MotionEvent, - ) { - // While grab is active, no client has pointer focus. - handle.motion(data, None, event); - - let delta = event.location - self.start_data.location; - let new_location = self.initial_window_location.to_f64() + delta; - - data.space - .map_element(self.window.clone(), new_location.to_i32_round(), true); - } - - fn button( - &mut self, - data: &mut Navda, - handle: &mut pointer::PointerInnerHandle<'_, Navda>, - event: &pointer::ButtonEvent, - ) { - handle.button(data, event); - if handle.current_pressed().is_empty() { - // No more buttons are pressed, release grab. - handle.unset_grab(data, event.serial, event.time); - } - } - - fn axis( - &mut self, - data: &mut Navda, - handle: &mut pointer::PointerInnerHandle<'_, Navda>, - details: pointer::AxisFrame, - ) { - handle.axis(data, details); - } - - fn start_data(&self) -> &pointer::GrabStartData> { - &self.start_data - } - - fn relative_motion( - &mut self, - data: &mut Navda, - handle: &mut pointer::PointerInnerHandle<'_, Navda>, - focus: Option<( - as SeatHandler>::PointerFocus, - Point, - )>, - event: &pointer::RelativeMotionEvent, - ) { - handle.relative_motion(data, focus, event); - - // TODO(Sammy99jsp) possibly find another use for this. - } -} diff --git a/src/compositor/shell/grabs/grab_resize.rs b/src/compositor/shell/grabs/grab_resize.rs deleted file mode 100644 index d0c6fcf..0000000 --- a/src/compositor/shell/grabs/grab_resize.rs +++ /dev/null @@ -1,442 +0,0 @@ -//! -//! Grab handlers for resizing windows. -//! - -use std::cell::RefCell; - -use smithay::{ - desktop::{space::SpaceElement, Space}, - input::{ - pointer::{self, GrabStartData as PointerGrabStartData, PointerGrab}, - SeatHandler, - }, - reexports::{ - wayland_protocols::xdg::shell::server::xdg_toplevel, - wayland_server::protocol::wl_surface::WlSurface, - }, - utils::{IsAlive, Logical, Point, Rectangle, Serial, Size, SERIAL_COUNTER}, - wayland::{ - compositor::{self}, - shell::xdg::SurfaceCachedState, - }, - xwayland, -}; - -use crate::compositor::{ - backend::Backend, - shell::{avwindow::AvWindow, SurfaceData}, - state::Navda, -}; - -bitflags::bitflags! { - /// - /// Edge/Corner that was grabbed, if any. - /// - /// Here, we are using the `smallvil`'s implementation, - /// + NONE. - /// - /// - #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] - pub struct ResizeEdge: u32 { - const NONE = 0b0000; - - const TOP = 0b0001; - const BOTTOM = 0b0010; - const LEFT = 0b0100; - const RIGHT = 0b1000; - - const TOP_LEFT = Self::TOP.bits() | Self::LEFT.bits(); - const BOTTOM_LEFT = Self::BOTTOM.bits() | Self::LEFT.bits(); - const TOP_RIGHT = Self::TOP.bits() | Self::RIGHT.bits(); - const BOTTOM_RIGHT = Self::BOTTOM.bits() | Self::RIGHT.bits(); - } -} - -impl From for ResizeEdge { - #[inline] - fn from(x: xdg_toplevel::ResizeEdge) -> Self { - Self::from_bits(x as u32).unwrap() - } -} - -impl From for xdg_toplevel::ResizeEdge { - #[inline] - fn from(x: ResizeEdge) -> Self { - Self::try_from(x.bits()).unwrap() - } -} - -impl From for ResizeEdge { - fn from(edge: xwayland::xwm::ResizeEdge) -> Self { - use xwayland::xwm::ResizeEdge::*; - - match edge { - Bottom => Self::BOTTOM, - BottomLeft => Self::BOTTOM_LEFT, - BottomRight => Self::BOTTOM_RIGHT, - Left => Self::LEFT, - Top => Self::TOP, - TopLeft => Self::TOP_LEFT, - Right => Self::RIGHT, - TopRight => Self::TOP_RIGHT, - } - } -} - -// -/// Data associated with resize grabbing -/// -pub struct ResizeSurfaceGrab { - pub start_data: PointerGrabStartData>, - pub window: AvWindow, - - pub edges: ResizeEdge, - - pub initial_rect: Rectangle, - pub last_window_size: Size, -} - -impl PointerGrab> for ResizeSurfaceGrab { - fn motion( - &mut self, - data: &mut Navda, - handle: &mut pointer::PointerInnerHandle<'_, Navda>, - focus: Option<( - as SeatHandler>::PointerFocus, - Point, - )>, - event: &pointer::MotionEvent, - ) { - // While grab active, no client has focus. - handle.motion(data, None, event); - - // If dead toplevel, we can't get `min_size` or `max_size` - // so return early - if !self.window.alive() { - handle.unset_grab(data, event.serial, event.time); - return; - } - - // Used for the size - let mut delta = event.location - self.start_data.location; - - let Size { - w: mut new_window_width, - h: mut new_window_height, - .. - } = self.initial_rect.size; - - if self.edges.intersects(ResizeEdge::LEFT | ResizeEdge::RIGHT) { - if self.edges.intersects(ResizeEdge::LEFT) { - delta.x = -delta.x; - } - - new_window_width = (self.initial_rect.size.w as f64 + delta.x) as i32; - } - - if self.edges.intersects(ResizeEdge::TOP | ResizeEdge::BOTTOM) { - if self.edges.intersects(ResizeEdge::TOP) { - delta.y = -delta.y; - } - - new_window_height = (self.initial_rect.size.h as f64 + delta.y) as i32; - } - - let (min_size, max_size) = self - .window - .wl_surface() - .map(|ref s| { - compositor::with_states(s, |states| { - let data = states.cached_state.current::(); - (data.min_size, data.max_size) - }) - }) - .unwrap_or(((0, 0).into(), (0, 0).into())); - - let (min_width, min_height) = (min_size.w.max(1), min_size.h.max(1)); - let (max_width, max_height) = ( - (max_size.w == 0).then(|| i32::MAX).unwrap_or(max_size.w), - (max_size.h == 0).then(|| i32::MAX).unwrap_or(max_size.h), - ); - - self.last_window_size = (( - new_window_width.max(min_width).min(max_width), - new_window_height.max(min_height).min(max_height), - )) - .into(); - - match &self.window { - AvWindow::Wayland(w) => { - let xdg = w.toplevel(); - xdg.with_pending_state(|state| { - state.states.set(xdg_toplevel::State::Resizing); - state.size = Some(self.last_window_size); - }); - xdg.send_configure(); - } - AvWindow::X11(x11) => { - let location = data.space.element_location(&self.window).unwrap(); - x11.configure(Rectangle::from_loc_and_size( - location, - self.last_window_size, - )) - .unwrap(); - } - } - - ResizeState::with(self.window.wl_surface().as_ref().unwrap(), |state| { - *state = ResizeState::Resizing(ResizeData { - edges: self.edges, - initial_rect: self.initial_rect, - }); - }); - } - - fn button( - &mut self, - data: &mut Navda, - handle: &mut pointer::PointerInnerHandle<'_, Navda>, - event: &pointer::ButtonEvent, - ) { - handle.button(data, event); - - /// The button is a button code as defined in the - /// Linux kernel's [linux/input-event-codes.h](https://github.com/torvalds/linux/blob/master/include/uapi/linux/input-event-codes.h) header file, e.g. BTN_LEFT. - const BTN_LEFT: u32 = 0x110; - - // What happens when we let go... - if !handle.current_pressed().contains(&BTN_LEFT) { - // No more buttons pressed, release grab. - handle.unset_grab(data, event.serial, event.time); - - // If toplevel is dead, we can't resize it, so we return early. - if !self.window.alive() { - return; - } - - match &self.window { - AvWindow::Wayland(w) => { - let xdg = w.toplevel(); - xdg.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Resizing); - state.size = Some(self.last_window_size); - }); - - xdg.send_configure(); - - compositor::with_states(&self.window.wl_surface().unwrap(), |states| { - let mut data = states - .data_map - .get::>() - .unwrap() - .borrow_mut(); - - if let ResizeState::Resizing(resize_data) = data.resize_state { - data.resize_state = - ResizeState::WaitingForFinalAck(resize_data, event.serial); - } else { - panic!("invalid resize state: {:?}", data.resize_state); - } - }); - } - AvWindow::X11(x11) => { - let loc = self.initial_rect.loc; - - x11.configure(Rectangle::from_loc_and_size(loc, self.last_window_size)) - .unwrap(); - - let Some(surface) = self.window.wl_surface() else { - // X11 Window got unmapped, abort - return - }; - - compositor::with_states(&surface, |states| { - let mut data = states - .data_map - .get::>() - .unwrap() - .borrow_mut(); - - if let ResizeState::Resizing(resize_data) = data.resize_state { - data.resize_state = ResizeState::WaitingForCommit(resize_data); - } else { - panic!("invalid resize state: {:?}", data.resize_state); - } - }); - } - } - - ResizeState::with(self.window.wl_surface().as_ref().unwrap(), |state| { - *state = ResizeState::WaitingForFinalAck( - ResizeData { - edges: self.edges, - initial_rect: self.initial_rect, - }, - SERIAL_COUNTER.next_serial(), - ); - }); - } - } - - fn axis( - &mut self, - data: &mut Navda, - handle: &mut pointer::PointerInnerHandle<'_, Navda>, - details: pointer::AxisFrame, - ) { - handle.axis(data, details); - } - - fn start_data(&self) -> &pointer::GrabStartData> { - &self.start_data - } - - fn relative_motion( - &mut self, - data: &mut Navda, - handle: &mut pointer::PointerInnerHandle<'_, Navda>, - focus: Option<( - as SeatHandler>::PointerFocus, - Point, - )>, - event: &pointer::RelativeMotionEvent, - ) { - // While grab active, no client has focus. - handle.relative_motion(data, None, event); - - // If dead toplevel, we can't get `min_size` or `max_size` - // so return early - if !self.window.alive() { - handle.unset_grab( - data, - SERIAL_COUNTER.next_serial(), - (event.utime / 1000) as u32, - ); - return; - } - - // TODO(Sammy99jsp) CHECK ABOVE CONVERSION TO ms - } -} - -/// -/// Apply any location fixes to the -/// window before it is drawn -/// -pub fn handle_commit(space: &mut Space, surface: &WlSurface) -> Option<()> { - let window = space - .elements() - .find(|w| { - w.wl_surface() - .as_ref() - .map(|s| s == surface) - .unwrap_or_default() - }) - .cloned()?; - - let mut window_loc = space.element_location(&window)?; - let geometry = window.geometry(); - - let new_loc: Point, Logical> = ResizeState::with(surface, |state| { - state - .commit() - .and_then(|(edges, initial_rect)| { - // If the window is being resized by top or left, its location must be adjusted - // accordingly. - edges.intersects(ResizeEdge::TOP_LEFT).then(|| { - let new_x = edges - .intersects(ResizeEdge::LEFT) - .then_some(initial_rect.loc.x + (initial_rect.size.w - geometry.size.w)); - - let new_y = edges - .intersects(ResizeEdge::TOP) - .then_some(initial_rect.loc.y + (initial_rect.size.h - geometry.size.h)); - - (new_x, new_y).into() - }) - }) - .unwrap_or_default() - }); - - if let Some(new_x) = new_loc.x { - window_loc.x = new_x; - } - if let Some(new_y) = new_loc.y { - window_loc.y = new_y; - } - - if new_loc.x.is_some() || new_loc.y.is_some() { - // If TOP or LEFT side of the window got resized, we have to move it - space.map_element(window, window_loc, false); - } - - Some(()) -} - -/// Information about the resize operation. -#[derive(Debug, Clone, Copy, Eq, PartialEq)] -pub struct ResizeData { - /// The edges the surface is being resized with. - pub edges: ResizeEdge, - - /// The initial window rectangle. - pub initial_rect: Rectangle, -} - -/// -/// State of operations. -/// -#[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub enum ResizeState { - /// The surface is not being resized. - Idle, - /// The surface is currently being resized. - Resizing(ResizeData), - /// The resize has finished, and the surface needs to ack the final configure. - WaitingForFinalAck(ResizeData, Serial), - /// The resize has finished, and the surface needs to commit its final state. - WaitingForCommit(ResizeData), -} - -impl Default for ResizeState { - fn default() -> Self { - Self::Idle - } -} - -impl ResizeState { - fn with(surface: &WlSurface, cb: impl FnOnce(&mut Self) -> T) -> T { - compositor::with_states(surface, |states| { - states.data_map.insert_if_missing(RefCell::::default); - let state = states.data_map.get::>().unwrap(); - - cb(&mut state.borrow_mut()) - }) - } - - fn commit(&mut self) -> Option<(ResizeEdge, Rectangle)> { - match *self { - Self::Resizing(ResizeData { - edges, - initial_rect, - }) => Some((edges, initial_rect)), - Self::WaitingForCommit(ResizeData { - edges, - initial_rect, - }) => Some((edges, initial_rect)), - Self::WaitingForFinalAck( - ResizeData { - edges, - initial_rect, - }, - _, - ) => { - // The resize is done, let's go back to idle - *self = Self::Idle; - - Some((edges, initial_rect)) - } - Self::Idle => None, - } - } -} diff --git a/src/compositor/shell/grabs/mod.rs b/src/compositor/shell/grabs/mod.rs deleted file mode 100644 index a77bbc0..0000000 --- a/src/compositor/shell/grabs/mod.rs +++ /dev/null @@ -1,5 +0,0 @@ -mod grab_move; -mod grab_resize; - -pub use grab_move::*; -pub use grab_resize::*; diff --git a/src/compositor/shell/mod.rs b/src/compositor/shell/mod.rs deleted file mode 100644 index 18da357..0000000 --- a/src/compositor/shell/mod.rs +++ /dev/null @@ -1,340 +0,0 @@ -use std::cell::RefCell; - -use smithay::{ - backend::renderer::utils::on_commit_buffer_handler, - delegate_layer_shell, delegate_shm, delegate_viewporter, - desktop::{ - layer_map_for_output, space::SpaceElement, LayerSurface, PopupKind, PopupManager, Space, - WindowSurfaceType, - }, - output::Output, - reexports::wayland_server::protocol::{wl_buffer::WlBuffer, wl_output, wl_surface::WlSurface}, - utils::{Logical, Point, Rectangle, Size}, - wayland::{ - buffer::BufferHandler, - compositor::{ - get_parent, is_sync_subsurface, with_states, with_surface_tree_upward, - CompositorHandler, CompositorState, TraversalAction, - }, - shell::{ - wlr_layer::{ - Layer, LayerSurface as WlrLayerSurface, LayerSurfaceData, WlrLayerShellHandler, - WlrLayerShellState, - }, - xdg::{XdgPopupSurfaceData, XdgToplevelSurfaceData}, - }, - shm::ShmHandler, - }, - xwayland::X11Wm, -}; - -pub use self::avwindow::{AvWindow, AvWindowRenderElement}; -pub use self::grabs::handle_commit; -use self::grabs::ResizeState; - -use super::{ - backend::Backend, - state::{CalloopData, Navda}, -}; - -mod avwindow; -mod grabs; -mod x11; -mod xdg; - -#[derive(Default)] -pub struct SurfaceData { - pub geometry: Option>, - pub resize_state: ResizeState, -} - -fn fullscreen_output_geometry( - wl_surface: &WlSurface, - wl_output: Option<&wl_output::WlOutput>, - space: &mut Space, -) -> Option> { - // First test if a specific output has been requested - // if the requested output is not found ignore the request - wl_output - .and_then(Output::from_resource) - .or_else(|| { - let w = space - .elements() - .find(|window| { - window - .wl_surface() - .map(|s| s == *wl_surface) - .unwrap_or(false) - }) - .cloned(); - w.and_then(|w| space.outputs_for_element(&w).get(0).cloned()) - }) - .and_then(|o| space.output_geometry(&o)) -} - -#[derive(Default)] -pub struct FullscreenSurface(RefCell>); - -impl FullscreenSurface { - pub fn set(&self, window: AvWindow) { - *self.0.borrow_mut() = Some(window); - } - - pub fn get(&self) -> Option { - self.0.borrow().clone() - } - - pub fn clear(&self) -> Option { - self.0.borrow_mut().take() - } -} - -impl BufferHandler for Navda { - fn buffer_destroyed(&mut self, _buffer: &WlBuffer) {} -} - -impl CompositorHandler for Navda { - fn compositor_state(&mut self) -> &mut CompositorState { - &mut self.compositor_state - } - fn commit(&mut self, surface: &WlSurface) { - X11Wm::commit_hook::>(surface); - - on_commit_buffer_handler(surface); - self.backend_data.early_import(surface); - - if !is_sync_subsurface(surface) { - let mut root = surface.clone(); - while let Some(parent) = get_parent(&root) { - root = parent; - } - if let Some(AvWindow::Wayland(window)) = self.window_for_surface(&root) { - window.on_commit(); - } - } - self.popups.commit(surface); - handle_commit(&mut self.space, surface); - - ensure_initial_configure(surface, &self.space, &mut self.popups) - } -} - -impl WlrLayerShellHandler for Navda { - fn shell_state(&mut self) -> &mut WlrLayerShellState { - &mut self.layer_shell_state - } - - fn new_layer_surface( - &mut self, - surface: WlrLayerSurface, - wl_output: Option, - _layer: Layer, - namespace: String, - ) { - let output = wl_output - .as_ref() - .and_then(Output::from_resource) - .unwrap_or_else(|| self.space.outputs().next().unwrap().clone()); - let mut map = layer_map_for_output(&output); - map.map_layer(&LayerSurface::new(surface, namespace)) - .unwrap(); - } -} - -delegate_layer_shell!(@ Navda); - -impl Navda { - pub fn window_for_surface(&self, surface: &WlSurface) -> Option { - self.space - .elements() - .find(|window| window.wl_surface().map(|s| s == *surface).unwrap_or(false)) - .cloned() - } -} - -fn ensure_initial_configure( - surface: &WlSurface, - space: &Space, - popups: &mut PopupManager, -) { - with_surface_tree_upward( - surface, - (), - |_, _, _| TraversalAction::DoChildren(()), - |_, states, _| { - states - .data_map - .insert_if_missing(|| RefCell::new(SurfaceData::default())); - }, - |_, _, _| true, - ); - - if let Some(window) = space - .elements() - .find(|window| window.wl_surface().map(|s| s == *surface).unwrap_or(false)) - .cloned() - { - // send the initial configure if relevant - #[cfg_attr(not(feature = "xwayland"), allow(irrefutable_let_patterns))] - if let AvWindow::Wayland(ref toplevel) = window { - let initial_configure_sent = with_states(surface, |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - if !initial_configure_sent { - toplevel.toplevel().send_configure(); - } - } - - with_states(surface, |states| { - let mut data = states - .data_map - .get::>() - .unwrap() - .borrow_mut(); - - // Finish resizing. - if let ResizeState::WaitingForCommit(_) = data.resize_state { - data.resize_state = ResizeState::Idle; - } - }); - - return; - } - - if let Some(popup) = popups.find_popup(surface) { - let PopupKind::Xdg(ref popup) = popup; - let initial_configure_sent = with_states(surface, |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - if !initial_configure_sent { - // NOTE: This should never fail as the initial configure is always - // allowed. - popup.send_configure().expect("initial configure failed"); - } - - return; - }; - - if let Some(output) = space.outputs().find(|o| { - let map = layer_map_for_output(o); - map.layer_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .is_some() - }) { - let initial_configure_sent = with_states(surface, |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - - // send the initial configure if relevant - if !initial_configure_sent { - let mut map = layer_map_for_output(output); - - // arrange the layers before sending the initial configure - // to respect any size the client may have sent - map.arrange(); - - let layer = map - .layer_for_surface(surface, WindowSurfaceType::TOPLEVEL) - .unwrap(); - - layer.layer_surface().send_configure(); - } - }; -} - -impl ShmHandler for Navda { - fn shm_state(&self) -> &smithay::wayland::shm::ShmState { - &self.shm_state - } -} - -delegate_shm!(@ Navda); - -delegate_viewporter!(@ Navda); - -/// -/// Decides where to place a new window. -/// -fn place_new_window(space: &mut Space, window: &AvWindow, activate: bool) { - // TODO(Sammy99jsp): Change from `anvil`'s default below for a more fancy impl: - // place the window at a random location on the primary output - // or if there is not output in a [0;800]x[0;800] square - use rand::distributions::{Distribution, Uniform}; - - let output = space.outputs().next().cloned(); - let output_geometry = output - .and_then(|o| { - let geo = space.output_geometry(&o)?; - let map = layer_map_for_output(&o); - let zone = map.non_exclusive_zone(); - Some(Rectangle::from_loc_and_size(geo.loc + zone.loc, zone.size)) - }) - .unwrap_or_else(|| Rectangle::from_loc_and_size((0, 0), (800, 800))); - - let max_x = output_geometry.loc.x + (((output_geometry.size.w as f32) / 3.0) * 2.0) as i32; - let max_y = output_geometry.loc.y + (((output_geometry.size.h as f32) / 3.0) * 2.0) as i32; - let x_range = Uniform::new(output_geometry.loc.x, max_x); - let y_range = Uniform::new(output_geometry.loc.y, max_y); - let mut rng = rand::thread_rng(); - let x = x_range.sample(&mut rng); - let y = y_range.sample(&mut rng); - - space.map_element(window.clone(), (x, y), activate); -} - -pub fn fixup_positions(space: &mut Space) { - // fixup outputs - let mut offset = Point::::from((0, 0)); - for output in space.outputs().cloned().collect::>().into_iter() { - let size = space - .output_geometry(&output) - .map(|geo| geo.size) - .unwrap_or_else(|| Size::from((0, 0))); - space.map_output(&output, offset); - layer_map_for_output(&output).arrange(); - offset.x += size.w; - } - - // fixup windows - let mut orphaned_windows = Vec::new(); - let outputs = space - .outputs() - .flat_map(|o| { - let geo = space.output_geometry(o)?; - let map = layer_map_for_output(o); - let zone = map.non_exclusive_zone(); - Some(Rectangle::from_loc_and_size(geo.loc + zone.loc, zone.size)) - }) - .collect::>(); - for window in space.elements() { - let window_location = match space.element_location(window) { - Some(loc) => loc, - None => continue, - }; - let geo_loc = window.bbox().loc + window_location; - - if !outputs.iter().any(|o_geo| o_geo.contains(geo_loc)) { - orphaned_windows.push(window.clone()); - } - } - for window in orphaned_windows.into_iter() { - place_new_window(space, &window, false); - } -} diff --git a/src/compositor/shell/x11.rs b/src/compositor/shell/x11.rs deleted file mode 100644 index 87ce4af..0000000 --- a/src/compositor/shell/x11.rs +++ /dev/null @@ -1,345 +0,0 @@ -use std::cell::RefCell; - -use smithay::{ - desktop::space::SpaceElement, - input::pointer::Focus, - reexports::x11rb::protocol::xproto, - utils::{Logical, Rectangle, SERIAL_COUNTER}, - wayland::{compositor, shell::xdg::XdgShellHandler}, - xwayland::{ - xwm::{self, XwmId}, - X11Surface, X11Wm, XwmHandler, - }, -}; - -use crate::compositor::{ - backend::Backend, - state::{CalloopData, Navda}, -}; - -use super::{ - avwindow::AvWindow, - grabs::{MoveSurfaceGrab, ResizeData, ResizeState, ResizeSurfaceGrab}, - place_new_window, FullscreenSurface, SurfaceData, -}; - -#[derive(Debug, Default)] -struct OldGeometry(RefCell>>); -impl OldGeometry { - pub fn save(&self, geo: Rectangle) { - *self.0.borrow_mut() = Some(geo); - } - - pub fn restore(&self) -> Option> { - self.0.borrow_mut().take() - } -} - -/// -/// Implementing a XWayland server -/// -impl XwmHandler for CalloopData { - fn xwm_state(&mut self, xwm: XwmId) -> &mut X11Wm { - self.state.xwm.as_mut().unwrap() - } - - fn new_window(&mut self, xwm: XwmId, window: X11Surface) {} - - fn new_override_redirect_window(&mut self, xwm: XwmId, window: X11Surface) {} - - fn map_window_request(&mut self, xwm: XwmId, window: X11Surface) { - window.set_mapped(true).unwrap(); - - let window = AvWindow::X11(window); - - place_new_window(&mut self.state.space, &window, true); - let bbox = self.state.space.element_bbox(&window).unwrap(); - let AvWindow::X11(xsurface) = &window else { unreachable!() }; - xsurface.configure(Some(bbox)).unwrap(); - - // TODO: Server-side decorations - } - - fn mapped_override_redirect_window(&mut self, xwm: XwmId, window: X11Surface) { - let location = window.geometry().loc; - let window = AvWindow::X11(window); - self.state.space.map_element(window, location, true); - } - - fn unmapped_window(&mut self, xwm: XwmId, window: X11Surface) { - let maybe = self - .state - .space - .elements() - .find(|e| matches!(e, AvWindow::X11(w) if w == &window)) - .cloned(); - - if let Some(elem) = maybe { - self.state.space.unmap_elem(&elem); - } - if !window.is_override_redirect() { - window.set_mapped(false).unwrap(); - } - } - - fn destroyed_window(&mut self, xwm: XwmId, window: X11Surface) {} - - fn configure_request( - &mut self, - xwm: XwmId, - window: X11Surface, - _x: Option, - _y: Option, - w: Option, - h: Option, - _reorder: Option, - ) { - // From anvil: "set new size -- do not let window move - // themselves freely" - let mut geo = window.geometry(); - if let Some(w) = w { - geo.size.w = w as i32; - } - if let Some(h) = w { - geo.size.h = h as i32; - } - - let _ = window.configure(geo); - } - - fn configure_notify( - &mut self, - _xwm: XwmId, - window: X11Surface, - geometry: Rectangle, - _above: Option, - ) { - let Some(elem) = self.state.space - .elements().find(|e| - matches!(e, AvWindow::X11(w) if w == &window) - ).cloned() - else {return}; - - self.state.space.map_element(elem, geometry.loc, false); - - // TODO from `anvil`: - // "We don't properly handle the order of override-redirect windows here, - // they are always mapped top and then never reordered."" - } - - fn maximize_request(&mut self, xwm: XwmId, window: X11Surface) { - self.state.maximize_request_x11(&window); - } - - fn unmaximize_request(&mut self, xwm: XwmId, window: X11Surface) { - let Some(elem) = self - .state.space - .elements().find(|e| - matches!(e, AvWindow::X11(w) if w == &window) - ) - .cloned() - else { return }; - - window.set_maximized(false).unwrap(); - - if let Some(old_geo) = window - .user_data() - .get::() - .and_then(|data| data.restore()) - { - window.configure(old_geo).unwrap(); - self.state.space.map_element(elem, old_geo.loc, false); - } - } - - fn fullscreen_request(&mut self, xwm: XwmId, window: X11Surface) { - if let Some(elem) = self - .state - .space - .elements() - .find(|e| matches!(e, AvWindow::X11(w) if w == &window)) - { - let outputs_for_window = self.state.space.outputs_for_element(elem); - let output = outputs_for_window - .first() - // The window hasn't been mapped yet, use the primary output instead - .or_else(|| self.state.space.outputs().next()) - // Assumes that at least one output exists - .expect("No outputs found"); - let geometry = self.state.space.output_geometry(output).unwrap(); - - window.set_fullscreen(true).unwrap(); - window.configure(geometry).unwrap(); - output - .user_data() - .insert_if_missing(FullscreenSurface::default); - output - .user_data() - .get::() - .unwrap() - .set(elem.clone()); - slog::trace!(self.state.log, "Fullscreening: {:?}", elem); - } - } - - fn unfullscreen_request(&mut self, _xwm: XwmId, window: X11Surface) { - if let Some(elem) = self - .state - .space - .elements() - .find(|e| matches!(e, AvWindow::X11(w) if w == &window)) - { - window.set_fullscreen(false).unwrap(); - if let Some(output) = self.state.space.outputs().find(|o| { - o.user_data() - .get::() - .and_then(|f| f.get()) - .map(|w| &w == elem) - .unwrap_or(false) - }) { - slog::trace!(self.state.log, "Unfullscreening: {:?}", elem); - output - .user_data() - .get::() - .unwrap() - .clear(); - window - .configure(self.state.space.element_bbox(elem)) - .unwrap(); - self.state.backend_data.reset_buffers(output); - } - } - } - - fn resize_request( - &mut self, - xwm: XwmId, - window: X11Surface, - button: u32, - edges: xwm::ResizeEdge, - ) { - let seat = &self.state.seat; // TODO: Review if multiple seats are supported in the future... - let pointer = seat.get_pointer().unwrap(); - let start_data = pointer.grab_start_data().unwrap(); - - let Some(element) = self - .state - .space - .elements() - .find(|e| matches!(e, AvWindow::X11(w) if w == &window)) - else { return }; - - let geometry = element.geometry(); - let loc = self.state.space.element_location(element).unwrap(); - let (initial_window_location, initial_window_size) = (loc, geometry.size); - - compositor::with_states(&element.wl_surface().unwrap(), move |states| { - states - .data_map - .get::>() - .unwrap() - .borrow_mut() - .resize_state = ResizeState::Resizing(ResizeData { - edges: edges.into(), - initial_rect: Rectangle::from_loc_and_size( - initial_window_location, - initial_window_size, - ), - }); - }); - - let grab = ResizeSurfaceGrab { - start_data, - window: element.clone(), - edges: edges.into(), - initial_rect: Rectangle::from_loc_and_size( - initial_window_location, - initial_window_size, - ), - last_window_size: initial_window_size, - }; - - pointer.set_grab( - &mut self.state, - grab, - SERIAL_COUNTER.next_serial(), - Focus::Clear, - ); - } - - fn move_request(&mut self, xwm: XwmId, window: X11Surface, button: u32) { - self.state.move_request_x11(&window) - } -} - -impl Navda { - pub fn maximize_request_x11(&mut self, window: &X11Surface) { - let Some(elem) = self - .space - .elements() - .find(|e| matches!(e, AvWindow::X11(ref w) if w == window)) - .cloned() - else { return }; - - let old_geo = self.space.element_bbox(&elem).unwrap(); - let outputs_for_window = self.space.outputs_for_element(&elem); - let output = outputs_for_window - .first() - // The window hasn't been mapped yet, use the primary output instead - .or_else(|| self.space.outputs().next()) - // Assumes that at least one output exists - .expect("No outputs found"); - let geometry = self.space.output_geometry(output).unwrap(); - - window.set_maximized(true).unwrap(); - window.configure(geometry).unwrap(); - window.user_data().insert_if_missing(OldGeometry::default); - window - .user_data() - .get::() - .unwrap() - .save(old_geo); - self.space.map_element(elem, geometry.loc, false); - } - - pub fn move_request_x11(&mut self, window: &X11Surface) { - let seat = &self.seat; // luckily anvil only supports one seat anyway... - let pointer = seat.get_pointer().unwrap(); - let Some(start_data) = pointer.grab_start_data() else { return }; - - let Some(element) = self - .space - .elements() - .find(|e| matches!(e, AvWindow::X11(ref w) if w == window)) - else { return }; - - let mut initial_window_location = self.space.element_location(element).unwrap(); - - // If surface is maximized then unmaximize it - if window.is_maximized() { - window.set_maximized(false).unwrap(); - let pos = pointer.current_location(); - initial_window_location = (pos.x as i32, pos.y as i32).into(); - if let Some(old_geo) = window - .user_data() - .get::() - .and_then(|data| data.restore()) - { - window - .configure(Rectangle::from_loc_and_size( - initial_window_location, - old_geo.size, - )) - .unwrap(); - } - } - - let grab = MoveSurfaceGrab { - start_data, - window: element.clone(), - initial_window_location, - }; - - pointer.set_grab(self, grab, SERIAL_COUNTER.next_serial(), Focus::Clear); - } -} diff --git a/src/compositor/shell/xdg/decoration.rs b/src/compositor/shell/xdg/decoration.rs deleted file mode 100644 index 2370b81..0000000 --- a/src/compositor/shell/xdg/decoration.rs +++ /dev/null @@ -1,85 +0,0 @@ -//! -//! Server-Side Decorations -//! -//! This would be the place to introduce the Tabbing UI. -//! - -use smithay::{ - delegate_xdg_decoration, - reexports::wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode, - wayland::{ - compositor::with_states, - shell::xdg::{decoration::XdgDecorationHandler, ToplevelSurface, XdgToplevelSurfaceData}, - }, -}; - -use crate::compositor::{backend::Backend, shell::AvWindow, state::Navda}; - -impl XdgDecorationHandler for Navda { - fn new_decoration(&mut self, toplevel: ToplevelSurface) { - toplevel.with_pending_state(|state| { - state.decoration_mode = Some(Mode::ClientSide); - }); - toplevel.send_configure(); - } - fn request_mode(&mut self, toplevel: ToplevelSurface, mode: Mode) { - if let Some(_w) = self - .space - .elements() - .find(|window| matches!(window, AvWindow::Wayland(w) if w.toplevel() == &toplevel)) - { - toplevel.with_pending_state(|state| { - state.decoration_mode = Some(match mode { - Mode::ServerSide => { - // TODO(Sammy99jsp) Server-side decoration. - // w.set_ssd(true); - Mode::ServerSide - } - _ => { - // TODO(Sammy99jsp) Server-side decoration. - // w.set_ssd(false); - Mode::ClientSide - } - }); - }); - - let initial_configure_sent = with_states(toplevel.wl_surface(), |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - if initial_configure_sent { - toplevel.send_configure(); - } - } - } - fn unset_mode(&mut self, toplevel: ToplevelSurface) { - if let Some(w) = self - .space - .elements() - .find(|window| matches!(window, AvWindow::Wayland(w) if w.toplevel() == &toplevel)) - { - // w.set_ssd(false); - toplevel.with_pending_state(|state| { - state.decoration_mode = Some(Mode::ClientSide); - }); - let initial_configure_sent = with_states(toplevel.wl_surface(), |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .initial_configure_sent - }); - if initial_configure_sent { - toplevel.send_configure(); - } - } - } -} -delegate_xdg_decoration!(@ Navda); diff --git a/src/compositor/shell/xdg/handlers.rs b/src/compositor/shell/xdg/handlers.rs deleted file mode 100644 index 2ac86d9..0000000 --- a/src/compositor/shell/xdg/handlers.rs +++ /dev/null @@ -1,63 +0,0 @@ -//! -//! Collection of XDG handlers/Smithay delegations: -//! -//! * [xdg_activation](https://wayland.app/protocols/xdg-activation-v1) -//! * [xdg_shell](https://wayland.app/protocols/xdg-shell) -//! -//! -//! -use smithay::{ - delegate_xdg_activation, delegate_xdg_decoration, - reexports::{ - wayland_protocols::xdg::decoration::zv1::server::zxdg_toplevel_decoration_v1::Mode, - wayland_server::protocol::wl_surface::WlSurface, - }, - wayland::{ - compositor::with_states, - shell::xdg::{decoration::XdgDecorationHandler, ToplevelSurface, XdgToplevelSurfaceData}, - xdg_activation::{ - XdgActivationHandler, XdgActivationState, XdgActivationToken, XdgActivationTokenData, - }, - }, -}; - -use crate::compositor::{backend::Backend, shell::avwindow::AvWindow, state::Navda}; - -impl XdgActivationHandler for Navda { - fn activation_state(&mut self) -> &mut XdgActivationState { - &mut self.xdg_activation_state - } - - fn request_activation( - &mut self, - token: XdgActivationToken, - token_data: XdgActivationTokenData, - surface: WlSurface, - ) { - if token_data.timestamp.elapsed().as_secs() < 10 { - // Just grant the wish - let w = self - .space - .elements() - .find(|window| window.wl_surface().map(|s| s == surface).unwrap_or(false)) - .cloned(); - if let Some(window) = w { - self.space.raise_element(&window, true); - } - } else { - // Discard the request - self.xdg_activation_state.remove_request(&token); - } - } - - fn destroy_activation( - &mut self, - _token: XdgActivationToken, - _token_data: XdgActivationTokenData, - _surface: WlSurface, - ) { - // The request is cancelled - } -} -delegate_xdg_activation!(@ Navda); - diff --git a/src/compositor/shell/xdg/mod.rs b/src/compositor/shell/xdg/mod.rs deleted file mode 100644 index ee5796b..0000000 --- a/src/compositor/shell/xdg/mod.rs +++ /dev/null @@ -1,455 +0,0 @@ -//! -//! XDG-related implementations. -//! -//! See `impl XDGShellHandler for Navda<...>` for more information -//! on XDG Shell. -//! - -use std::cell::RefCell; - -use smithay::{ - delegate_xdg_shell, - desktop::{ - find_popup_root_surface, layer_map_for_output, space::SpaceElement, PopupKeyboardGrab, - PopupKind, PopupPointerGrab, PopupUngrabStrategy, Window, WindowSurfaceType, - }, - input::{pointer::Focus, Seat}, - output::Output, - reexports::{ - wayland_protocols::xdg::shell::server::xdg_toplevel::{self, ResizeEdge}, - wayland_server::{ - protocol::{ - wl_output, - wl_seat::{self, WlSeat}, - wl_surface, - }, - Resource, - }, - }, - utils::{Rectangle, Serial}, - wayland::{ - compositor::with_states, - seat::WaylandFocus, - shell::xdg::{ - self, PopupSurface, PositionerState, ToplevelSurface, XdgShellHandler, XdgShellState, - XdgToplevelSurfaceData, - }, - }, -}; - -use crate::compositor::{ - backend::Backend, focus::FocusTarget, shell::FullscreenSurface, state::Navda, -}; - -use super::{ - avwindow::AvWindow, - fullscreen_output_geometry, - grabs::{MoveSurfaceGrab, ResizeData, ResizeState, ResizeSurfaceGrab}, - place_new_window, SurfaceData, -}; - -mod decoration; -mod handlers; - -/// -/// Implementing the [XDG Shell](https://wayland.app/protocols/xdg-shell) protocol. -/// -/// Essentially, the XDG Shell protocol describes most of the graphical -/// features of a typical desktop OS, such as windows and popups. -/// -/// XDG Shell also provides events, and interfaces to handle -/// actions such as resizing and moving windows -- it is a protocol for -/// communicating between compositors and client applications. -/// -impl XdgShellHandler for Navda { - fn xdg_shell_state(&mut self) -> &mut XdgShellState { - &mut self.xdg_shell_state - } - - fn new_toplevel(&mut self, surface: ToplevelSurface) { - let window = AvWindow::Wayland(Window::new(surface)); - place_new_window(&mut self.space, &window, true); - } - - /// - /// [xdg_popup](https://wayland.app/protocols/xdg-shell#xdg_popup) - /// - /// - fn new_popup(&mut self, surface: PopupSurface, positioner: PositionerState) { - surface.with_pending_state(|state| { - state.geometry = positioner.get_geometry(); - }); - - if let Err(err) = self.popups.track_popup(PopupKind::from(surface)) { - slog::warn!(self.log, "Failed to track popup: {}", err); - } - } - - /// - /// Respond to a reposition request: - /// - /// https://wayland.app/protocols/xdg-shell#xdg_popup:request:reposition - /// - fn reposition_request( - &mut self, - surface: PopupSurface, - positioner: PositionerState, - token: u32, - ) { - surface.with_pending_state(|state| { - // TODO(@Sammy99jsp) Reference the follwing `anvil` comment: - // "This is again a simplification, a proper compositor would - // calculate the geometry of the popup here. For simplicity we just - // use the default implementation here that does not take the - // window position and output constraints into account." - let geo = positioner.get_geometry(); - state.geometry = geo; - state.positioner = positioner; - }); - surface.send_repositioned(token); - } - - fn move_request(&mut self, surface: ToplevelSurface, seat: wl_seat::WlSeat, serial: Serial) { - let seat: Seat = Seat::from_resource(&seat).unwrap(); - self.move_request_xdg(&surface, &seat, serial); - } - - /// - /// User-driven interactive surface resize. - /// - /// https://wayland.app/protocols/xdg-shell#xdg_toplevel:request:resize - /// - fn resize_request( - &mut self, - surface: ToplevelSurface, - seat: wl_seat::WlSeat, - serial: Serial, - edges: ResizeEdge, - ) { - let seat: Seat = Seat::from_resource(&seat).unwrap(); - // TODO(from Anvil): "Touch resize" - let pointer = seat.get_pointer().unwrap(); - - // Check if this surface has click grab. - if !pointer.has_grab(serial) { - return; - } - - let start_data = pointer.grab_start_data().unwrap(); - - let window = self.window_for_surface(surface.wl_surface()).unwrap(); - - // If focus is on a different surface, ignore. - if start_data.focus.is_none() - || !start_data - .focus - .as_ref() - .unwrap() - .0 - .same_client_as(&surface.wl_surface().id()) - { - return; - } - - let geo = window.geometry(); - let loc = self.space.element_location(&window).unwrap(); - let (initial_window_location, initial_window_size) = (loc, geo.size); - - with_states(surface.wl_surface(), move |states| { - states - .data_map - .get::>() - .unwrap() - .borrow_mut() - .resize_state = ResizeState::Resizing(ResizeData { - edges: edges.into(), - initial_rect: Rectangle::from_loc_and_size( - initial_window_location, - initial_window_size, - ), - }) - }); - - let grab = ResizeSurfaceGrab { - start_data, - window, - edges: edges.into(), - initial_rect: Rectangle::from_loc_and_size( - initial_window_location, - initial_window_size, - ), - last_window_size: initial_window_size, - }; - - pointer.set_grab(self, grab, serial, Focus::Clear); - } - - fn ack_configure(&mut self, surface: wl_surface::WlSurface, configure: xdg::Configure) { - if let xdg::Configure::Toplevel(configure) = configure { - if let Some(serial) = with_states(&surface, |states| { - if let Some(data) = states.data_map.get::>() { - if let ResizeState::WaitingForFinalAck(_, serial) = data.borrow().resize_state { - return Some(serial); - } - } - - None - }) { - // NOTE: From `anvil`: - // When the resize grab is released the surface - // resize state will be set to WaitingForFinalAck - // and the client will receive a configure request - // without the resize state to inform the client - // resizing has finished. Here we will wait for - // the client to acknowledge the end of the - // resizing. To check if the surface was resizing - // before sending the configure we need to use - // the current state as the received acknowledge - // will no longer have the resize state set - let is_resizing = with_states(&surface, |states| { - states - .data_map - .get::() - .unwrap() - .lock() - .unwrap() - .current - .states - .contains(xdg_toplevel::State::Resizing) - }); - - if configure.serial >= serial && is_resizing { - with_states(&surface, |states| { - let mut data = states - .data_map - .get::>() - .unwrap() - .borrow_mut(); - if let ResizeState::WaitingForFinalAck(resize_data, _) = data.resize_state { - data.resize_state = ResizeState::WaitingForCommit(resize_data); - } else { - unreachable!() - } - }); - } - } - } - } - - fn fullscreen_request( - &mut self, - surface: ToplevelSurface, - mut wl_output: Option, - ) { - // TODO(Sammy99jsp) review comment from `anvil`: - // NOTE: This is only one part of the solution. We can set the - // location and configure size here, but the surface should be rendered fullscreen - // independently from its buffer size - let wl_surface = surface.wl_surface(); - - let output_geometry = - fullscreen_output_geometry(wl_surface, wl_output.as_ref(), &mut self.space); - - if let Some(geometry) = output_geometry { - let output = wl_output - .as_ref() - .and_then(Output::from_resource) - .unwrap_or_else(|| self.space.outputs().next().unwrap().clone()); - let client = self.display_handle.get_client(wl_surface.id()).unwrap(); - for output in output.client_outputs(&client) { - wl_output = Some(output); - } - let window = self - .space - .elements() - .find(|window| { - window - .wl_surface() - .map(|s| s == *wl_surface) - .unwrap_or(false) - }) - .unwrap(); - - surface.with_pending_state(|state| { - state.states.set(xdg_toplevel::State::Fullscreen); - state.size = Some(geometry.size); - state.fullscreen_output = wl_output; - }); - surface.send_configure(); - output - .user_data() - .insert_if_missing(FullscreenSurface::default); - output - .user_data() - .get::() - .unwrap() - .set(window.clone()); - slog::trace!(self.log, "Fullscreening: {:?}", window); - } - } - - fn unfullscreen_request(&mut self, surface: ToplevelSurface) { - let ret = surface.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Fullscreen); - state.size = None; - state.fullscreen_output.take() - }); - if let Some(output) = ret { - let output = Output::from_resource(&output).unwrap(); - if let Some(fullscreen) = output.user_data().get::() { - slog::trace!(self.log, "Unfullscreening: {:?}", fullscreen.get()); - fullscreen.clear(); - self.backend_data.reset_buffers(&output); - } - - surface.send_configure(); - } - } - - fn maximize_request(&mut self, surface: ToplevelSurface) { - // NOTE from `anvil`: This should use layer-shell when it is implemented to - // get the correct maximum size - let window = self.window_for_surface(surface.wl_surface()).unwrap(); - let outputs_for_window = self.space.outputs_for_element(&window); - let output = outputs_for_window - .first() - // The window hasn't been mapped yet, use the primary output instead - .or_else(|| self.space.outputs().next()) - // Assumes that at least one output exists - .expect("No outputs found"); - let geometry = self.space.output_geometry(output).unwrap(); - - surface.with_pending_state(|state| { - state.states.set(xdg_toplevel::State::Maximized); - state.size = Some(geometry.size); - }); - surface.send_configure(); - self.space.map_element(window, geometry.loc, true); - } - - fn unmaximize_request(&mut self, surface: ToplevelSurface) { - surface.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Maximized); - state.size = None; - }); - surface.send_configure(); - } - - fn grab(&mut self, surface: PopupSurface, seat: WlSeat, serial: Serial) { - let seat: Seat> = Seat::from_resource(&seat).unwrap(); - let kind = PopupKind::Xdg(surface); - if let Some(root) = find_popup_root_surface(&kind).ok().and_then(|root| { - self.space - .elements() - .find(|w| w.wl_surface().map(|s| s == root).unwrap_or(false)) - .cloned() - .map(FocusTarget::Window) - .or_else(|| { - self.space - .outputs() - .find_map(|o| { - let map = layer_map_for_output(o); - map.layer_for_surface(&root, WindowSurfaceType::TOPLEVEL) - .cloned() - }) - .map(FocusTarget::LayerSurface) - }) - }) { - let ret = self.popups.grab_popup(root, kind, &seat, serial); - - if let Ok(mut grab) = ret { - if let Some(keyboard) = seat.get_keyboard() { - if keyboard.is_grabbed() - && !(keyboard.has_grab(serial) - || keyboard.has_grab(grab.previous_serial().unwrap_or(serial))) - { - grab.ungrab(PopupUngrabStrategy::All); - return; - } - keyboard.set_focus(self, grab.current_grab(), serial); - keyboard.set_grab(PopupKeyboardGrab::new(&grab), serial); - } - if let Some(pointer) = seat.get_pointer() { - if pointer.is_grabbed() - && !(pointer.has_grab(serial) - || pointer - .has_grab(grab.previous_serial().unwrap_or_else(|| grab.serial()))) - { - grab.ungrab(PopupUngrabStrategy::All); - return; - } - pointer.set_grab(self, PopupPointerGrab::new(&grab), serial, Focus::Keep); - } - } - } - } -} - -delegate_xdg_shell!(@ Navda); - -impl Navda { - fn move_request_xdg(&mut self, surface: &ToplevelSurface, seat: &Seat, serial: Serial) { - // TODO: touch move. - let pointer = seat.get_pointer().unwrap(); - - // Check that this surface has a click grab. - if !pointer.has_grab(serial) { - return; - } - - let start_data = pointer.grab_start_data().unwrap(); - - let window = self.window_for_surface(surface.wl_surface()).unwrap(); - - // If the focus was for a different surface, ignore the request. - if start_data.focus.is_none() - || !start_data - .focus - .as_ref() - .unwrap() - .0 - .same_client_as(&surface.wl_surface().id()) - { - return; - } - - let mut initial_window_location = self.space.element_location(&window).unwrap(); - - // If surface is maximized then unmaximize it - let current_state = surface.current_state(); - if current_state - .states - .contains(xdg_toplevel::State::Maximized) - { - surface.with_pending_state(|state| { - state.states.unset(xdg_toplevel::State::Maximized); - state.size = None; - }); - - surface.send_configure(); - - // TODO(Sammy99jsp) review below: - // NOTE: In real compositor mouse location should be mapped to a new window size - // For example, you could: - // 1) transform mouse pointer position from compositor space to window space (location relative) - // 2) divide the x coordinate by width of the window to get the percentage - // - 0.0 would be on the far left of the window - // - 0.5 would be in middle of the window - // - 1.0 would be on the far right of the window - // 3) multiply the percentage by new window width - // 4) by doing that, drag will look a lot more natural - // - // but for anvil needs setting location to pointer location is fine - let pos = pointer.current_location(); - initial_window_location = (pos.x as i32, pos.y as i32).into(); - } - - let grab = MoveSurfaceGrab { - start_data, - window, - initial_window_location, - }; - - pointer.set_grab(self, grab, serial, Focus::Clear); - } -} diff --git a/src/compositor/state/mod.rs b/src/compositor/state/mod.rs deleted file mode 100644 index a4f7134..0000000 --- a/src/compositor/state/mod.rs +++ /dev/null @@ -1,321 +0,0 @@ -//! -//! Collection of state structs for the compositor. -//! - -mod utils; - -pub use utils::*; - -use std::sync::{atomic::AtomicBool, Arc, Mutex}; - -use smithay::{ - delegate_compositor, - desktop::{PopupManager, Space}, - input::{pointer::CursorImageStatus, Seat, SeatState}, - reexports::{ - calloop::LoopHandle, - wayland_server::{ - backend::{ClientData, ClientId, DisconnectReason}, - protocol::wl_surface::WlSurface, - Display, DisplayHandle, - }, - }, - utils::{Clock, Logical, Monotonic, Point}, - wayland::{ - compositor::CompositorState, - data_device::DataDeviceState, - fractional_scale::FractionalScaleManagerState, - keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, - output::OutputManagerState, - presentation::PresentationState, - primary_selection::PrimarySelectionState, - shell::{ - wlr_layer::WlrLayerShellState, - xdg::{decoration::XdgDecorationState, XdgShellState}, - }, - shm::ShmState, - viewporter::ViewporterState, - xdg_activation::XdgActivationState, - }, - xwayland::{X11Wm, XWayland}, -}; - -use super::{backend::Backend, shell::AvWindow}; - -/// -/// State for a client (application). -/// -#[derive(Debug, Default)] -pub struct ClientState; -impl ClientData for ClientState { - /// Notification that a client was initialized - fn initialized(&self, _client_id: ClientId) {} - /// Notification that a client is disconnected - fn disconnected(&self, _client_id: ClientId, _reason: DisconnectReason) {} -} - -/// -/// State for callbacks in the event loop. -/// -pub struct CalloopData { - pub state: Navda, - pub display: Display>, -} - -#[derive(Debug)] -/// -/// State for Navda's Wayland compositor. -/// -pub struct Navda { - /// - /// State associated with a particular backend - /// (e.g. `udev`, `winit`) - /// - pub backend_data: BEnd, - - /// - /// Wayland socket name (e.g. "wayland-0"). - /// - /// We'll always be listening, so no Option<...> - /// like anvil. - /// - pub socket_name: String, - - /// - /// Clone-able handle for the Wayland Display. - /// - pub display_handle: DisplayHandle, - - /// - /// Is the compositor running? - /// - pub running: Arc, - - /// - /// Handle to the main event loop. - /// - pub handle: LoopHandle<'static, CalloopData>, - - // - /// - /// 2D Plane for elements to map upon. - /// - /// Things like Windows, Widgets can live - /// here. - /// - pub space: Space, - - /// - /// Helper for popups. - /// - pub popups: PopupManager, - - // - - // - /// - /// Smithay's internal state for compositors. - /// - pub compositor_state: CompositorState, - - /// - /// Handles state of data devices. - /// - /// Data devices are Wayland's answer to clipboards - /// and drag-and-drop. - /// - /// See the [Wayland Documentation](https://wayland.freedesktop.org/docs/html/ch04.html#:~:text=Data%20devices%20glue%20data) - /// - pub data_device_state: DataDeviceState, - - /// - /// Allows for the request of all known - /// surfaces to the shell. - /// - pub layer_shell_state: WlrLayerShellState, - - /// - /// Smithay's state for managing outputs - /// with the XDG spec. - /// - /// See more about xdg_output_manager - /// on [Wayland.app](https://wayland.app/protocols/xdg-output-unstable-v1). - /// - pub output_manager_state: OutputManagerState, - - /// - /// State for the primary selection. - /// - /// See more about the PRIMARY selection - /// on the [Arch Wiki](https://wiki.archlinux.org/title/Clipboard#:~:text=PRIMARY). - /// - pub primary_selection_state: PrimarySelectionState, - - /// - /// State for all Seat globals. - /// - /// See `Navda.seat` for more information on seats. - /// - pub seat_state: SeatState, - - /// - /// Smithay state for the Keyboard shortcuts inhibit - /// protocol. - /// - /// > "This protocol specifies a way for a client to request the compositor - /// > to ignore its own keyboard shortcuts for a given seat, - /// > so that all key events from that seat get forwarded to a surface." - /// - /// See more on [Wayland.app](https://wayland.app/protocols/keyboard-shortcuts-inhibit-unstable-v1) - /// - pub keyboard_shortcuts_inhibit_state: KeyboardShortcutsInhibitState, - - /// - /// Smithay state for handling - /// Shared Memory (shm) between the compositor - /// and clients. - /// - /// See more on [The Wayland Book](https://wayland-book.com/surfaces/shared-memory.html). - /// - pub shm_state: ShmState, - - // TODO (@Sammy99jsp) Review need for this state. - /// - /// Smithay state for the Viewporter protocol. - /// - /// The wp_viewporter interface allows for an extension - /// interface for `wl_surface` (Wayland Surface) objects - /// which allows them to be cropped and scaled. - /// - /// See more on [Wayland.app](https://wayland.app/protocols/viewporter). - /// - pub viewporter_state: ViewporterState, - - /// - /// Smithay state for the XDG activation protocol, - /// which allows clients to pass focus to another toplevel surface. - /// - /// See more on [Wayland.app](https://wayland.app/protocols/xdg-activation-v1). - /// - pub xdg_activation_state: XdgActivationState, - - /// - /// Smithay state for the XDG decoration protocol, - /// which allows for a compositor to notify clients that - /// it supports drawing window decorations (e.g. title bar, minimize, maximize, etc.) - /// - /// See more on [Wayland.app](https://wayland.app/protocols/xdg-decoration-unstable-v1). - /// - /// - pub xdg_decoration_state: XdgDecorationState, - - /// - /// Smithay state for XDG Shell protocol, - /// which outlines some components typically - /// associated with desktop OS Shells (e.g. Windows, Popups, etc.) - /// - /// See more on [Wayland.app](https://wayland.app/protocols/xdg-shell). - /// - pub xdg_shell_state: XdgShellState, - - /// - /// Smithay state for the Presentation time protocol, - /// which allows for the compositor to give feedback to surfaces - /// to ensure that video playback is smooth and in sync with audio. - /// - /// See more on [Wayland.app](https://wayland.app/protocols/presentation-time). - /// - pub presentation_state: PresentationState, - - /// - /// Smithay state for the Fractional scale protocol, - /// which allows the compositor to suggest for surfaces - /// to render at a fractional scales (e.g. 1.5, 0.75, etc.);. - /// - pub fractional_scale_manager_state: FractionalScaleManagerState, - - // - /// - /// Logger for the compositor. - /// - pub log: slog::Logger, - - // - - // TODO(Sammy99jsp) : `Navda.suppressed_keys` description. - /// - /// ... - /// - pub suppressed_keys: Vec, - - /// - /// Location of the pointer. - /// - pub pointer_location: Point, - - /// - /// Status of the cursor: - /// * Hidden - /// * Default (compositor's own cursor) - /// * Surface — the compositor should render - /// this surface as the cursor. - /// - pub cursor_status: Arc>, - - // TODO(Sammy99jsp) : `Navda.seat_name` description. - /// - pub seat_name: String, - - /// - /// Smithay Seat handle. - /// - /// Wayland seats can abstract over a group of - /// up to one keyboard and pointer device each - /// (and other configs for different devices). - /// - /// See more in [The Wayland Book](https://wayland-book.com/seat.html) - /// - /// - // NOTE: Multiple keyboard support, lol ? - pub seat: Seat, - - /// - /// Compositor's Monotonic (no jumps in time) - /// Clock. - /// - pub clock: Clock, - - /// - /// Icon for drag and drop. - /// - pub dnd_icon: Option, - - // - - // - /// - /// - /// Handle for XWayland, - /// which allows Wayland to interop - /// with X11 Clients. - /// - /// See more [here](https://wayland.freedesktop.org/xserver.html). - /// - pub xwayland: XWayland, - - /// - /// Runtime state of the XWayland window manager. - /// - /// See more on Window Managers on the - /// [Arch Wiki](https://wiki.archlinux.org/title/window_manager). - /// - pub xwm: Option, - - /// - /// XWayland display id (for environment variables) - /// - pub x_display: Option, - // -} - -delegate_compositor!(@ Navda); diff --git a/src/compositor/state/utils.rs b/src/compositor/state/utils.rs deleted file mode 100644 index 4e78bbd..0000000 --- a/src/compositor/state/utils.rs +++ /dev/null @@ -1,300 +0,0 @@ -use std::{ - os::unix::prelude::AsRawFd, - sync::{atomic::AtomicBool, Arc, Mutex}, - time::Duration, -}; - -use smithay::{ - backend::renderer::element::{default_primary_scanout_output_compare, RenderElementStates}, - desktop::{ - utils::{ - surface_presentation_feedback_flags_from_states, surface_primary_scanout_output, - update_surface_primary_scanout_output, OutputPresentationFeedback, - }, - PopupManager, Space, - }, - input::{keyboard::XkbConfig, pointer::CursorImageStatus, SeatState}, - output::Output, - reexports::{ - calloop::{generic::Generic, Interest, LoopHandle, Mode, PostAction}, - wayland_server::Display, - }, - utils::{Clock, Point, Size}, - wayland::{ - compositor::CompositorState, - data_device::DataDeviceState, - fractional_scale::{with_fractional_scale, FractionalScaleManagerState}, - input_method::{InputMethodManagerState, InputMethodSeat}, - keyboard_shortcuts_inhibit::KeyboardShortcutsInhibitState, - output::OutputManagerState, - presentation::PresentationState, - primary_selection::PrimarySelectionState, - shell::{ - wlr_layer::WlrLayerShellState, - xdg::{decoration::XdgDecorationState, XdgShellState}, - }, - shm::ShmState, - socket::ListeningSocketSource, - tablet_manager::TabletSeatTrait, - text_input::TextInputManagerState, - viewporter::ViewporterState, - virtual_keyboard::VirtualKeyboardManagerState, - xdg_activation::XdgActivationState, - }, - xwayland::{X11Wm, XWayland, XWaylandEvent}, -}; - -use crate::compositor::{ - backend::Backend, components::Cursor, shell::AvWindow, state::ClientState, -}; - -use super::{CalloopData, Navda}; - -impl Navda { - pub fn new( - display: &mut Display>, - handle: LoopHandle<'static, CalloopData>, - backend_data: BEnd, - log: slog::Logger, - ) -> Self { - let clock = Clock::new().expect("failed to initialize clock"); - - // init wayland clients - let socket_name = { - let source = ListeningSocketSource::new_auto(log.clone()).unwrap(); - let socket_name = source.socket_name().to_string_lossy().into_owned(); - handle - .insert_source(source, |client_stream, _, data| { - if let Err(err) = data - .display - .handle() - .insert_client(client_stream, Arc::new(ClientState)) - { - slog::warn!(data.state.log, "Error adding wayland client: {}", err); - }; - }) - .expect("Failed to init wayland socket source"); - slog::info!(log, "Listening on wayland socket"; "name" => socket_name.clone()); - - socket_name - }; - - handle - .insert_source( - Generic::new( - display.backend().poll_fd().as_raw_fd(), - Interest::READ, - Mode::Level, - ), - |_, _, data| { - data.display.dispatch_clients(&mut data.state).unwrap(); - Ok(PostAction::Continue) - }, - ) - .expect("Failed to init wayland server source"); - - // init globals - let dh = display.handle(); - let compositor_state = CompositorState::new::(&dh, log.clone()); - let data_device_state = DataDeviceState::new::(&dh, log.clone()); - let layer_shell_state = WlrLayerShellState::new::(&dh, log.clone()); - let output_manager_state = OutputManagerState::new_with_xdg_output::(&dh); - let primary_selection_state = PrimarySelectionState::new::(&dh, log.clone()); - let mut seat_state = SeatState::new(); - let shm_state = ShmState::new::(&dh, vec![], log.clone()); - let viewporter_state = ViewporterState::new::(&dh, log.clone()); - let xdg_activation_state = XdgActivationState::new::(&dh, log.clone()); - let xdg_decoration_state = XdgDecorationState::new::(&dh, log.clone()); - let xdg_shell_state = XdgShellState::new::(&dh, log.clone()); - let presentation_state = PresentationState::new::(&dh, clock.id() as u32); - let fractional_scale_manager_state = - FractionalScaleManagerState::new::(&dh, log.clone()); - TextInputManagerState::new::(&dh); - InputMethodManagerState::new::(&dh); - VirtualKeyboardManagerState::new::(&dh, |_client| true); - - // init input - let seat_name = backend_data.seat_name(); - let mut seat = seat_state.new_wl_seat(&dh, seat_name.clone(), log.clone()); - - let cursor_status = Arc::new(Mutex::new(CursorImageStatus::Default)); - seat.add_pointer(); - // TODO: Add keyboard layout support here: - seat.add_keyboard(XkbConfig::default(), 200, 25) - .expect("Failed to initialize the keyboard"); - - let cursor_status2 = cursor_status.clone(); - seat.tablet_seat() - .on_cursor_surface(move |_tool, new_status| { - // TODO: tablet tools should have their own cursors - *cursor_status2.lock().unwrap() = new_status; - }); - - seat.add_input_method(XkbConfig::default(), 200, 25); - - let dh = display.handle(); - let keyboard_shortcuts_inhibit_state = KeyboardShortcutsInhibitState::new::(&dh); - - let xwayland = { - let (xwayland, channel) = XWayland::new(log.clone(), &dh); - let log2 = log.clone(); - let ret = handle.insert_source(channel, move |event, _, data| match event { - XWaylandEvent::Ready { - connection, - client, - client_fd: _, - display, - } => { - let mut wm = X11Wm::start_wm( - data.state.handle.clone(), - dh.clone(), - connection, - client, - log2.clone(), - ) - .expect("Failed to attach X11 Window Manager"); - let cursor = Cursor::load(&log2); - let image = cursor.get_image(1, Duration::ZERO); - wm.set_cursor( - &image.pixels_rgba, - Size::from((image.width as u16, image.height as u16)), - Point::from((image.xhot as u16, image.yhot as u16)), - ) - .expect("Failed to set xwayland default cursor"); - data.state.xwm = Some(wm); - data.state.x_display = Some(display); - } - XWaylandEvent::Exited => { - let _ = data.state.xwm.take(); - } - }); - if let Err(e) = ret { - slog::error!( - log, - "Failed to insert the XWaylandSource into the event loop: {}", - e - ); - } - xwayland - }; - - Navda { - backend_data, - display_handle: display.handle(), - socket_name, - running: Arc::new(AtomicBool::new(true)), - handle, - space: Space::new(log.clone()), - popups: PopupManager::new(log.clone()), - compositor_state, - data_device_state, - layer_shell_state, - output_manager_state, - primary_selection_state, - seat_state, - keyboard_shortcuts_inhibit_state, - shm_state, - viewporter_state, - xdg_activation_state, - xdg_decoration_state, - xdg_shell_state, - presentation_state, - fractional_scale_manager_state, - dnd_icon: None, - log, - suppressed_keys: Vec::new(), - pointer_location: (0.0, 0.0).into(), - cursor_status, - seat_name, - seat, - clock, - xwayland, - xwm: None, - x_display: None, - } - } -} - -pub fn post_repaint( - output: &Output, - render_element_states: &RenderElementStates, - space: &Space, - time: impl Into, -) { - let time = time.into(); - let throttle = Some(Duration::from_secs(1)); - - space.elements().for_each(|window| { - window.with_surfaces(|surface, states| { - let primary_scanout_output = update_surface_primary_scanout_output( - surface, - output, - states, - render_element_states, - default_primary_scanout_output_compare, - ); - - if let Some(output) = primary_scanout_output { - with_fractional_scale(states, |fraction_scale| { - fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); - }); - } - }); - - if space.outputs_for_element(window).contains(output) { - window.send_frame(output, time, throttle, surface_primary_scanout_output); - } - }); - let map = smithay::desktop::layer_map_for_output(output); - for layer_surface in map.layers() { - layer_surface.with_surfaces(|surface, states| { - let primary_scanout_output = update_surface_primary_scanout_output( - surface, - output, - states, - render_element_states, - default_primary_scanout_output_compare, - ); - - if let Some(output) = primary_scanout_output { - with_fractional_scale(states, |fraction_scale| { - fraction_scale.set_preferred_scale(output.current_scale().fractional_scale()); - }); - } - }); - - layer_surface.send_frame(output, time, throttle, surface_primary_scanout_output); - } -} - -pub fn take_presentation_feedback( - output: &Output, - space: &Space, - render_element_states: &RenderElementStates, -) -> OutputPresentationFeedback { - let mut output_presentation_feedback = OutputPresentationFeedback::new(output); - - space.elements().for_each(|window| { - if space.outputs_for_element(window).contains(output) { - window.take_presentation_feedback( - &mut output_presentation_feedback, - surface_primary_scanout_output, - |surface, _| { - surface_presentation_feedback_flags_from_states(surface, render_element_states) - }, - ); - } - }); - let map = smithay::desktop::layer_map_for_output(output); - for layer_surface in map.layers() { - layer_surface.take_presentation_feedback( - &mut output_presentation_feedback, - surface_primary_scanout_output, - |surface, _| { - surface_presentation_feedback_flags_from_states(surface, render_element_states) - }, - ); - } - - output_presentation_feedback -} diff --git a/src/config/config.rs b/src/config/config.rs deleted file mode 100644 index c993919..0000000 --- a/src/config/config.rs +++ /dev/null @@ -1,128 +0,0 @@ -use crate::{ - config::errors::UnexpectedToken, - CONST::{CONFIG_FILE, CONFIG_FOLDER}, -}; - -pub(crate) use json_comments::StripComments; - -use lazy_static::lazy_static; - -use json_tree::{parser::Token, Index, Indexer, JSONPath, Location, Source, Tokenizer, Value}; - -use serde::Deserialize; - -use std::{ - collections::HashMap, - error::Error, - fs::{self, File}, - io::BufReader, - path::PathBuf, -}; - -use super::sections::keybinds::Keybinds; - -lazy_static! { - pub static ref PATH: PathBuf = CONFIG_FOLDER.join(*CONFIG_FILE); -} - -static mut INDEX: Option = None; - -static mut CONFIG: Option = None; - -#[derive(Deserialize, Debug)] -pub struct Config { - pub keybinds: Keybinds, -} - -impl Config { - pub fn path() -> String { - PATH.to_str().unwrap().to_string() - } - - /// - /// Returns the config file's JSON index. - /// - pub fn index<'a>() -> &'a Index { - unsafe { INDEX.as_ref().unwrap() } - } - - /// - /// Returns the Global Configuration Object. - /// - pub fn config<'a>() -> &'a Self { - unsafe { CONFIG.as_ref().unwrap() } - } - - /// - /// Loads the config. - /// - /// A CALL TO THIS FUNCTION SHOULD BE NEAR THE TOP OF `main.rs` - /// - pub fn load() -> Result<(), Box> { - // Recursively crate config dir if it doesn't exist. - fs::create_dir_all(&*CONFIG_FOLDER) - .expect("Could not create config folder '$XDG_CONFIG_HOME/avdan'."); - - let file: File = match fs::OpenOptions::new().read(true).open(&*PATH) { - Err(_) => { - // File probs doesn't exist - let default = include_str!("../../DefaultConfig.jsonc"); - fs::write(&*PATH, default) - .expect(&format!("{} not writeable!", PATH.to_str().unwrap())); - - fs::OpenOptions::new() - .read(true) - .open(&*PATH) - .expect("Couldn't read newly-written default config!") - } - Ok(o) => o, - }; - - let reader: BufReader = BufReader::new(file); - - let stripped: StripComments<_> = StripComments::new(reader); - - let src_map: HashMap = { - let mut src: Source = Source::new( - // TODO: @Sammy99jsp Prettier Error - fs::read_to_string(&*PATH).expect("Missing AvdanOS config!"), - ); - - let tokens: Vec = match Tokenizer::tokenize(&mut src) { - Ok(tkns) => tkns, - Err(err) => { - let a: UnexpectedToken = UnexpectedToken::from_parser(err); - - println!("{}", a); - - panic!() - } - }; - let root: Value = match Value::parse(&mut tokens.iter().peekable()) { - Ok(r) => r, - - Err(err) => { - let a: UnexpectedToken = UnexpectedToken::from_parser(err); - - println!("{}", a); - - panic!() - } - }; - - let mut index: HashMap = HashMap::new(); - - Indexer::index(&root, &mut index, None); - - index - }; - - unsafe { INDEX = Some(src_map) } - - let o: Config = serde_json::from_reader(stripped)?; - - unsafe { CONFIG = Some(o) } - - Ok(()) - } -} diff --git a/src/config/errors.rs b/src/config/errors.rs deleted file mode 100644 index cde6597..0000000 --- a/src/config/errors.rs +++ /dev/null @@ -1,50 +0,0 @@ -use colored::Colorize; - -use compositor_macros::AvError; - -use crate::core::error::{AvError, Traceable, TraceableError}; - -use json_tree::{ParserError, TokenContent}; - -use super::config; - -/// -/// Error in parsing the config file. -/// -#[AvError(TraceableError, CONFIG_UNEXPECTED_TOKEN, "Config: Unexpected Token")] -pub struct UnexpectedToken(pub String, pub Traceable); - -impl UnexpectedToken { - pub fn from_parser(p: ParserError) -> Self - where - T: Into, - { - match p { - ParserError::UnexpectedToken(t) => { - let to: TokenContent = t.into(); - - let loc: Traceable = - Traceable::new(config::PATH.to_str().unwrap().to_string(), to.loc()); - - // Convert to our friendlier error format - UnexpectedToken( - // rust-analyzer (and I guess cargo check) gives up on the line below - // but it *does* compile -- smh my head - to.content, loc, - ) - } - - ParserError::UnexpectedEnd(_, _) => unimplemented!(), - } - } -} - -impl TraceableError for UnexpectedToken { - fn location(&self) -> &Traceable { - &self.1 - } - - fn description(&self) -> String { - format!("Unexpected token `{}`", self.0.blue()) - } -} diff --git a/src/config/mod.rs b/src/config/mod.rs deleted file mode 100644 index 86a112a..0000000 --- a/src/config/mod.rs +++ /dev/null @@ -1,8 +0,0 @@ -pub mod config; -pub mod errors; -pub mod section; -pub mod sections; -pub mod templating; - -pub use config::Config; -pub use section::ConfigurationSection; diff --git a/src/config/section.rs b/src/config/section.rs deleted file mode 100644 index 25b46c8..0000000 --- a/src/config/section.rs +++ /dev/null @@ -1,199 +0,0 @@ -use colored::Colorize; - -use compositor_macros::{description, location, AvError}; - -use crate::{ - config::{ - config::{self, Config}, - templating::{avvalue::AvValue, r#macro::SignatureMismatchError, AvMacro}, - }, - core::error::compare_errors, - core::error::{AvError, Traceable, TraceableError}, -}; - -use json_tree::{JSONPath, Location}; - -use std::collections::HashMap; -pub trait ConfigurationSection: Sized { - /// - /// The absolute path to this section as a str. - /// - const PATH: &'static str; - - /// - /// Returns the absolute path to this section. - /// Can be used in finding location of data. - /// - fn path() -> JSONPath { - Self::PATH.to_string().try_into().unwrap() - } - - /// - /// Returns this section's traceable. - /// - fn traceable(key: Option) -> Traceable { - let loc: &Location = Config::index().get(&Self::path()).unwrap(); - - Traceable::combine(&config::PATH.to_str().unwrap().to_string(), loc, key) - } - - fn from_map( - declared: HashMap, - raw: HashMap, - ) -> HashMap { - let path: JSONPath = Self::path(); - - let res = raw.iter().map(|(k, v)| { - // Parse as a macro. - let p: JSONPath = path.push(k.clone()); - let loc: &Location = Config::index().get(&p).unwrap(); - let k_pos: Traceable = Traceable::combine(&Config::path(), loc, Some(true)); - let v_pos: Traceable = Traceable::combine(&Config::path(), loc, Some(false)); - - (AvMacro::parse(k_pos.clone(), k.clone()), v, k_pos, v_pos) - }); - - // Syntactically invalid macros. - - let mut errors: Vec<(Box, Traceable)> = vec![]; - - res.clone() - .filter(|(k, _, _, _)| k.is_err()) - .for_each(|(k, _, p, _)| { - let n: Vec> = k.unwrap_err(); - - for err in n { - errors.push((err, p.clone())) - } - }); - - // Syntactically Valid macros - let defined = res - .filter(|(k, _, _, _)| k.is_ok()) - .map(|(k, v, p1, p2)| (k.unwrap(), v, p1, p2)); - - let mut output: HashMap = HashMap::new(); - - let mut found_macros: Vec = vec![]; - // Look up the valid macros against our declared HashMap. - - for (declared_m, default_v) in declared { - let defined_m: Option = defined - .clone() - .position(|(m, _, _, _)| m.identifier() == declared_m.identifier()); - - let (avmacro, avvalue) = match defined_m { - None => { - // Macro not in user's config, - // use default - // (and possibly issue a warning). - - // TODO: @Sammy99jsp add 'not found' warning. - - errors.push(( - Box::new(MacroMissing( - Self::traceable(Some(false)), - declared_m.identifier(), - Self::path(), - )), - Self::traceable(Some(false)), - )); - - (declared_m, default_v) - } - - Some(i) => { - found_macros.push(i); - let (m, v, p, p_v) = defined.clone().nth(i).unwrap(); - - // Check if the macro's signature matches our defined one. - let sig_check = declared_m.has_signature(&m); - - if let Err((delta, vec)) = sig_check { - errors.push(( - Box::new(SignatureMismatchError( - p.clone(), - m.identifier(), - (delta, vec.iter().map(|e| (*e).clone()).collect()), - )) as Box, - p, - )); - - (declared_m, default_v) - } else { - // VALUE CHECKS - // Now check the value's type against the default's - match default_v.parse_same_type(p_v.clone(), v.clone()) { - Err(e) => { - errors.push((e, p)); - (declared_m, default_v) - } - - Ok(val) => { - // Last value check: - // Check if value is consistent with macro - match val.consistent_with_macro(p_v.clone(), &m) { - Ok(()) => (declared_m, val), - - Err(e) => { - errors.push((e, p)); - (declared_m, default_v) - } - } - } - } - } - } - }; - - output.insert(avmacro, avvalue); - } - - // User defined macros which were not found in our declaration. - let not_found: Vec<_> = defined - .enumerate() - .filter(|(i, _)| !found_macros.contains(i)) - .map(|(_, e)| e) - .collect(); - - for (m, _, p1, _) in not_found { - errors.push(( - Box::new(MacroNotFound(p1.clone(), m.identifier(), Self::path())) - as Box, - p1, - )) - } - - errors.sort_by(|(a, _), (b, _)| compare_errors(a, b).unwrap()); - - for (err, _) in errors { - println!("{}", err) - } - - output - } -} - -#[AvError(TraceableError, CONFIG_MACRO_NOT_FOUND, "Config: Macro Not Found")] -pub struct MacroNotFound(pub Traceable, pub String, pub JSONPath); - -impl TraceableError for MacroNotFound { - location!(&self.0); - description!(( - "The macro `{}` is not formally defined in this section (`{}`) -- ignoring it.", - self.1.blue(), - self.2 - )); -} - -#[AvError(TraceableError, CONFIG_MACRO_MISSING, "Config: Macro Missing")] -pub struct MacroMissing(pub Traceable, pub String, pub JSONPath); - -impl TraceableError for MacroMissing { - location!(&self.0); - description!(( - "The macro `{}` wasn't found in {} -- we've used the default.", - self.1.blue(), - self.2 - )); -} diff --git a/src/config/sections/keybinds.rs b/src/config/sections/keybinds.rs deleted file mode 100644 index 8ca8eaa..0000000 --- a/src/config/sections/keybinds.rs +++ /dev/null @@ -1,17 +0,0 @@ -use compositor_macros::config_section; - -use crate::config::ConfigurationSection; - -config_section!( - Keybinds { - "Move focused window to `d`th on the taskbar." - window(d) => (Meta+{d}), - - "How many horns does a unicorn have?" - hornsInUnicorn => 1, - } -); - -impl ConfigurationSection for Keybinds { - const PATH: &'static str = "$.keybinds"; -} diff --git a/src/config/sections/mod.rs b/src/config/sections/mod.rs deleted file mode 100644 index d000e1b..0000000 --- a/src/config/sections/mod.rs +++ /dev/null @@ -1 +0,0 @@ -pub mod keybinds; diff --git a/src/config/templating/avvalue.rs b/src/config/templating/avvalue.rs deleted file mode 100644 index d3ac66b..0000000 --- a/src/config/templating/avvalue.rs +++ /dev/null @@ -1,159 +0,0 @@ -//! A short enum wrapper of the value of a macro in a configuration section -//! -//! Implemented for different types with the macro: `AvValue!([])` -//! the types should have the `AvDeserialize` trait. -//! - -use colored::Colorize; - -use compositor_macros::{description, location, AvError, AvValue}; - -use crate::core::{ - error::{AvError, Traceable, TraceableError}, - keyboard::{avkeys::AvKey, AvKeys}, -}; - -use serde_json::Value; - -use super::{ - r#macro::{AvKeysMismatch, Difference}, - AvMacro, MacroParameter, -}; - -/// -/// # UnexpectedType -/// -/// Where the config expects one type, but gets another. -/// -/// ## Members -/// * Location -/// * Expected type -/// * Received type -/// -#[AvError(TraceableError, CONFIG_UNEXPECTED_TYPE, "Config: Unexpected Type")] -pub struct UnexpectedType(Traceable, String, String); - -impl TraceableError for UnexpectedType { - description!(("Expected a {}, got a {}.", &self.1.blue(), &self.2.blue())); - location!(&self.0); -} - -impl UnexpectedType { - fn type_name(v: &Value) -> String { - match v { - Value::Array(_) => "Array", - Value::Object(_) => "Object", - Value::String(_) => "String", - Value::Number(_) => "Number", - Value::Bool(_) => "Boolean", - Value::Null => "Null", - } - .to_string() - } - - pub fn from(loc: Traceable, e: &str, v: Value) -> UnexpectedType { - Self(loc, e.to_string(), Self::type_name(&v)) - } -} - -AvValue!([String, i64, f64, bool, AvKeys]); - -impl AvValue { - pub fn parse_same_type( - &self, - loc: Traceable, - val: Value, - ) -> Result> { - match &self { - AvValue::String(_) => String::deserialize(loc, val), - AvValue::i64(_) => i64::deserialize(loc, val), - AvValue::f64(_) => f64::deserialize(loc, val), - AvValue::bool(_) => bool::deserialize(loc, val), - AvValue::AvKeys(_) => AvKeys::deserialize(loc, val), - } - } - - pub fn consistent_with_macro( - &self, - loc: Traceable, - m: &AvMacro, - ) -> Result<(), Box> { - match &self { - AvValue::String(_) => Ok(()), - AvValue::i64(_) => Ok(()), - AvValue::f64(_) => Ok(()), - AvValue::bool(_) => Ok(()), - AvValue::AvKeys(k) => { - let p: Vec<_> = - k.0.iter() - .filter_map(|k: &AvKey| match k { - AvKey::Parameter(k) => Some(k), - _ => None, - }) - .collect(); - - m.has_parameters(p.iter().map(|k: &&MacroParameter| (*k).clone()).collect()) - .map_err(|e: (Difference, Vec)| { - Box::new(AvKeysMismatch(loc, k.to_string(), e)) as Box - }) - } - } - } -} - -/// -/// Allows for conversion of `serde_json::Value` to `AvValue` for a given type -/// -pub trait AvDeserialize { - fn deserialize(loc: Traceable, val: Value) -> Result>; -} - -impl AvDeserialize for String { - fn deserialize(loc: Traceable, val: Value) -> Result> { - match val { - Value::String(s) => Ok(AvValue::String(s)), - - v => Err(Box::new(UnexpectedType::from(loc, "String", v))), - } - } -} - -impl AvDeserialize for i64 { - fn deserialize(loc: Traceable, val: Value) -> Result> { - match val { - Value::Number(ref v) => match v.as_i64() { - Some(v) => Ok(AvValue::i64(v.clone())), - None => Err(Box::new(UnexpectedType::from( - loc, - "Integer", - Into::::into(v.clone()), - ))), - }, - - v => Err(Box::new(UnexpectedType::from(loc, "Integer", v.clone()))), - } - } -} - -impl AvDeserialize for f64 { - fn deserialize(loc: Traceable, val: Value) -> Result> { - match val { - Value::Number(ref v) => match v.as_f64() { - Some(v) => Ok(AvValue::f64(v)), - None => Err(Box::new(UnexpectedType::from(loc, "Float", val.clone()))), - }, - - v => Err(Box::new(UnexpectedType::from(loc, "Float", v))), - } - } -} - -impl AvDeserialize for bool { - fn deserialize(loc: Traceable, val: Value) -> Result> { - match val { - Value::Bool(v) => Ok(AvValue::bool(v)), - - v => Err(Box::new(UnexpectedType::from(loc, "Boolean", v))), - } - } -} diff --git a/src/config/templating/macro.rs b/src/config/templating/macro.rs deleted file mode 100644 index 84f984a..0000000 --- a/src/config/templating/macro.rs +++ /dev/null @@ -1,443 +0,0 @@ -use colored::Colorize; - -use compositor_macros::{description, location, AvError}; - -use crate::core::error::{AvError, Traceable, TraceableError}; - -use std::{collections::HashSet, convert::TryFrom, hash::Hash, iter::FromIterator}; - -#[derive(Clone)] -pub enum ParseErrorType { - UnexpectedToken(String), - ExpectedToken(String), -} - -#[AvError(TraceableError, CONFIG_MACRO_PARSE_ERROR, "Config: Macro Parser Error")] -pub struct ParseError { - /// - /// The token that caused the failure. - /// - erroneous: ParseErrorType, - - /// - /// Absolute location of the token. - /// - location: Traceable, -} - -impl TraceableError for ParseError { - location!(&self.location); - - fn description(&self) -> String { - match &self.erroneous { - ParseErrorType::ExpectedToken(t) => { - format!("Expected `{}`", t.blue()) - } - ParseErrorType::UnexpectedToken(t) => { - format!("Unexpected token `{}`", t.blue()) - } - } - } -} - -/// -/// Parameter types supported by -/// macros. -/// -#[derive(Debug, Clone, Hash, PartialEq, Eq)] -pub enum MacroParameter { - /// - /// Digit keys from 0...9 - /// - DigitKey, - - /// - /// Function keys from 1...12 - /// - FunctionKey, -} - -impl<'a> TryFrom<&'a str> for MacroParameter { - type Error = String; - - /// - /// Parse enum code into an enum member. - /// - fn try_from(value: &'a str) -> Result { - match value { - "d" => Ok(Self::DigitKey), - "F" => Ok(Self::FunctionKey), - _ => Err(value.to_string()), - } - } -} - -impl From for String { - fn from(p: MacroParameter) -> Self { - match p { - MacroParameter::DigitKey => "d", - MacroParameter::FunctionKey => "F", - } - .to_string() - } -} - -impl ToString for MacroParameter { - fn to_string(&self) -> String { - >::into(self.clone()) - } -} - -#[AvError( - TraceableError, - CONFIG_MACRO_PARAMETER_ERROR, - "Config: Macro Parameter Error" -)] -pub struct ParameterError(pub Traceable, pub String); - -impl TraceableError for ParameterError { - location!(&self.0); - description!(("Invalid macro parameter `{}`", self.1.blue())); -} - -#[AvError(TraceableError, CONFIG_MACRO_EMPTY, "Config: Expected Macro")] -pub struct MacroEmpty(Traceable); - -impl TraceableError for MacroEmpty { - location!(&self.0); - description!(("Expected a macro here, got the silent treatment :(",)); -} - -/// -/// ## Structure of a Macro -/// All macros have an identifier. -/// Macro identifiers should use `camelCase` -/// and only contain ASCII alphanumeric characters. -/// -/// -/// Macro expressions can either have brackets (for parameters), or not. -/// * With parameters `macroNameHere(param1, param2, ...)` -/// * Without `macroNameHere` (equivalent to `macroNameHere()`) -/// -/// ## Use -/// -/// Allows for macros in the configuration allow for things such as: -/// ```jsonc -/// { -/// "moveToWorkspace1": "Ctrl+Super+1", -/// "moveToWorkspace2": "Ctrl+Super+2", -/// "moveToWorkspace3": "Ctrl+Super+3", -/// /* . . . */ -/// "moveToWorkspace9": "Ctrl+Super+9", -/// } -/// ``` -/// -/// to be shortened to: -/// -/// ```jsonc -/// { -/// "moveToWorkspace(d)": "Ctrl+Super+{d}" -/// } -/// ``` -/// -#[derive(Debug, PartialEq, Eq, Clone)] -pub struct AvMacro { - identifier: String, - parameters: HashSet, -} - -impl Hash for AvMacro { - fn hash(&self, state: &mut H) { - self.cannonize().hash(state) - } -} - -#[derive(Clone)] -pub enum Difference { - FromOriginal, - FromNew, -} - -impl AvMacro { - /// Two macros are said to have equal signature if - /// they have the same parameters (the order doesn't matter). - pub fn has_signature<'a>( - &'a self, - o: &'a Self, - ) -> Result<(), (Difference, Vec<&'a MacroParameter>)> { - let u: HashSet<_> = self.parameters.union(&o.parameters).collect(); - - match u.len() == self.parameters.len() && u.len() == o.parameters.len() { - true => Ok(()), - - false => { - let from_original: Vec<_> = self.parameters.difference(&o.parameters).collect(); - - if from_original.len() > 0 { - return Err((Difference::FromOriginal, from_original)); - } - - return Err(( - Difference::FromNew, - o.parameters.difference(&self.parameters).collect(), - )); - } - } - } - - pub fn has_parameters( - &self, - v: Vec, - ) -> Result<(), (Difference, Vec)> { - match self.parameters.len() { - l if l < v.len() => Err(( - Difference::FromNew, - v.iter() - .filter(|k: &&MacroParameter| !self.parameters.contains(k)) - .map(|k: &MacroParameter| k.clone()) - .collect(), - )), - - l if l == v.len() => Ok(()), - - l if l > v.len() => Err(( - Difference::FromNew, - self.parameters - .iter() - .filter(|k: &&MacroParameter| !v.contains(k)) - .map(|k: &MacroParameter| k.clone()) - .collect(), - )), - - _ => unreachable!(), - } - } - - pub fn has_same_id(&self, o: &Self) -> bool { - self.identifier == o.identifier - } - - pub fn identifier(&self) -> String { - self.identifier.clone() - } - - fn cannonize(&self) -> String { - format!("{}{:?}", self.identifier, self.parameters) - } - pub fn parse(loc: Traceable, value: String) -> Result>> { - let mut ident: String = String::new(); - let mut parameters: Vec = vec![]; - let mut parameter_locs: Vec = vec![]; - - enum State { - Identifier, - Parameters, - Finished, - } - - let mut current_token: String = "".to_string(); - - let mut current_state: State = State::Identifier; - - // If this macro is empty. - if value.len() == 0 { - return Err(vec![Box::new(MacroEmpty(loc))]); - } - - // Parse each character. - for (index, chr) in value.chars().enumerate() { - if chr == ' ' { - continue; - } - - match current_state { - State::Identifier => match chr { - c if c.is_ascii_alphanumeric() => { - current_token.push(c); - } - - '(' => { - ident = current_token; - current_token = "".to_string(); - - parameter_locs.push(loc.at_index(index + 1)); - - current_state = State::Parameters; - } - - _ => { - return Err(vec![Box::new(ParseError { - location: loc.at_index(index), - erroneous: ParseErrorType::UnexpectedToken(chr.to_string()), - })]) - } - }, - State::Parameters => match chr { - c if c.is_ascii_alphanumeric() => { - current_token.push(c); - } - - ',' => { - parameters.push(current_token); - parameter_locs.push(loc.at_index(index + 1)); - - current_token = "".to_string(); - } - - ')' => { - parameter_locs.push(loc.at_index(index + 1)); - parameters.push(current_token); - current_token = "".to_string(); - - current_state = State::Finished; - } - - _ => { - return Err(vec![Box::new(ParseError { - location: loc.at_index(index), - erroneous: ParseErrorType::UnexpectedToken(chr.to_string()), - })]) - } - }, - - State::Finished => { - return Err(vec![Box::new(ParseError { - location: loc.at_index(index), - erroneous: ParseErrorType::ExpectedToken("".to_string()), - })]) - } - } - } - - match current_state { - State::Identifier => ident = current_token, - - State::Parameters => { - // Didn't finish the parameters with a ')' - return Err(vec![Box::new(ParseError { - location: loc.at_index(value.len()), - erroneous: ParseErrorType::ExpectedToken(")".to_string()), - })]); - } - - _ => {} - } - - let parameters = parameters - .iter() - .map(|v: &String| MacroParameter::try_from(v.as_str())); - - let errs: Vec<_> = parameters - .clone() - .enumerate() - .filter(|(_, r)| r.is_err()) - .map(|(i, r)| { - let err: String = r.unwrap_err(); - let e: &Traceable = parameter_locs.get(i).unwrap(); - - Box::new(ParameterError(e.clone(), err)) as Box - }) - .collect(); - - if errs.len() > 0 { - return Err(errs); - } - - let parameters: Vec<_> = parameters - .map(|r: Result| r.unwrap()) - .collect(); - - Ok(Self { - identifier: ident, - parameters: HashSet::from_iter(parameters.iter().map(|s: &MacroParameter| s.clone())), - }) - } -} - -#[AvError( - TraceableError, - CONFIG_MACRO_SIGNATURE_MISMATCH, - "Config: Macro Signature Mismatch" -)] -pub struct SignatureMismatchError( - pub Traceable, - pub String, - pub (Difference, Vec), -); - -impl TraceableError for SignatureMismatchError { - location!(&self.0); - description!(( - "The macro parameter(s) of {} is not valid against its definition.\n\ - {}", - self.1, - match &self.2 { - (p, v) => format!( - "{} {}", - match p { - Difference::FromNew => "Excess parameters:", - Difference::FromOriginal => "Missing:", - }, - v.iter() - .map(|s| format!("`{}` ", s.to_string().blue())) - .collect::() - ), - } - )); -} -#[AvError( - TraceableError, - CONFIG_AVKEYS_PARAMETER_MISMATCH, - "Config: Macro Value Mismatch" -)] -pub struct AvKeysMismatch( - pub Traceable, - pub String, - pub (Difference, Vec), -); - -impl TraceableError for AvKeysMismatch { - location!(&self.0); - description! ( - ( - "The macro parameter(s) of the Key expression {} are not valid against the macro holding it.\n\ - {}", - - self.1, - - match &self.2 { - (p, v) => format!( - "{} {}", - - match p { - Difference::FromNew => "Excess parameters:", - Difference::FromOriginal => "Missing:" - }, - - v.iter().map(|s| - format!("`{}` ", s.to_string().blue() - ) - ).collect::()), - } - ) - ); -} - -#[cfg(test)] -mod tests { - use compositor_macros::traceable; - - use crate::core::error::TraceableError; - - use super::AvMacro; - - #[test] - fn parsing_test() { - let m: Result>> = - AvMacro::parse(traceable!(), "helpMe(deez)".to_string()); - - let m: AvMacro = m.unwrap(); - - println!("{m:?}"); - } -} diff --git a/src/config/templating/mod.rs b/src/config/templating/mod.rs deleted file mode 100644 index 74b80da..0000000 --- a/src/config/templating/mod.rs +++ /dev/null @@ -1,6 +0,0 @@ -pub mod avvalue; -pub use avvalue::AvDeserialize; -pub use avvalue::AvValue; -pub mod r#macro; -pub use r#macro::AvMacro; -pub use r#macro::MacroParameter; diff --git a/src/consts.rs b/src/consts.rs deleted file mode 100644 index e8eb5ff..0000000 --- a/src/consts.rs +++ /dev/null @@ -1,10 +0,0 @@ -use std::path::{Path, PathBuf}; - -use lazy_static::lazy_static; - -use crate::compat::{BaseDir, XdgBaseDir}; - -lazy_static! { - pub static ref CONFIG_FOLDER: PathBuf = { XdgBaseDir::Config.path().join("avdan") }; - pub static ref CONFIG_FILE: &'static Path = Path::new("Compositor.jsonc"); -} diff --git a/src/core/error.rs b/src/core/error.rs deleted file mode 100644 index 66c3b9f..0000000 --- a/src/core/error.rs +++ /dev/null @@ -1,339 +0,0 @@ -use colored::Colorize; - -use json_tree::Location; - -use std::cmp::Ordering; - -pub mod color { - use colored::Color; - - pub const ERROR: Color = Color::TrueColor { - r: 215, - g: 38, - b: 56, - }; - - pub const WARNING: Color = Color::TrueColor { - r: 246, - g: 170, - b: 28, - }; - - pub const NEUTRAL: Color = Color::TrueColor { - r: 5, - g: 142, - b: 217, - }; -} - -pub trait Indentable -where - Self: Sized, -{ - const BASE_UNIT: usize = 2; - /// - /// Indent all the lines by a specific number of spaces - /// - fn indent(&self, levels: usize) -> String; -} - -impl Indentable for String { - fn indent(&self, level: usize) -> String { - let v: Vec<_> = self - .split("\n") - .map(|s| format!("{}{}", " ".repeat(Self::BASE_UNIT * level), s)) - .collect(); - v.join("\n") - } -} - -impl Indentable for &str { - fn indent(&self, level: usize) -> String { - let v: Vec<_> = self - .split("\n") - .map(|s| format!("{}{}", " ".repeat(Self::BASE_UNIT * level), s)) - .collect(); - v.join("\n") - } -} - -/// -/// ## Errors -/// -/// All errors in AvdanOS should implement this trait. -/// -/// `AvError`s allow for colorful, and descriptive error messages. -/// -/// The `#[AvError]` macro can be used to make the process of creating -/// a new error easily. -/// -/// #### Example - Generic Error -/// -/// ```rust -/// use crate::core::error::AvError; -/// use compositor_macros::AvError; -/// // ↓ Error code ↓ Error Title -/// #[AvError(EXAMPLE_ERROR, "Example Error for Documentation")] -/// struct ExampleError; -/// -/// let example = ExampleError; -/// println!("{}", example); -/// -/// ``` -pub trait AvError: std::fmt::Display + std::fmt::Debug + std::error::Error { - /// - /// The unique code for this error. - /// - /// Convention is to use TRAIN_CASE - /// - /// ### Examples - /// ``` - /// "CONFIG_MACRO_NOT_FOUND" - /// "CONFIG_MACRO_INVALID_PARAMETER" - /// ``` - /// - fn code(&self) -> String; - - /// - /// A title for this error (shared by - /// all instances of this error). - /// - /// Capitalize each word. - /// - /// ### Examples - /// ``` - /// "Macro Not Found" - /// "Invalid Macro Parameter" - /// ``` - /// - fn title(&self) -> String; - - /// - /// The content of this error warning. - /// - /// **NOTE**: do not override this, unless - /// you are making a new error type, - /// like `LocatableError` - /// - fn body(&self) -> String { - "".into() - } -} - -/// -/// Represents a location -/// of a traceable error. -/// -#[derive(Debug, Clone)] -pub struct Traceable { - /// - /// The path to the file - /// that caused this error. - /// - path: String, - - /// - /// The line where this error - /// occurred. - /// - line: usize, - - /// - /// The column index where this - /// error occurred. - /// - column: usize, -} - -impl Traceable { - /// - /// Makes a new instance of [`Traceable`]. - /// - pub fn new(path: String, loc: (usize, usize)) -> Self { - Self { - path, - line: loc.0, - column: loc.1, - } - } - - /// - /// Returns new [`Traceable`] at a string index - /// relative to the current column pos. - /// - pub fn at_index(&self, index: usize) -> Self { - Self { - path: self.path.clone(), - line: self.line, - column: self.column + 1 + index, // Account for the "" ^ - } - } - - /// - /// Returns a new [`Traceable`] with new - /// line and column numbers. - /// - pub fn at_loc(&self, (line, column): (usize, usize)) -> Self { - Self { - path: self.path.clone(), - line, - column, - } - } - - /// - /// Makes a Locatable from a Location and a file path - /// - /// key object determines if it should return the Locatable for the key or value - pub fn combine(path: &String, loc: &Location, key: Option) -> Self { - let (line, column): (usize, usize) = match loc { - Location::KeyValue(k, v) => match key { - None => { - unimplemented!("Should pass in Some(bool) as last argument for KeyValue pairs") - } - Some(true) => k, - Some(false) => v, - }, - - Location::Value(v) => v, - } - .loc(); - - Self { - path: path.clone(), - line, - column, - } - } -} - -impl ToString for Traceable { - fn to_string(&self) -> String { - format!("{}:{}:{}", self.path, self.line, self.column) - } -} - -/// -/// ## Traceable Error -/// Traceable Errors are errors that can be -/// traced to a specific location -/// in a file such as a mis-configuration. -/// -/// ### Macros -/// -/// * `location!(&self )` -/// - Implements [`TraceableError::location`] by returning the value -/// inside the brackets -/// -/// See [`location`]. -/// -/// -/// * `description!(())` -/// - Implements [`TraceableError::description`] by returning `format!()` -/// which allows for easy string interpolation. -/// -/// See [`description`]. -/// -/// ### Example -/// -/// ```rust -/// use crate::core::error::{LocatableError, Traceable}; -/// use compositor_macros::{AvError, location, description}; -/// -/// #[AvError(TraceableError, EXAMPLE_ERROR, "Example Error for Documentation")] -/// struct ExampleError(Traceable); -/// -/// -/// -/// impl TraceableError for ExampleError { -/// location!(&self.0); -/// description!(("Example traceable error", )); -/// } -/// -/// let test = ExampleError( -/// Traceable { -/// path: "config.txt".to_string(), -/// line: 203, -/// column: 20 -/// } -/// ); -/// -/// println!("{}", test); -/// -/// ``` -/// -pub trait TraceableError: AvError { - /// - /// The error's location - /// - fn location(&self) -> &Traceable; - - fn description(&self) -> String; - - fn body(&self) -> String { - format!( - "{}\nat {}\n", - TraceableError::description(self), - self.location().to_string().color(color::NEUTRAL) - ) - } -} - -pub fn compare_errors( - s: &Box, - o: &Box, -) -> Option { - let s: &Traceable = s.location(); - let o: &Traceable = o.location(); - - if s.path != o.path { - return None; - } - - match s.line.partial_cmp(&o.line).unwrap() { - Ordering::Equal => s.column.partial_cmp(&o.column), - a => Some(a), - } -} - -#[cfg(test)] -mod tests { - use compositor_macros::AvError; - - use crate::core::error::{Traceable, TraceableError}; - - use super::AvError; - - #[test] - fn derive() { - #[AvError(TEST_DERIVE_ERROR, "Test Error")] - struct Error; - - let test: Error = Error; - println!("{}", test); - } - - #[test] - fn locatable() { - #[AvError(TraceableError, TEST_DERIVE_ERROR, "Test Error")] - struct Locatable(Traceable); - - let test: Locatable = Locatable(Traceable { - path: "src/core/error.rs".to_string(), - line: 203, - column: 20, - }); - - impl TraceableError for Locatable { - fn location(&self) -> &Traceable { - &self.0 - } - - fn description(&self) -> String { - "Test Traceable thing!".into() - } - } - - println!("{}", test); - } -} diff --git a/src/core/keyboard/avkeys.rs b/src/core/keyboard/avkeys.rs deleted file mode 100644 index 5031a51..0000000 --- a/src/core/keyboard/avkeys.rs +++ /dev/null @@ -1,151 +0,0 @@ -use colored::Colorize; - -use compositor_macros::{description, location, AvError}; - -use crate::{ - config::templating::{ - avvalue::{AvValue, UnexpectedType}, - r#macro::ParameterError, - AvDeserialize, MacroParameter, - }, - core::error::{AvError, Traceable, TraceableError}, -}; - -use serde_json::Value; - -use std::convert::TryFrom; - -#[derive(Debug, PartialEq, Clone)] -pub enum AvKey { - Key(String), - Parameter(MacroParameter), -} - -impl TryFrom for AvKey { - type Error = (String, String); - - fn try_from(value: String) -> Result { - match value.as_str() { - "" => Err(("key".to_string(), "".to_string())), - - v if v.ends_with("}") && v.starts_with("{") => { - let t: &str = &v[1..(v.len() - 1)]; - return Ok(AvKey::Parameter( - MacroParameter::try_from(t) - .map_err(|s: String| ("macro_param".to_string(), s))?, - )); - } - - a => Ok(AvKey::Key(a.to_string())), - } - } -} - -impl ToString for AvKey { - fn to_string(&self) -> String { - match self { - Self::Key(k) => k.clone(), - Self::Parameter(p) => format!("{{{}}}", p.to_string()), - } - } -} - -#[derive(Debug, PartialEq)] -pub struct AvKeys(pub Vec); - -impl TryFrom for AvKeys { - type Error = (String, String); - - fn try_from(value: String) -> Result { - let vec: Vec<_> = value - .split("+") - .map(|s: &str| AvKey::try_from(s.to_string())) - .collect(); - - let mut v: Vec = vec![]; - for val in vec { - v.push(val?); - } - - Ok(Self(v)) - } -} - -impl Clone for AvKeys { - fn clone(&self) -> Self { - Self(self.0.iter().map(|k: &AvKey| (*k).clone()).collect()) - } -} - -impl ToString for AvKeys { - fn to_string(&self) -> String { - let v: Vec<_> = self.0.iter().map(AvKey::to_string).collect(); - - v.join("+") - } -} - -#[AvError( - TraceableError, - CONFIG_KEY_PARSE, - "Config: Keyboard Shortcut Parsing Error" -)] -struct KeyParseError(Traceable, String); - -impl TraceableError for KeyParseError { - location!(&self.0); - description!(("Error while parsing `{}` as a Keyboard Key", self.1.blue())); -} - -impl AvDeserialize for AvKeys { - fn deserialize( - loc: Traceable, - val: serde_json::Value, - ) -> Result> { - match val { - Value::String(s) => { - Ok(AvValue::AvKeys(AvKeys::try_from(s).map_err( - |(t, v)| match t.as_str() { - "key" => Box::new(KeyParseError(loc, v)) as Box, - "macro_param" => { - Box::new(ParameterError(loc, v)) as Box - } - _ => unreachable!(), - }, - )?)) - } - - v => Err(Box::new(UnexpectedType::from(loc, "String", v))), - } - } -} - -impl Default for AvKeys { - fn default() -> Self { - Self(vec![]) - } -} - -#[cfg(test)] -mod tests { - use crate::config::templating::MacroParameter; - - use std::convert::TryFrom; - - use super::{AvKey, AvKeys}; - - #[test] - fn deserialize() { - let j: String = "Ctrl+{d}".to_string(); - - let v = AvKeys::try_from(j); - - assert_eq!( - v, - Ok(AvKeys(vec![ - AvKey::Key("Ctrl".to_string()), - AvKey::Parameter(MacroParameter::DigitKey) - ])) - ) - } -} diff --git a/src/core/keyboard/mod.rs b/src/core/keyboard/mod.rs deleted file mode 100644 index 2ccfbc0..0000000 --- a/src/core/keyboard/mod.rs +++ /dev/null @@ -1,3 +0,0 @@ -pub mod avkeys; -pub use avkeys::AvKey; -pub use avkeys::AvKeys; diff --git a/src/core/mod.rs b/src/core/mod.rs deleted file mode 100644 index 4d35908..0000000 --- a/src/core/mod.rs +++ /dev/null @@ -1,2 +0,0 @@ -pub mod error; -pub mod keyboard; diff --git a/src/macros/Cargo.toml b/src/macros/Cargo.toml deleted file mode 100644 index 073d9c0..0000000 --- a/src/macros/Cargo.toml +++ /dev/null @@ -1,18 +0,0 @@ -[package] - -name = "compositor-macros" -version = "0.1.0" -edition = "2021" - -[lib] -proc-macro = true - -[dependencies] - -quote = "1.0.21" -proc-macro2 = "1.0.43" - -[dependencies.syn] - -version = "1.0.99" -features = ["full"] diff --git a/src/macros/src/avvalue.rs b/src/macros/src/avvalue.rs deleted file mode 100644 index dd779b8..0000000 --- a/src/macros/src/avvalue.rs +++ /dev/null @@ -1,147 +0,0 @@ -use proc_macro2::TokenStream; - -use quote::quote; - -use syn::{ - parse::{Lookahead1, ParseBuffer, ParseStream}, - punctuated::Punctuated, - token::Brace, - Error, Ident, LitBool, LitFloat, LitInt, LitStr, Result, Token, -}; - -pub enum AvValue { - String(LitStr), - Integer(LitInt), - Float(LitFloat), - Null(Ident), - Boolean(LitBool), - AvKeys(Punctuated), - List(Punctuated), -} - -pub enum AvKey { - Key(Ident), - Parameter(Brace, Ident), -} - -impl AvValue { - pub fn get_type(&self) -> TokenStream { - use AvValue::*; - match self { - String(_) => quote! { String }, - Integer(_) => quote! { i64 }, - Float(_) => quote! { f64 }, - Null(_) => panic!("Null token is not supported for deserialization!"), - Boolean(_) => quote! { bool }, - AvKeys(_) => quote! { AvKeys }, - List(_) => panic!("List tokens are not supported for deserialization yet!"), - } - } - - pub fn value(&self) -> TokenStream { - let t: TokenStream = self.get_type(); - let v: TokenStream = match self { - AvValue::String(s) => quote! { #s.into() }, - AvValue::Integer(s) => quote! { #s.into() }, - AvValue::Float(s) => quote! { #s.into() }, - AvValue::Null(_) => panic!("Null token is not supported for deserialization!"), - AvValue::Boolean(s) => quote! { #s.into() }, - AvValue::AvKeys(s) => { - let t = s.iter().map(|k: &AvKey| match k { - AvKey::Key(k) => { - let k: String = k.to_string(); - - quote! { - AvKey::Key(#k.into()) - } - } - AvKey::Parameter(_, p) => { - let p: String = p.to_string(); - - quote! { - AvKey::Parameter(#p.try_into().unwrap()) - } - } - }); - - quote! { - AvKeys ( vec![#(#t),*] ) - } - } - - AvValue::List(_) => panic!("List tokens are not supported for deserialization yet!"), - }; - - quote! { AvValue::#t(#v) } - } -} - -impl syn::parse::Parse for AvKey { - fn parse(input: ParseStream) -> Result { - let look: Lookahead1 = input.lookahead1(); - - return match look.peek(syn::Ident) { - true => Ok(AvKey::Key(input.parse()?)), - - false => { - let content: ParseBuffer; - - Ok(AvKey::Parameter( - syn::braced!(content in input), - content.parse()?, - )) - } - }; - } -} - -impl syn::parse::Parse for AvValue { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - if input.peek(syn::LitBool) { - return Ok(Self::Boolean(input.parse()?)); - } - - if input.peek(syn::LitInt) { - return Ok(Self::Integer(input.parse()?)); - } - - if input.peek(syn::LitFloat) { - return Ok(Self::Float(input.parse()?)); - } - - if input.peek(syn::LitStr) { - return Ok(Self::String(input.parse()?)); - } - - if input.peek(syn::Ident) { - let ident: syn::Ident = input.parse()?; - if ident.to_string().eq("null") { - return Ok(Self::Null(ident)); - } - } - - if input.peek(syn::token::Paren) { - let content: ParseBuffer; - - let _ = syn::parenthesized!(content in input); - - return Ok(Self::AvKeys(content.parse_terminated(AvKey::parse)?)); - } - - if input.peek(syn::token::Bracket) { - let content: ParseBuffer; - - let _ = syn::bracketed!(content in input); - - return Ok(Self::List(content.parse_terminated(AvValue::parse)?)); - } - - // let mut e = Diagnostic::new(Level::Error, "Expected a bool, float/int, string, null, or AvKey collection."); - // e.set_spans(input.span().unwrap()); - // e.emit(); - Err(Error::new( - input.span(), - "Expected a bool, float/int, string, null, or AvKey collection.", - )) - } -} diff --git a/src/macros/src/delcaration.rs b/src/macros/src/delcaration.rs deleted file mode 100644 index 9d6981c..0000000 --- a/src/macros/src/delcaration.rs +++ /dev/null @@ -1,122 +0,0 @@ -use crate::avvalue::AvValue; - -use quote::ToTokens; - -use syn::{ - braced, - parse::{Parse, ParseBuffer, ParseStream}, - punctuated::Punctuated, - token::Brace, - Expr, Ident, Token, -}; - -use std::fmt::{Debug, Result}; - -pub struct ConfigDelcaration { - pub ident: Ident, - _brace_token: Brace, - pub fields: Punctuated, -} - -impl Parse for ConfigDelcaration { - fn parse(input: ParseStream) -> syn::Result { - let content: ParseBuffer; - - Ok(ConfigDelcaration { - ident: input.parse()?, - _brace_token: braced!(content in input), - fields: content.parse_terminated(AvMacro::parse)?, - }) - } -} - -pub struct AvMacro { - // Comment for users - description: syn::LitStr, - - // Macro declaration itself with its parameters - avmacro: (String, Vec), - - // Separates the macro - _delim: Token![=>], - - default: AvValue, -} - -impl AvMacro { - pub fn description(&self) -> String { - self.description.value() - } - - pub fn default(&self) -> &AvValue { - &self.default - } - - pub fn av_macro(&self) -> (String, Vec) { - self.avmacro.clone() - } -} - -impl Parse for AvMacro { - fn parse(input: ParseStream) -> syn::Result { - Ok(AvMacro { - description: input.parse()?, - - avmacro: { - match input.peek2(Token![=>]) { - true => { - // Macro has no params. - let ident: syn::Ident = input.parse()?; - - (ident.to_string(), vec![]) - } - - false => { - let exp: syn::ExprCall = input.parse()?; - - ( - exp.func.to_token_stream().to_string(), - exp.args - .into_iter() - .map(|t: Expr| t.to_token_stream().to_string()) - .collect(), - ) - } - } - }, - - _delim: input.parse()?, - - default: input.parse()?, - }) - } -} - -impl Debug for AvMacro { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result { - write!( - f, - "AvMacro Declaration:\n Name: {}\n Parameters:\n", - &self.avmacro.0, - )?; - - for param in (&self.avmacro.1).into_iter() { - let n: String = param.to_token_stream().to_string(); - write!(f, " {n}\n")?; - } - - write!(f, "\n") - } -} - -impl Debug for ConfigDelcaration { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "ConfigDeclaration:\n")?; - - for field in (&self.fields).into_iter() { - write!(f, " {:?}", field)?; - } - - Ok(()) - } -} diff --git a/src/macros/src/lib.rs b/src/macros/src/lib.rs deleted file mode 100644 index 533815a..0000000 --- a/src/macros/src/lib.rs +++ /dev/null @@ -1,462 +0,0 @@ -#![feature(proc_macro_diagnostic)] - -mod avvalue; -mod delcaration; - -use delcaration::{AvMacro, ConfigDelcaration}; - -use proc_macro::{Diagnostic, Level}; - -use proc_macro2::{Ident, TokenStream}; - -use quote::quote; - -use syn::{ - bracketed, parse::ParseBuffer, parse_macro_input, punctuated::Punctuated, AttributeArgs, - ExprReference, ExprTuple, ItemStruct, Lit, LitStr, Meta, NestedMeta, Token, TypePath, -}; - -#[proc_macro] -#[allow(non_snake_case)] -pub fn AvValue(input: proc_macro::TokenStream) -> proc_macro::TokenStream { - struct AvValueDeclaration { - types: Punctuated, - } - - impl syn::parse::Parse for AvValueDeclaration { - fn parse(input: syn::parse::ParseStream) -> syn::Result { - let content: ParseBuffer; - - let _ = bracketed!(content in input); - - Ok(Self { - types: content.parse_terminated(syn::TypePath::parse)?, - }) - } - } - - let t: AvValueDeclaration = parse_macro_input!(input as AvValueDeclaration); - - let iter = t.types.iter(); - let variants = iter.clone().map(|t: &TypePath| { - quote! { - #t(#t) - } - }); - - let impls = iter; - - let impls = impls.map(|t: &TypePath| { - quote! { - impl<'a> core::convert::TryFrom<&'a AvValue> for #t { - type Error = (); - - fn try_from(value: &'a AvValue) -> Result { - match value { - AvValue::#t(k) => Ok(k.clone()), - _ => Err(()) - } - } - } - } - }); - - quote! { - #[derive(Debug, PartialEq,)] - #[allow(non_camel_case_types)] - - pub enum AvValue { #(#variants),* } - - #(#impls)* - } - .into() -} - -#[proc_macro] -pub fn traceable(_: proc_macro::TokenStream) -> proc_macro::TokenStream { - quote! { - crate::core::error::Traceable::new ( - file!().to_string(), - (line!() as usize, column!() as usize) - ) - } - .into() -} - -#[proc_macro] -pub fn config_section(struc: proc_macro::TokenStream) -> proc_macro::TokenStream { - let parsed: ConfigDelcaration = parse_macro_input!(struc as ConfigDelcaration); - - // fs::write("./test.txt", format!("{:?}", parsed)).unwrap(); - - let ident: Ident = parsed.ident; - - let mut q: TokenStream = quote! {}; - - let iter = parsed.fields.iter(); - - for field in iter.clone() { - let (name, _): (String, _) = field.av_macro(); - let comments: String = field.description(); - - let n: Ident = syn::Ident::new(&name, ident.span()); - - let mut lines: TokenStream = quote! {}; - - for l in comments.lines() { - let tkns: proc_macro2::TokenStream = format!("/// {}", l.trim_start()).parse().unwrap(); - lines = quote! { - #lines - #tkns - } - } - - let typ: TokenStream = field.default().get_type(); - - let z: TokenStream = quote! { - #q - #lines - pub #n : #typ, - }; - - q = z; - } - - let macro_registration = iter.clone().map(|m: &AvMacro| { - let v: TokenStream = m.default().value(); - let (m_ident, m_params): (String, Vec) = m.av_macro(); - let av_mac_raw: String = format!( - "{m_ident}{}", - match m_params.len() { - 0 => format!(""), - _ => format!("({})", m_params.join(",")), - } - ); - - quote! { - let m = AvMacro::parse ( - traceable!(), - // Insert full macro as string - #av_mac_raw.to_string() - ).unwrap(); - - declared.insert( - m.clone(), - #v - ); - - ids.insert(#m_ident, m); - } - }); - - let macro_idents = iter.map(|m: &AvMacro| m.av_macro().0).map(|k: String| { - let n: Ident = syn::Ident::new(&k, ident.span()); - quote! { - #n: m.get(ids.get(#k).unwrap()).unwrap().try_into().unwrap(), - } - }); - - quote! { - use compositor_macros::traceable; - - use crate::{ - config::{ - templating::{ - AvMacro, - AvValue, - }, - }, - core::keyboard::{ - AvKey, - AvKeys, - }, - }; - - use std::collections::HashMap; - - #[allow(non_snake_case)] - #[derive(Debug)] - pub struct #ident { - #q - } - - impl<'de> serde::de::Deserialize<'de> for #ident { - fn deserialize(deserializer: D) -> Result - where D: serde::Deserializer<'de> - { - let mut ids = HashMap::new(); - let declared = { - let mut declared: HashMap = HashMap::new(); - - #(#macro_registration)* - - declared - }; - - let raw: HashMap = serde::de::Deserialize::deserialize(deserializer)?; - - let m = ::from_map(declared, raw); - - Ok ( - #ident { #(#macro_idents)* } - ) - } - } - }.into() -} - -extern crate proc_macro; - -/// -/// ## AvError Macro -/// This macro acts like -/// `#[derive(AvError)]` -/// -/// ### Parameters -/// -/// 1. *(Optional)* Error Type - AvError (default), or a super trait of it. -/// 2. Error Code -- The error code as an identifier (in TRAIN_CASE) -/// 3. Error Title -- A user-friendly description of the error. -/// -/// -#[proc_macro_attribute] -#[allow(non_snake_case)] -pub fn AvError( - attributes: proc_macro::TokenStream, - input: proc_macro::TokenStream, -) -> proc_macro::TokenStream { - let a: Vec = parse_macro_input!(attributes as AttributeArgs); - - let (parent, code, title): (Option<&NestedMeta>, &NestedMeta, &NestedMeta) = match a.len() { - 2 => (None, a.get(0).unwrap(), a.get(1).unwrap()), - - 3 => ( - Some(a.get(0).unwrap()), - a.get(1).unwrap(), - a.get(2).unwrap(), - ), - - _ => { - return quote::quote! { - compile_error!("Expected two or three elements: [`TYPE`], `ERROR CODE`, `TITLE`") - } - .into(); - } - }; - - let (code, title): (&Meta, &Lit) = match (code, title) { - (syn::NestedMeta::Meta(l1), syn::NestedMeta::Lit(l2)) => (l1, l2), - _ => unimplemented!("Code and title in invalid format!"), - }; - - let code: &syn::Path = match code { - syn::Meta::Path(p) => p, - _ => { - return quote::quote! { - compile_error!("Expected `ERROR CODE` to be a raw identifier (no \"\")") - } - .into(); - } - }; - - let title: &LitStr = match title { - syn::Lit::Str(tkn) => tkn, - _ => { - return quote::quote! { - compile_error!("Invalid format for title!") - } - .into(); - } - }; - - let code = match code.get_ident() { - None => { - return quote::quote! { - compile_error!("`ERROR CODE` needs to be a raw identifier (no ::)") - } - .into(); - } - - Some(t) => t, - }; - - // // I wish diagnostics were on the stable channel :( - // // 'Linting' the error code. - match code.to_string() { - s if !s.is_ascii() => { - let mut w = Diagnostic::new(Level::Warning, "Error Codes should be ASCII only "); - w.set_spans(code.span().unwrap()); - w.emit(); - } - - s if s.to_ascii_uppercase() != s => { - let mut w = Diagnostic::new(Level::Warning, "Error Codes should be in TRAIN_CASE"); - w.set_spans(code.span().unwrap()); - w.emit(); - } - - s if s.ends_with("_") => { - let mut w = Diagnostic::new(Level::Warning, "Remove the trailing `_`"); - w.set_spans(code.span().unwrap()); - w.emit(); - } - - _ => {} - } - - let code: LitStr = LitStr::new(&code.to_string(), code.span()); - - let input: ItemStruct = parse_macro_input!(input as ItemStruct); - - let ident: Ident = input.ident.clone(); - - // if the parent error attribute is defined, - // use it as the base trait instead of the default AvError - // for AvError::body()... - let line: TokenStream = match parent { - Some(err_type) => { - let err_type: &Meta = match err_type { - syn::NestedMeta::Meta(l) => l, - _ => { - (return quote::quote! { - compile_error!("`ERROR TYPE` needs to be a raw identifier (no ::)") - } - .into()); - } - }; - - let err_type: &syn::Path = match err_type { - syn::Meta::Path(tkn) => tkn, - - _ => { - (return quote::quote! { - compile_error!("Expected `ERROR TYPE` to be a Trait!") - } - .into()); - } - }; - - quote! { - ::body(&self).indent(1) - } - } - - None => { - quote! { - ::body(&self).indent(1) - } - } - }; - - quote! { - #[derive(Clone)] - #input - - impl AvError for #ident { - fn code(&self) -> String { #code.to_string() } - - fn title(&self) -> String { #title.to_string() } - } - - impl std::fmt::Display for #ident { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - use colored::Colorize; - - use crate::core::error::{ - AvError, - color, - Indentable, - }; - - writeln! ( - f, - "{} -- {}:", - format!("{}", self.code()).bold().color(color::ERROR), - self.title().color(color::ERROR), - )?; - - write! ( - f, - "{}", - #line - ) - } - } - - impl std::fmt::Debug for #ident { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - ::fmt(&self, f) - } - } - - impl std::error::Error for #ident {} - } - .into() -} - -/// -/// ## AvError Description -/// Generates a TraceableError::description implementation for -/// a new error struct. -/// -/// This works exactly the same way as `format!(...)` -/// be sure to include an addition pair of brackets -/// as the contents of this macro must be a tuple expression. -/// -/// ### Example -/// ``` -/// impl TraceableError for MyCustomError { -/// /* . . . */ -/// description!("Invalid option `{}`", self.option.blue()); -/// } -/// ``` -/// -/// -#[proc_macro] -#[allow(non_snake_case)] -pub fn description(attrs: proc_macro::TokenStream) -> proc_macro::TokenStream { - let args: ExprTuple = parse_macro_input!(attrs as syn::ExprTuple); - - quote! { - fn description(&self) -> String { format!#args } - } - .into() -} - -#[proc_macro] -pub fn name(attrs: proc_macro::TokenStream) -> proc_macro::TokenStream { - let args: LitStr = parse_macro_input!(attrs as syn::LitStr); - quote! { - fn name<'a>(&self) -> &'a str { #args } - } - .into() -} - -/// -/// ## TraceableError Location -/// Generates a TraceableError::Location implementation for -/// a new error struct. -/// -/// The contents of this macro must be a reference to -/// field within `self` -/// -/// ### Example -/// ``` -/// impl TraceableError for MyCustomError { -/// location!(&self.location); -/// /* . . . */ -/// } -/// ``` -/// -/// -#[proc_macro] -pub fn location(attrs: proc_macro::TokenStream) -> proc_macro::TokenStream { - let args: ExprReference = parse_macro_input!(attrs as syn::ExprReference); - - quote! { - fn location(&self) -> &crate::core::error::Traceable { - #args - } - } - .into() -} diff --git a/src/main.rs b/src/main.rs index 3bb8f2b..f328e4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,33 +1 @@ -//! -//! # Navda -//! -//! The Wayland compositor behind AvdanOS. -//! -//! Based off the [Smithay](https://github.com/Smithay/smithay) -//! library. -//! -//! If you are looking for user-oriented documentation, -//! you are in the wrong place! Please use -//! [docs.avdanos.org](https://docs.avdanos.org) instead. -//! - -pub mod compat; -mod compositor; -mod consts; -pub mod core; - -pub(crate) use crate::config::Config; -use crate::consts as CONST; - -use std::error::Error; - -pub mod config; - -fn main() -> Result<(), Box> { - println!("\n"); - // Load Nadva's Config - Config::load().unwrap(); - - compositor::start()?; - Ok(()) -} +fn main() {} From 49936cb675d561fad2d7ba6a59a2c86b5b3d5f8e Mon Sep 17 00:00:00 2001 From: Sammy99jsp Date: Wed, 22 Jul 2026 16:39:28 +0100 Subject: [PATCH 2/8] Get Vulkan+DRM+GBM working --- .gitignore | 6 +- Cargo.lock | 2375 +++++++++++++++++ Cargo.toml | 19 +- README.md | 4 + crates/compositor/Cargo.toml | 13 + crates/compositor/src/backend/mod.rs | 1 + .../compositor/src/backend/tty/atomic_req.rs | 401 +++ .../src/backend/tty/format_modifier.rs | 147 + crates/compositor/src/backend/tty/mod.rs | 106 + crates/compositor/src/backend/tty/utils.rs | 61 + crates/compositor/src/backend/tty/vulkan.rs | 480 ++++ crates/compositor/src/colors.rs | 1002 +++++++ crates/compositor/src/config/mod.rs | 0 crates/compositor/src/main.rs | 767 ++++++ src/main.rs | 1 - 15 files changed, 5362 insertions(+), 21 deletions(-) create mode 100644 README.md create mode 100644 crates/compositor/Cargo.toml create mode 100644 crates/compositor/src/backend/mod.rs create mode 100644 crates/compositor/src/backend/tty/atomic_req.rs create mode 100644 crates/compositor/src/backend/tty/format_modifier.rs create mode 100644 crates/compositor/src/backend/tty/mod.rs create mode 100644 crates/compositor/src/backend/tty/utils.rs create mode 100644 crates/compositor/src/backend/tty/vulkan.rs create mode 100644 crates/compositor/src/colors.rs create mode 100644 crates/compositor/src/config/mod.rs create mode 100644 crates/compositor/src/main.rs delete mode 100644 src/main.rs diff --git a/.gitignore b/.gitignore index 544bc28..c41cc9e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1 @@ -target -.DS_Store -Cargo.lock -.cargo -*.log +/target \ No newline at end of file diff --git a/Cargo.lock b/Cargo.lock index ae72646..32d332b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,2381 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "ahash" +version = "0.8.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a15f179cd60c4584b8a8c596927aadc462e27f2ca70c04e0071964a73ba7a75" +dependencies = [ + "cfg-if", + "getrandom 0.3.4", + "once_cell", + "version_check", + "zerocopy", +] + +[[package]] +name = "aho-corasick" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddd31a130427c27518df266943a5308ed92d4b226cc639f5a8f1002816174301" +dependencies = [ + "memchr", +] + +[[package]] +name = "aliasable" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "250f629c0161ad8107cf89319e990051fae62832fd343083bea452d93e2205fd" + +[[package]] +name = "android-activity" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0f2a1bb052857d5dd49572219344a7332b31b76405648eabac5bc68978251bcd" +dependencies = [ + "android-properties", + "bitflags", + "cc", + "jni", + "libc", + "log", + "ndk", + "ndk-context", + "ndk-sys", + "num_enum", + "thiserror 2.0.19", +] + +[[package]] +name = "android-properties" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc7eb209b1518d6bb87b283c20095f5228ecda460da70b44f0802523dea6da04" + +[[package]] +name = "anyhow" +version = "1.0.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a4385e2e34eb35d6b3efe798b9eb88096925d87726c0798709bf56d9ed84af3" + +[[package]] +name = "appendlist" +version = "1.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e149dc73cd30538307e7ffa2acd3d2221148eaeed4871f246657b1c3eaa1cbd2" + +[[package]] +name = "as-raw-xcb-connection" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "175571dd1d178ced59193a6fc02dde1b972eb0bc56c892cde9beeceac5bf0f6b" + +[[package]] +name = "ash" +version = "0.38.0+1.3.281" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bb44936d800fea8f016d7f2311c6a4f97aebd5dc86f09906139ec848cf3a46f" +dependencies = [ + "libloading", +] + +[[package]] +name = "atomic-waker" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1505bd5d3d116872e7271a6d4e16d81d0c8570876c8de68093a09ac269d8aac0" + +[[package]] +name = "atomic_float" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "628d228f918ac3b82fe590352cc719d30664a0c13ca3a60266fe02c7132d480a" + [[package]] name = "avdanos-compositor" version = "0.0.1" +dependencies = [ + "anyhow", + "glob", + "log", + "pretty_env_logger", + "smithay", + "thiserror 2.0.19", +] + +[[package]] +name = "bitflags" +version = "2.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b588b76d00fde79687d7646a9b5bdf3cc0f655e0bbd080335a95d7e96f3587da" + +[[package]] +name = "block-buffer" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3078c7629b62d3f0439517fa394996acacc5cbc91c5a20d8c658e77abd503a71" +dependencies = [ + "generic-array", +] + +[[package]] +name = "block2" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdeb9d870516001442e364c5220d3574d2da8dc765554b4a617230d33fa58ef5" +dependencies = [ + "objc2", +] + +[[package]] +name = "borsh" +version = "1.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a88b7ea17d208c4193f2c1e6de3c35fe71f98c96982d5ced308bdcc749ff6e1f" +dependencies = [ + "bytes", + "cfg_aliases", +] + +[[package]] +name = "bumpalo" +version = "3.20.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "72f5acc6cb2ba439de613abc23857ec3d78374d8ed5ac84e9d11336e87da8649" + +[[package]] +name = "bytemuck" +version = "1.25.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6aedf8ae72766347502cf3cb4f41cf5e9cc37d28bee90f1fdaaae15f9cf9424" +dependencies = [ + "bytemuck_derive", +] + +[[package]] +name = "bytemuck_derive" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f65693059b6b9c588b9f62fed1cedbf0a8b805631457ea162d68f0de186f3de5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + +[[package]] +name = "calloop" +version = "0.14.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4dbf9978365bac10f54d1d4b04f7ce4427e51f71d61f2fe15e3fed5166474df7" +dependencies = [ + "bitflags", + "polling", + "rustix 1.1.4", + "slab", + "tracing", +] + +[[package]] +name = "calloop-wayland-source" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "138efcf0940a02ebf0cc8d1eff41a1682a46b431630f4c52450d6265876021fa" +dependencies = [ + "calloop", + "rustix 1.1.4", + "wayland-backend", + "wayland-client", +] + +[[package]] +name = "cc" +version = "1.2.67" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e17dd265a7d0f31ef544e1b20e03add05d3b45b491b633b10d67145d2acc1a38" +dependencies = [ + "find-msvc-tools", + "jobserver", + "libc", + "shlex", +] + +[[package]] +name = "cfg-if" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801" + +[[package]] +name = "cfg_aliases" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" + +[[package]] +name = "combine" +version = "4.6.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba5a308b75df32fe02788e748662718f03fde005016435c444eea572398219fd" +dependencies = [ + "bytes", + "memchr", +] + +[[package]] +name = "concurrent-queue" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ca0197aee26d1ae37445ee532fefce43251d24cc7c166799f4d46817f1d3973" +dependencies = [ + "crossbeam-utils", +] + +[[package]] +name = "cpufeatures" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280" +dependencies = [ + "libc", +] + +[[package]] +name = "crossbeam-utils" +version = "0.8.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "61803da095bee82a81bb1a452ecc25d3b2f1416d1897eb86430c6159ef717c17" + +[[package]] +name = "crypto-common" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78c8292055d1c1df0cce5d180393dc8cce0abec0a7102adb6c7b1eef6016d60a" +dependencies = [ + "generic-array", + "typenum", +] + +[[package]] +name = "cursor-icon" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f27ae1dd37df86211c42e150270f82743308803d90a6f6e6651cd730d5e1732f" + +[[package]] +name = "digest" +version = "0.10.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9ed9a281f7bc9b7576e61468ba615a66a5c8cfdff42420a70aa82701a3b1e292" +dependencies = [ + "block-buffer", + "crypto-common", +] + +[[package]] +name = "dispatch2" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e0e367e4e7da84520dedcac1901e4da967309406d1e51017ae1abfb97adbd38" +dependencies = [ + "bitflags", + "objc2", +] + +[[package]] +name = "dlib" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ab8ecd87370524b461f8557c119c405552c396ed91fc0a8eec68679eab26f94a" +dependencies = [ + "libloading", +] + +[[package]] +name = "downcast-rs" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75b325c5dbd37f80359721ad39aca5a29fb04c89279657cffdda8736d0c0b9d2" + +[[package]] +name = "dpi" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8b14ccef22fc6f5a8f4d7d768562a182c04ce9a3b3157b91390b52ddfdf1a76" + +[[package]] +name = "drm" +version = "0.14.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "80bc8c5c6c2941f70a55c15f8d9f00f9710ebda3ffda98075f996a0e6c92756f" +dependencies = [ + "bitflags", + "bytemuck", + "drm-ffi", + "drm-fourcc", + "libc", + "rustix 0.38.44", +] + +[[package]] +name = "drm-ffi" +version = "0.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51a91c9b32ac4e8105dec255e849e0d66e27d7c34d184364fb93e469db08f690" +dependencies = [ + "drm-sys", + "rustix 1.1.4", +] + +[[package]] +name = "drm-fourcc" +version = "2.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0aafbcdb8afc29c1a7ee5fbe53b5d62f4565b35a042a662ca9fecd0b54dae6f4" + +[[package]] +name = "drm-sys" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ecc8e1361066d91f5ffccff060a3c3be9c3ecde15be2959c1937595f7a82a9f8" +dependencies = [ + "libc", + "linux-raw-sys 0.9.4", +] + +[[package]] +name = "encoding_rs" +version = "0.8.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75030f3c4f45dafd7586dd6780965a8c7e8e285a5ecb86713e63a79c5b2766f3" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "enumflags2" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1027f7680c853e056ebcec683615fb6fbbc07dbaa13b4d5d9442b146ded4ecef" +dependencies = [ + "enumflags2_derive", +] + +[[package]] +name = "enumflags2_derive" +version = "0.7.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67c78a4d8fdf9953a5c9d458f9efe940fd97a0cab0941c075a813ac594733827" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "env_logger" +version = "0.10.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cd405aab171cb85d6735e5c8d9db038c17d3ca007a4d2c25f337935c3d90580" +dependencies = [ + "humantime", + "is-terminal", + "log", + "regex", + "termcolor", +] + +[[package]] +name = "equivalent" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f" + +[[package]] +name = "errno" +version = "0.3.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "fastrand" +version = "2.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" + +[[package]] +name = "find-msvc-tools" +version = "0.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" + +[[package]] +name = "futures-core" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7e3450815272ef58cec6d564423f6e755e25379b217b0bc688e295ba24df6b1d" + +[[package]] +name = "futures-task" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "037711b3d59c33004d3856fbdc83b99d4ff37a24768fa1be9ce3538a1cde4393" + +[[package]] +name = "futures-util" +version = "0.3.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389ca41296e6190b48053de0321d02a77f32f8a5d2461dd38762c0593805c6d6" +dependencies = [ + "futures-core", + "futures-task", + "pin-project-lite", + "slab", +] + +[[package]] +name = "gbm" +version = "0.18.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ce852e998d3ca5e4a97014fb31c940dc5ef344ec7d364984525fd11e8a547e6a" +dependencies = [ + "bitflags", + "drm", + "drm-fourcc", + "gbm-sys", + "libc", +] + +[[package]] +name = "gbm-sys" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c13a5f2acc785d8fb6bf6b7ab6bfb0ef5dad4f4d97e8e70bb8e470722312f76f" +dependencies = [ + "libc", +] + +[[package]] +name = "generic-array" +version = "0.14.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" +dependencies = [ + "typenum", + "version_check", +] + +[[package]] +name = "gethostname" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bd49230192a3797a9a4d6abe9b3eed6f7fa4c8a8a4947977c6f80025f92cbd8" +dependencies = [ + "rustix 1.1.4", + "windows-link", +] + +[[package]] +name = "getrandom" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" +dependencies = [ + "cfg-if", + "libc", + "r-efi 5.3.0", + "wasip2", +] + +[[package]] +name = "getrandom" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] + +[[package]] +name = "gl_generator" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1a95dfc23a2b4a9a2f5ab41d194f8bfda3cabec42af4e39f08c339eb2a0c124d" +dependencies = [ + "khronos_api", + "log", + "xml-rs", +] + +[[package]] +name = "glam" +version = "0.33.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f22fb22f065b308be0d8724e3706c7fa3fc2a6c7d6899df4cad7860e7a75436" + +[[package]] +name = "glob" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0cc23270f6e1808e30a928bdc84dea0b9b4136a8bc82338574f23baf47bbd280" + +[[package]] +name = "hashbrown" +version = "0.17.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" + +[[package]] +name = "hermit-abi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d231dfb89cfffdbc30e7fc41579ed6066ad03abda9e567ccafae602b97ec5024" + +[[package]] +name = "hermit-abi" +version = "0.5.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc0fef456e4baa96da950455cd02c081ca953b141298e41db3fc7e36b1da849c" + +[[package]] +name = "humantime" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "15cdd26707701c53297e2fa6afb323d55fbc1d0810c3aec078ae3ef0424c3c15" + +[[package]] +name = "indexmap" +version = "2.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9" +dependencies = [ + "equivalent", + "hashbrown", +] + +[[package]] +name = "input" +version = "0.10.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f9793345a65d71317763a33066b5d8351f8760dde8d4930fe9e39b5f14a7959d" +dependencies = [ + "bitflags", + "input-sys", + "libc", + "udev", +] + +[[package]] +name = "input-sys" +version = "1.19.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36eee07d8e02bd95bf52b2e642cf13d33701b94c6e4b04fbf1d1fb07e9cb19e7" + +[[package]] +name = "io-lifetimes" +version = "1.0.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eae7b9aee968036d54dce06cebaefd919e4472e753296daccd6d344e3e2df0c2" +dependencies = [ + "hermit-abi 0.3.9", + "libc", + "windows-sys 0.48.0", +] + +[[package]] +name = "is-terminal" +version = "0.4.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46" +dependencies = [ + "hermit-abi 0.5.2", + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "jni" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5efd9a482cf3a427f00d6b35f14332adc7902ce91efb778580e180ff90fa3498" +dependencies = [ + "cfg-if", + "combine", + "jni-macros", + "jni-sys 0.4.1", + "log", + "simd_cesu8", + "thiserror 2.0.19", + "walkdir", + "windows-link", +] + +[[package]] +name = "jni-macros" +version = "0.22.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a00109accc170f0bdb141fed3e393c565b6f5e072365c3bd58f5b062591560a3" +dependencies = [ + "proc-macro2", + "quote", + "rustc_version", + "simd_cesu8", + "syn 2.0.119", +] + +[[package]] +name = "jni-sys" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41a652e1f9b6e0275df1f15b32661cf0d4b78d4d87ddec5e0c3c20f097433258" +dependencies = [ + "jni-sys 0.4.1", +] + +[[package]] +name = "jni-sys" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6377a88cb3910bee9b0fa88d4f42e1d2da8e79915598f65fb0c7ee14c878af2" +dependencies = [ + "jni-sys-macros", +] + +[[package]] +name = "jni-sys-macros" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38c0b942f458fe50cdac086d2f946512305e5631e720728f2a61aabcd47a6264" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "jobserver" +version = "0.1.35" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1c00acbd29eabad4a2392fa0e921c874934dbbf4194312ad20f04a0ed67a3cb3" +dependencies = [ + "getrandom 0.4.3", + "libc", +] + +[[package]] +name = "js-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53b44bfcdb3f8d5837a46dae1ca9660a837176eee74a28b229bc626816589102" +dependencies = [ + "cfg-if", + "futures-util", + "wasm-bindgen", +] + +[[package]] +name = "keyboard-types" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fbe853b403ae61a04233030ae8a79d94975281ed9770a1f9e246732b534b28d" +dependencies = [ + "bitflags", + "serde", +] + +[[package]] +name = "khronos_api" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2db585e1d738fc771bf08a151420d3ed193d9d895a36df7f6f8a9456b911ddc" + +[[package]] +name = "libc" +version = "0.2.186" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68ab91017fe16c622486840e4c83c9a37afeff978bd239b5293d61ece587de66" + +[[package]] +name = "libloading" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d7c4b02199fee7c5d21a5ae7d8cfa79a6ef5bb2fc834d6e9058e89c825efdc55" +dependencies = [ + "cfg-if", + "windows-link", +] + +[[package]] +name = "libredox" +version = "0.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c943259e342f1e06ff2da7a83eabdfe7f92ce10262688dbf1895ff0b3e6e4652" +dependencies = [ + "bitflags", + "libc", + "plain", + "redox_syscall 0.9.0", +] + +[[package]] +name = "libseat" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6656c69b6e0366ffb83faa232f3e4fda5fc91161722d140f2c95ce2e90b1ef31" +dependencies = [ + "errno", + "libseat-sys", + "log", +] + +[[package]] +name = "libseat-sys" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e8a484fa48be5f62a8d3ffed767779d0ab808cfead91de98ef4362d469097dfb" +dependencies = [ + "pkg-config", +] + +[[package]] +name = "libudev-sys" +version = "0.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3c8469b4a23b962c1396b9b451dda50ef5b283e8dd309d69033475fa9b334324" +dependencies = [ + "libc", + "pkg-config", +] + +[[package]] +name = "linux-raw-sys" +version = "0.4.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d26c52dbd32dccf2d10cac7725f8eae5296885fb5703b261f7d0a0739ec807ab" + +[[package]] +name = "linux-raw-sys" +version = "0.9.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cd945864f07fe9f5371a27ad7b52a172b4b499999f1d97574c9fa68373937e12" + +[[package]] +name = "linux-raw-sys" +version = "0.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a66949e030da00e8c7d4434b251670a91556f4144941d37452769c25d58a53" + +[[package]] +name = "log" +version = "0.4.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ceec5bc11778974d1bcb055b18002eba7f4b3518b6a0081b3af5f21666da9ad" + +[[package]] +name = "memchr" +version = "2.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf8baf1c55e62ffcace7a9f06f4bd9cd3f0c4beb022d3b367256b91b87513d98" + +[[package]] +name = "memmap2" +version = "0.9.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1219ed1b7f229ee7104d281dd01d6802fe28bb6e95d292942c4daacdeb798c0" +dependencies = [ + "libc", +] + +[[package]] +name = "ndk" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3f42e7bbe13d351b6bead8286a43aac9534b82bd3cc43e47037f012ebfd62d4" +dependencies = [ + "bitflags", + "jni-sys 0.3.1", + "log", + "ndk-sys", + "num_enum", + "raw-window-handle", + "thiserror 1.0.69", +] + +[[package]] +name = "ndk-context" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "27b02d87554356db9e9a873add8782d4ea6e3e58ea071a9adb9a2e8ddb884a8b" + +[[package]] +name = "ndk-sys" +version = "0.6.0+11769913" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee6cda3051665f1fb8d9e08fc35c96d5a244fb1be711a03b71118828afc9a873" +dependencies = [ + "jni-sys 0.3.1", +] + +[[package]] +name = "num_enum" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0bca838442ec211fa11de3a8b0e0e8f3a4522575b5c4c06ed722e005036f26" +dependencies = [ + "num_enum_derive", + "rustversion", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680998035259dcfcafe653688bf2aa6d3e2dc05e98be6ab46afb089dc84f1df8" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "objc2" +version = "0.6.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a12a8ed07aefc768292f076dc3ac8c48f3781c8f2d5851dd3d98950e8c5a89f" +dependencies = [ + "objc2-encode", +] + +[[package]] +name = "objc2-app-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d49e936b501e5c5bf01fda3a9452ff86dc3ea98ad5f283e1455153142d97518c" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "objc2-core-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536" +dependencies = [ + "bitflags", + "block2", + "dispatch2", + "objc2", +] + +[[package]] +name = "objc2-core-graphics" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e022c9d066895efa1345f8e33e584b9f958da2fd4cd116792e15e07e4720a807" +dependencies = [ + "bitflags", + "libc", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-core-video" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d425caf1df73233f29fd8a5c3e5edbc30d2d4307870f802d18f00d83dc5141a6" +dependencies = [ + "bitflags", + "objc2-core-foundation", + "objc2-core-graphics", +] + +[[package]] +name = "objc2-encode" +version = "4.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef25abbcd74fb2609453eb695bd2f860d389e457f67dc17cafc8b8cbc89d0c33" + +[[package]] +name = "objc2-foundation" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3e0adef53c21f888deb4fa59fc59f7eb17404926ee8a6f59f5df0fd7f9f3272" +dependencies = [ + "bitflags", + "block2", + "objc2", + "objc2-core-foundation", +] + +[[package]] +name = "objc2-ui-kit" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d87d638e33c06f577498cbcc50491496a3ed4246998a7fbba7ccb98b1e7eab22" +dependencies = [ + "bitflags", + "objc2", + "objc2-core-foundation", + "objc2-foundation", +] + +[[package]] +name = "once_cell" +version = "1.21.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f7c3e4beb33f85d45ae3e3a1792185706c8e16d043238c593331cc7cd313b50" + +[[package]] +name = "orbclient" +version = "0.3.55" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5df339f526ea9a60e371768d50efc2f2508c7203290731565d1f7a6f71d21747" +dependencies = [ + "libc", + "libredox", +] + +[[package]] +name = "paste" +version = "1.0.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" + +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + +[[package]] +name = "pin-project" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2466b2336ed02bcdca6b294417127b90ec92038d1d5c4fbeac971a922e0e0924" +dependencies = [ + "pin-project-internal", +] + +[[package]] +name = "pin-project-internal" +version = "1.1.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c96395f0a926bc13b1c17622aaddda1ecb55d49c8f1bf9777e4d877800a43f8b" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "pin-project-lite" +version = "0.2.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a89322df9ebe1c1578d689c92318e070967d1042b512afbe49518723f4e6d5cd" + +[[package]] +name = "pixman" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cea217d496c19ac0a8e502b37078e1f683d16344adee9eb247a5d57c165e1edf" +dependencies = [ + "drm-fourcc", + "paste", + "pixman-sys", + "thiserror 1.0.69", +] + +[[package]] +name = "pixman-sys" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1a0483e89e81d7915defe83c51f23f6800594d64f6f4a21253ce87fd8444ada" + +[[package]] +name = "pkg-config" +version = "0.3.33" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19f132c84eca552bf34cab8ec81f1c1dcc229b811638f9d283dceabe58c5569e" + +[[package]] +name = "plain" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b4596b6d070b27117e987119b4dac604f3c58cfb0b191112e24771b2faeac1a6" + +[[package]] +name = "polling" +version = "3.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d0e4f59085d47d8241c88ead0f274e8a0cb551f3625263c05eb8dd897c34218" +dependencies = [ + "cfg-if", + "concurrent-queue", + "hermit-abi 0.5.2", + "pin-project-lite", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "ppv-lite86" +version = "0.2.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85eae3c4ed2f50dcfe72643da4befc30deadb458a9b590d720cde2f2b1e97da9" +dependencies = [ + "zerocopy", +] + +[[package]] +name = "pretty_env_logger" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "865724d4dbe39d9f3dd3b52b88d859d66bcb2d6a0acfd5ea68a65fb66d4bdc1c" +dependencies = [ + "env_logger", + "log", +] + +[[package]] +name = "proc-macro-crate" +version = "3.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro2" +version = "1.0.106" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fd00f0bb2e90d81d1044c2b32617f68fcb9fa3bb7640c23e9c748e53fb30934" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "profiling" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3d595e54a326bc53c1c197b32d295e14b169e3cfeaa8dc82b529f947fba6bcf5" +dependencies = [ + "profiling-procmacros", +] + +[[package]] +name = "profiling-procmacros" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4488a4a36b9a4ba6b9334a32a39971f77c1436ec82c38707bce707699cc3bbcb" +dependencies = [ + "quote", + "syn 2.0.119", +] + +[[package]] +name = "quick-xml" +version = "0.39.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdcc8dd4e2f670d309a5f0e83fe36dfdc05af317008fea29144da1a2ac858e5e" +dependencies = [ + "memchr", +] + +[[package]] +name = "quote" +version = "1.0.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dfbc457d0c7a0759a614551b11a6409e5951f6c7537be1f1b7682b9ae9230368" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + +[[package]] +name = "r-efi" +version = "6.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dcc9c7d52a811697d2151c701e0d08956f92b0e24136cf4cf27b57a6a0d9bf" + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha", + "rand_core", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core", +] + +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + +[[package]] +name = "raw-window-handle" +version = "0.6.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20675572f6f24e9e76ef639bc5552774ed45f1c30e2951e1e99c59888861c539" + +[[package]] +name = "redox_syscall" +version = "0.5.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed2bf2547551a7053d6fdfafda3f938979645c44812fbfcda098faae3f1a362d" +dependencies = [ + "bitflags", +] + +[[package]] +name = "redox_syscall" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5102a6aaa05aa011a238e178e6bca86d2cb56fc9f586d37cb80f5bca6e07759" +dependencies = [ + "bitflags", +] + +[[package]] +name = "regex" +version = "1.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f020237b6c8eed93db2e2cb53c00c60a8e1bc73da7d073199a1180401450218d" +dependencies = [ + "aho-corasick", + "memchr", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "regex-automata" +version = "0.4.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8fcfdb36bda0c880c5931cdc7a2bcdc8ba4556847b9d912bca70bc94708711ad" +dependencies = [ + "aho-corasick", + "memchr", + "regex-syntax", +] + +[[package]] +name = "regex-syntax" +version = "0.8.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d6f6ff9a378485b298a5286656da665ba74413d36db0979633275d2e708145d4" + +[[package]] +name = "reis" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "81f3fedd2777cde52c1be5e572efbec485eac7b801c47820eda388d4f13b9c4b" +dependencies = [ + "calloop", + "enumflags2", + "log", + "rustix 1.1.4", +] + +[[package]] +name = "rustc_version" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfcb3a22ef46e85b45de6ee7e79d063319ebb6594faafcf1c225ea92ab6e9b92" +dependencies = [ + "semver", +] + +[[package]] +name = "rustix" +version = "0.38.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fdb5bc1ae2baa591800df16c9ca78619bf65c0488b41b96ccec5d11220d8c154" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.4.15", + "windows-sys 0.59.0", +] + +[[package]] +name = "rustix" +version = "1.1.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6fe4565b9518b83ef4f91bb47ce29620ca828bd32cb7e408f0062e9930ba190" +dependencies = [ + "bitflags", + "errno", + "libc", + "linux-raw-sys 0.12.1", + "windows-sys 0.61.2", +] + +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + +[[package]] +name = "same-file" +version = "1.0.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "scoped-tls" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1cf6437eb19a8f4a6cc0f7dca544973b0b78843adbfeb3683d1a94a0024a294" + +[[package]] +name = "scopeguard" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" + +[[package]] +name = "semver" +version = "1.0.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8a7852d02fc848982e0c167ef163aaff9cd91dc640ba85e263cb1ce46fae51cd" + +[[package]] +name = "serde" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a8e94ea7f378bd32cbbd37198a4a91436180c5bb472411e48b5ec2e2124ae9e" +dependencies = [ + "serde_core", + "serde_derive", +] + +[[package]] +name = "serde_core" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "41d385c7d4ca58e59fc732af25c3983b67ac852c1a25000afe1175de458b67ad" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.228" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "sha2" +version = "0.10.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7507d819769d01a365ab707794a4084392c824f54a7a6a7862f8c3d0892b283" +dependencies = [ + "cfg-if", + "cpufeatures", + "digest", +] + +[[package]] +name = "shlex" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" + +[[package]] +name = "simd_cesu8" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "11031e251abf8611c80f460e19dbdeb54a66db918e49c65a7065b46ac7aec520" +dependencies = [ + "rustc_version", + "simdutf8", +] + +[[package]] +name = "simdutf8" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" + +[[package]] +name = "slab" +version = "0.4.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c790de23124f9ab44544d7ac05d60440adc586479ce501c1d6d7da3cd8c9cf5" + +[[package]] +name = "smallvec" +version = "1.15.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ed6a63f02c8539c91a8685a86f4099661ba3da017932f6ebbea6de3f0fa7c90" + +[[package]] +name = "smithay" +version = "0.7.0" +source = "git+https://github.com/Smithay/smithay.git?rev=812bd332#812bd33259ff58810dadef6086d8385eeac1ca55" +dependencies = [ + "aliasable", + "appendlist", + "ash", + "atomic_float", + "bitflags", + "calloop", + "cc", + "cursor-icon", + "downcast-rs", + "drm", + "drm-ffi", + "drm-fourcc", + "encoding_rs", + "errno", + "gbm", + "gl_generator", + "glam", + "indexmap", + "input", + "libc", + "libloading", + "libseat", + "pixman", + "pkg-config", + "profiling", + "rand", + "reis", + "rustix 1.1.4", + "scopeguard", + "sha2", + "smallvec", + "tempfile", + "thiserror 2.0.19", + "tracing", + "udev", + "wayland-client", + "wayland-cursor", + "wayland-egl", + "wayland-protocols", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-server", + "winit", + "x11rb", + "xkbcommon", +] + +[[package]] +name = "smithay-client-toolkit" +version = "0.20.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0512da38f5e2b31201a93524adb8d3136276fa4fe4aafab4e1f727a82b534cc0" +dependencies = [ + "bitflags", + "calloop", + "calloop-wayland-source", + "cursor-icon", + "libc", + "log", + "memmap2", + "rustix 1.1.4", + "thiserror 2.0.19", + "wayland-backend", + "wayland-client", + "wayland-csd-frame", + "wayland-cursor", + "wayland-protocols", + "wayland-protocols-experimental", + "wayland-protocols-misc", + "wayland-protocols-wlr", + "wayland-scanner", + "xkeysym", +] + +[[package]] +name = "smol_str" +version = "0.3.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4aaa7368fcf4852a4c2dd92df0cace6a71f2091ca0a23391ce7f3a31833f1523" +dependencies = [ + "borsh", + "serde_core", +] + +[[package]] +name = "syn" +version = "2.0.119" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "872831b642d1a07999a962a351ed35b955ea2cfc8f3862091e2a240a84f17297" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "syn" +version = "3.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a207d6d6a2b7fc470b80443726053f18a2481b7e1eee970597051596567987a3" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "tempfile" +version = "3.27.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32497e9a4c7b38532efcdebeef879707aa9f794296a4f0244f6f69e9bc8574bd" +dependencies = [ + "fastrand", + "getrandom 0.4.3", + "once_cell", + "rustix 1.1.4", + "windows-sys 0.61.2", +] + +[[package]] +name = "termcolor" +version = "1.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "06794f8f6c5c898b3275aebefa6b8a1cb24cd2c6c79397ab15774837a0bc5755" +dependencies = [ + "winapi-util", +] + +[[package]] +name = "thiserror" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6aaf5339b578ea85b50e080feb250a3e8ae8cfcdff9a461c9ec2904bc923f52" +dependencies = [ + "thiserror-impl 1.0.69", +] + +[[package]] +name = "thiserror" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09a43598840e33d5b0331f38c5e30d13bb11c11210a4b58f0d9b18a5a5eefcd9" +dependencies = [ + "thiserror-impl 2.0.19", +] + +[[package]] +name = "thiserror-impl" +version = "1.0.69" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fee6c4efc90059e10f81e6d42c60a18f76588c3d74cb83a0b242a2b6c7504c1" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "thiserror-impl" +version = "2.0.19" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "43cbfe0cf76104d42a574802844187e84a305e531ed54455f11fbde0f10541cd" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.2", +] + +[[package]] +name = "toml_datetime" +version = "1.1.1+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7" +dependencies = [ + "serde_core", +] + +[[package]] +name = "toml_edit" +version = "0.25.13+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b" +dependencies = [ + "indexmap", + "toml_datetime", + "toml_parser", + "winnow", +] + +[[package]] +name = "toml_parser" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2abe9b86193656635d2411dc43050282ca48aa31c2451210f4202550afb7526" +dependencies = [ + "winnow", +] + +[[package]] +name = "tracing" +version = "0.1.44" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" +dependencies = [ + "log", + "pin-project-lite", + "tracing-attributes", + "tracing-core", +] + +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] + +[[package]] +name = "tracing-core" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", +] + +[[package]] +name = "typenum" +version = "1.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" + +[[package]] +name = "udev" +version = "0.9.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "af4e37e9ea4401fc841ff54b9ddfc9be1079b1e89434c1a6a865dd68980f7e9f" +dependencies = [ + "io-lifetimes", + "libc", + "libudev-sys", + "pkg-config", +] + +[[package]] +name = "unicode-ident" +version = "1.0.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e6e4313cd5fcd3dad5cafa179702e2b244f760991f45397d14d4ebf38247da75" + +[[package]] +name = "unicode-segmentation" +version = "1.13.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6f5d3c3b1bf09027a88a6bc961fc00497d651009560b5463668dc81b0fa87a8" + +[[package]] +name = "version_check" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a" + +[[package]] +name = "walkdir" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29790946404f91d9c5d06f9874efddea1dc06c5efe94541a7d6863108e3a5e4b" +dependencies = [ + "same-file", + "winapi-util", +] + +[[package]] +name = "wasip2" +version = "1.0.4+wasi-0.2.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" +dependencies = [ + "wit-bindgen", +] + +[[package]] +name = "wasm-bindgen" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4b067c0c11094aef6b7a801c1e34a26affafdf3d051dba08456b868789aaf9a4" +dependencies = [ + "cfg-if", + "once_cell", + "rustversion", + "wasm-bindgen-macro", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-futures" +version = "0.4.76" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c62df1340f32221cb9c54d6a27b030e3dba64361d4a95bed55f9aacb44da291d" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "wasm-bindgen-macro" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "167ce5e579f6bcf889c4f7175a8a5a585de84e8ff93976ce393efa5f2837aab1" +dependencies = [ + "quote", + "wasm-bindgen-macro-support", +] + +[[package]] +name = "wasm-bindgen-macro-support" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3997c7839262f4ef12cf90b818d6340c18e80f263f1a94bf157d0ec4420380e" +dependencies = [ + "bumpalo", + "proc-macro2", + "quote", + "syn 2.0.119", + "wasm-bindgen-shared", +] + +[[package]] +name = "wasm-bindgen-shared" +version = "0.2.126" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc1b4cb0cc549fcf58d7dfc081778139b3d283a081644e833e84682ad71cea24" +dependencies = [ + "unicode-ident", +] + +[[package]] +name = "wayland-backend" +version = "0.3.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2857dd20b54e916ec7253b3d6b4d5c4d7d4ca2c33c2e11c6c76a99bd8744755d" +dependencies = [ + "cc", + "downcast-rs", + "rustix 1.1.4", + "scoped-tls", + "smallvec", + "wayland-sys", +] + +[[package]] +name = "wayland-client" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "645c7c96bb74690c3189b5c9cb4ca1627062bb23693a4fad9d8c3de958260144" +dependencies = [ + "bitflags", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-csd-frame" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "625c5029dbd43d25e6aa9615e88b829a5cad13b2819c4ae129fdbb7c31ab4c7e" +dependencies = [ + "bitflags", + "cursor-icon", + "wayland-backend", +] + +[[package]] +name = "wayland-cursor" +version = "0.31.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4a52d18780be9b1314328a3de5f930b73d2200112e3849ca6cb11822793fb34d" +dependencies = [ + "rustix 1.1.4", + "wayland-client", + "xcursor", +] + +[[package]] +name = "wayland-egl" +version = "0.32.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b97bdb7c49e5bd9b7562f38ff84f0dad47079fdc9e926f691a787f6dbc05451" +dependencies = [ + "wayland-backend", + "wayland-sys", +] + +[[package]] +name = "wayland-protocols" +version = "0.32.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23d0c813de3daa2ed6520af85a3bd49b0e722a3078506899aa9686fea58dc4b6" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-scanner", + "wayland-server", +] + +[[package]] +name = "wayland-protocols-experimental" +version = "20250721.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "40a1f863128dcaaec790d7b4b396cc9b9a7a079e878e18c47e6c2d2c5a8dcbb1" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-misc" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6e9567599ef23e09b8dad6e429e5738d4509dfc46b3b21f32841a304d16b29c8" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", + "wayland-server", +] + +[[package]] +name = "wayland-protocols-plasma" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b6d8cf1eb2c1c31ed1f5643c88a6e53538129d4af80030c8cabd1f9fa884d91" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", +] + +[[package]] +name = "wayland-protocols-wlr" +version = "0.3.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "eb04e52f7836d7c7976c78ca0250d61e33873c34156a2a1fc9474828ec268234" +dependencies = [ + "bitflags", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-scanner", + "wayland-server", +] + +[[package]] +name = "wayland-scanner" +version = "0.31.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c324a910fd86ebdc364a3e61ec1f11737d3b1d6c273c0239ee8ff4bc0d24b4a" +dependencies = [ + "proc-macro2", + "quick-xml", + "quote", +] + +[[package]] +name = "wayland-server" +version = "0.31.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cc1846eb04c49182e04f4a099e2a830a2b745610bbc1d61246e206f29c7000a0" +dependencies = [ + "bitflags", + "downcast-rs", + "rustix 1.1.4", + "wayland-backend", + "wayland-scanner", +] + +[[package]] +name = "wayland-sys" +version = "0.31.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d8eab23fefc9e41f8e841df4a9c707e8a8c4ed26e944ef69297184de2785e3be" +dependencies = [ + "dlib", + "log", + "once_cell", + "pkg-config", +] + +[[package]] +name = "web-sys" +version = "0.3.103" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8622dcb61c0bcc9fffa6938bed81210af2da9a7e4a1a834b2e37a59b6dfb6141" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + +[[package]] +name = "winapi-util" +version = "0.1.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" +dependencies = [ + "windows-sys 0.61.2", +] + +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + +[[package]] +name = "windows-sys" +version = "0.48.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "677d2418bec65e3338edb076e806bc1ec15693c5d0104683f2efe857f61056a9" +dependencies = [ + "windows-targets 0.48.5", +] + +[[package]] +name = "windows-sys" +version = "0.59.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e38bc4d79ed67fd075bcc251a1c39b32a1776bbe92e5bef1f0bf1f8c531853b" +dependencies = [ + "windows-targets 0.52.6", +] + +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + +[[package]] +name = "windows-targets" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9a2fa6e2155d7247be68c096456083145c183cbbbc2764150dda45a87197940c" +dependencies = [ + "windows_aarch64_gnullvm 0.48.5", + "windows_aarch64_msvc 0.48.5", + "windows_i686_gnu 0.48.5", + "windows_i686_msvc 0.48.5", + "windows_x86_64_gnu 0.48.5", + "windows_x86_64_gnullvm 0.48.5", + "windows_x86_64_msvc 0.48.5", +] + +[[package]] +name = "windows-targets" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b724f72796e036ab90c1021d4780d4d3d648aca59e491e6b98e725b84e99973" +dependencies = [ + "windows_aarch64_gnullvm 0.52.6", + "windows_aarch64_msvc 0.52.6", + "windows_i686_gnu 0.52.6", + "windows_i686_gnullvm", + "windows_i686_msvc 0.52.6", + "windows_x86_64_gnu 0.52.6", + "windows_x86_64_gnullvm 0.52.6", + "windows_x86_64_msvc 0.52.6", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2b38e32f0abccf9987a4e3079dfb67dcd799fb61361e53e2882c3cbaf0d905d8" + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32a4622180e7a0ec044bb555404c800bc9fd9ec262ec147edd5989ccd0c02cd3" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dc35310971f3b2dbbf3f0690a219f40e2d9afcf64f9ab7cc1be722937c26b4bc" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ec2a7bb152e2252b53fa7803150007879548bc709c039df7627cabbd05d469" + +[[package]] +name = "windows_i686_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a75915e7def60c94dcef72200b9a8e58e5091744960da64ec734a6c6e9b3743e" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e9b5ad5ab802e97eb8e295ac6720e509ee4c243f69d781394014ebfe8bbfa0b" + +[[package]] +name = "windows_i686_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0eee52d38c090b3caa76c563b86c3a4bd71ef1a819287c19d586d7334ae8ed66" + +[[package]] +name = "windows_i686_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f55c233f70c4b27f66c523580f78f1004e8b5a8b659e05a4eb49d4166cca406" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "240948bc05c5e7c6dabba28bf89d89ffce3e303022809e73deaefe4f6ec56c66" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53d40abd2583d23e4718fddf1ebec84dbff8381c07cae67ff7768bbf19c6718e" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "147a5c80aabfbf0c7d901cb5895d1de30ef2907eb21fbbab29ca94c5b08b1a78" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b7b52767868a23d5bab768e390dc5f5c55825b6d30b86c844ff2dc7414044cc" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24d5b23dc417412679681396f2b49f3de8c1473deb516bd34410872eff51ed0d" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.48.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "589f6da84c646204747d1270a2a5661ea66ed1cced2631d546fdfb155959f9ec" + +[[package]] +name = "winit" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2879d2854d1a43e48f67322d4bd097afcb6eb8f8f775c8de0260a71aea1df1aa" +dependencies = [ + "bitflags", + "cfg_aliases", + "cursor-icon", + "dpi", + "libc", + "raw-window-handle", + "rustix 1.1.4", + "smol_str", + "tracing", + "winit-android", + "winit-appkit", + "winit-common", + "winit-core", + "winit-orbital", + "winit-uikit", + "winit-wayland", + "winit-web", + "winit-win32", + "winit-x11", +] + +[[package]] +name = "winit-android" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51d9c0d2cd93efec3a9f9ad819cfaf0834782403af7c0d248c784ec0c61761df" +dependencies = [ + "android-activity", + "bitflags", + "dpi", + "ndk", + "raw-window-handle", + "smol_str", + "tracing", + "winit-core", +] + +[[package]] +name = "winit-appkit" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21310ca07851a49c348e0c2cc768e36b52ca65afda2c2354d78ed4b90074d8aa" +dependencies = [ + "bitflags", + "block2", + "dispatch2", + "dpi", + "objc2", + "objc2-app-kit", + "objc2-core-foundation", + "objc2-core-graphics", + "objc2-core-video", + "objc2-foundation", + "raw-window-handle", + "smol_str", + "tracing", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-common" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45375fbac4cbb77260d83a30b1f9d8105880dbac99a9ae97f56656694680ff69" +dependencies = [ + "memmap2", + "objc2", + "objc2-core-foundation", + "smol_str", + "tracing", + "winit-core", + "x11-dl", + "xkbcommon-dl", +] + +[[package]] +name = "winit-core" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e4f0ccd7abb43740e2c6124ac7cae7d865ecec74eec63783e8922577ac232583" +dependencies = [ + "bitflags", + "cursor-icon", + "dpi", + "keyboard-types", + "raw-window-handle", + "smol_str", + "web-time", +] + +[[package]] +name = "winit-orbital" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "51ea1fb262e7209f265f12bd0cc792c399b14355675e65531e9c8a87db287d46" +dependencies = [ + "bitflags", + "dpi", + "orbclient", + "raw-window-handle", + "redox_syscall 0.5.18", + "smol_str", + "tracing", + "winit-core", +] + +[[package]] +name = "winit-uikit" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "680a356e798837d8eb274d4556e83bceaf81698194e31aafc5cfb8a9f2fab643" +dependencies = [ + "bitflags", + "block2", + "dispatch2", + "dpi", + "objc2", + "objc2-core-foundation", + "objc2-foundation", + "objc2-ui-kit", + "raw-window-handle", + "smol_str", + "tracing", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-wayland" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8ce5afb2ba07da603f84b722c95f9f9396d2cedae3944fb6c0cda4a6f88de545" +dependencies = [ + "ahash", + "bitflags", + "calloop", + "cursor-icon", + "dpi", + "libc", + "memmap2", + "raw-window-handle", + "rustix 1.1.4", + "smithay-client-toolkit", + "smol_str", + "tracing", + "wayland-backend", + "wayland-client", + "wayland-protocols", + "wayland-protocols-plasma", + "winit-common", + "winit-core", +] + +[[package]] +name = "winit-web" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c2490a953fb776fbbd5e295d54f1c3847f4f15b6c3929ec53c09acda6487a92" +dependencies = [ + "atomic-waker", + "bitflags", + "concurrent-queue", + "cursor-icon", + "dpi", + "js-sys", + "pin-project", + "raw-window-handle", + "smol_str", + "tracing", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", + "web-time", + "winit-core", +] + +[[package]] +name = "winit-win32" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "644ea78af0e858aa3b092e5d1c67c41995a98220c81813f1353b28bc8bb91eaa" +dependencies = [ + "bitflags", + "cursor-icon", + "dpi", + "raw-window-handle", + "smol_str", + "tracing", + "unicode-segmentation", + "windows-sys 0.59.0", + "winit-core", +] + +[[package]] +name = "winit-x11" +version = "0.31.0-beta.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa5b600756534c7041aa93cd0d244d44b09fca1b89e202bd1cd80dd9f3636c46" +dependencies = [ + "bitflags", + "bytemuck", + "calloop", + "cursor-icon", + "dpi", + "libc", + "percent-encoding", + "raw-window-handle", + "rustix 1.1.4", + "smol_str", + "tracing", + "winit-common", + "winit-core", + "x11-dl", + "x11rb", + "xkbcommon-dl", +] + +[[package]] +name = "winnow" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23b97319f7b8343df12cc98938e5c3eb436064524c8d2b4e30a1d3a36eecdf81" +dependencies = [ + "memchr", +] + +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "x11-dl" +version = "2.21.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "38735924fedd5314a6e548792904ed8c6de6636285cb9fec04d5b1db85c1516f" +dependencies = [ + "libc", + "once_cell", + "pkg-config", +] + +[[package]] +name = "x11rb" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9993aa5be5a26815fe2c3eacfc1fde061fc1a1f094bf1ad2a18bf9c495dd7414" +dependencies = [ + "as-raw-xcb-connection", + "gethostname", + "libc", + "libloading", + "once_cell", + "rustix 1.1.4", + "x11rb-protocol", + "xcursor", +] + +[[package]] +name = "x11rb-protocol" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" + +[[package]] +name = "xcursor" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bec9e4a500ca8864c5b47b8b482a73d62e4237670e5b5f1d6b9e3cae50f28f2b" + +[[package]] +name = "xkbcommon" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a7a974f48060a14e95705c01f24ad9c3345022f4d97441b8a36beb7ed5c4a02d" +dependencies = [ + "libc", + "memmap2", + "xkeysym", +] + +[[package]] +name = "xkbcommon-dl" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d039de8032a9a8856a6be89cea3e5d12fdd82306ab7c94d74e6deab2460651c5" +dependencies = [ + "bitflags", + "dlib", + "log", + "once_cell", + "xkeysym", +] + +[[package]] +name = "xkeysym" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9cc00251562a284751c9973bace760d86c0276c471b4be569fe6b068ee97a56" + +[[package]] +name = "xml-rs" +version = "0.8.28" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ae8337f8a065cfc972643663ea4279e04e7256de865aa66fe25cec5fb912d3f" + +[[package]] +name = "zerocopy" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b7cbbc0a705a0fd05cc3676525980d2bf5a9bc4adac6d6475209a7887cf59d19" +dependencies = [ + "zerocopy-derive", +] + +[[package]] +name = "zerocopy-derive" +version = "0.8.54" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e2e817b7b52d0c7358d3246da9d69935ebb18116b2b102b4230dac079b4862f5" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.119", +] diff --git a/Cargo.toml b/Cargo.toml index 1d8cc5a..22e48d5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,15 +1,4 @@ -[package] - -name = "avdanos-compositor" -description = "Wayland compositor made for AvdanOS" -documentation = "https://docs.avdanos.dev/" -homepage = "https://avdanos.dev/" -license = "MIT" -version = "0.0.1" -authors = [ - "Sammy ", -] -edition = "2024" - -[dependencies] - +[workspace] +resolver = "3" +members = ["crates/compositor"] +package.edition = "2024" \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9c52677 --- /dev/null +++ b/README.md @@ -0,0 +1,4 @@ +# Running the Compositor +## Requirements + +- Vulkan 1.3 (`VK_LAYER_KHRONOS_validation` for debug builds) \ No newline at end of file diff --git a/crates/compositor/Cargo.toml b/crates/compositor/Cargo.toml new file mode 100644 index 0000000..a2bf51f --- /dev/null +++ b/crates/compositor/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "avdanos-compositor" +version = "0.0.1" +authors = ["Sammy99jsp "] +edition.workspace = true + +[dependencies] +anyhow = "1.0.103" +glob = "0.3.3" +log = "0.4.33" +pretty_env_logger = "0.5.0" +smithay = { git = "https://github.com/Smithay/smithay.git", rev = "812bd332" } +thiserror = "2.0.19" diff --git a/crates/compositor/src/backend/mod.rs b/crates/compositor/src/backend/mod.rs new file mode 100644 index 0000000..cd8fd47 --- /dev/null +++ b/crates/compositor/src/backend/mod.rs @@ -0,0 +1 @@ +pub mod tty; diff --git a/crates/compositor/src/backend/tty/atomic_req.rs b/crates/compositor/src/backend/tty/atomic_req.rs new file mode 100644 index 0000000..90a8ecd --- /dev/null +++ b/crates/compositor/src/backend/tty/atomic_req.rs @@ -0,0 +1,401 @@ +use std::{ + collections::HashMap, + num::NonZeroU64, + ops::Deref, + os::fd::{AsRawFd, OwnedFd}, + sync::Arc, +}; + +use anyhow::Context; +use smithay::reexports::{ + drm::{ + self, + control::{Device, RawResourceHandle, atomic, property}, + }, + gbm, +}; + +pub use smithay::reexports::drm::control::AtomicCommitFlags; + +use crate::backend::tty::Card; + +#[derive(Debug)] +pub struct AtomicRequestCache { + card: Arc>, + cache: HashMap>, +} + +pub struct AtomicRequest<'a> { + cache: &'a mut AtomicRequestCache, + cleanup: Vec, + request: atomic::AtomicModeReq, +} + +impl<'a> Drop for AtomicRequest<'a> { + fn drop(&mut self) { + for cleanup in self.cleanup.drain(..) { + let _ = cleanup.failure(&self.cache.card); + } + } +} + +impl<'a> AtomicRequest<'a> { + pub fn set>( + mut self, + target: H, + prop: P, + value: P::Type, + ) -> anyhow::Result { + let atomic_value = value.to_value(&self.cache.card)?; + self.request.add_property( + target, + self.cache.property_handle(target, prop)?, + atomic_value, + ); + + self.cleanup.push(value.cleanup(atomic_value)); + + Ok(self) + } + + /// FIXME: Potential race condition where the blob is destroyed before the kernel actually applies + /// the commit when called with [drm::control::AtomicCommitFlags::NONBLOCK] + pub fn commit(mut self, flags: AtomicCommitFlags) -> std::io::Result<()> { + let request = std::mem::take(&mut self.request); + + let res = self.cache.card.atomic_commit(flags, request); + + let drop_fn = match res { + Ok(_) => Cleanup::success, + Err(_) => Cleanup::failure, + }; + + // Destroy any leftover resources here to prevent a resource leaks. + let cleanups = std::mem::take(&mut self.cleanup); + for cleanup in cleanups { + if let Err(e) = drop_fn(cleanup, &self.cache.card) { + log::warn!("failed to destroy property: {e}") + } + } + + res + } +} + +impl AtomicRequestCache { + pub fn new(card: Arc>) -> Self { + Self { + card, + cache: Default::default(), + } + } + + pub fn property_handle<'s, H: drm::control::ResourceHandle, P: Property<'s, H>>( + &mut self, + target: H, + _: P, + ) -> anyhow::Result { + let prop = if let Some(props) = self.cache.get(&target.into()) { + if let Some(&prop) = props.get(P::NAME) { + prop + } else { + anyhow::bail!("Property does not exist on this resource!"); + } + } else { + let map = self + .card + .get_properties(target)? + .as_hashmap(self.card.deref())? + .into_iter() + .map(|(name, info)| (name, info.handle())) + .collect::>(); + + let prop = map + .get(P::NAME) + .copied() + .context("Property does not exist on this resource")?; + + self.cache.insert(target.into(), map); + + prop + }; + + Ok(prop) + } + + pub fn request(&mut self) -> AtomicRequest<'_> { + AtomicRequest { + cache: self, + cleanup: Vec::new(), + request: atomic::AtomicModeReq::new(), + } + } +} + +pub trait AtomicReqType: Sized { + fn to_value(&self, card: &Card) -> std::io::Result>; + fn cleanup(self, _value: property::Value<'static>) -> Cleanup { + Cleanup::Nothing + } +} + +#[derive(Debug, Default)] +pub enum Cleanup { + #[default] + Nothing, + Blob(NonZeroU64), + Fd(OwnedFd), +} + +impl Cleanup { + pub fn success(self, card: &Card) -> std::io::Result<()> { + match self { + Cleanup::Nothing => Ok(()), + Cleanup::Blob(blob) => card.destroy_property_blob(blob.get()), + Cleanup::Fd(owned_fd) => { + // Kernel will close the fd now. + std::mem::forget(owned_fd); + Ok(()) + } + } + } + pub fn failure(self, card: &Card) -> std::io::Result<()> { + match self { + Cleanup::Nothing => Ok(()), + Cleanup::Blob(blob) => card.destroy_property_blob(blob.get()), + Cleanup::Fd(owned_fd) => { + drop(owned_fd); // Close the file descriptor. + Ok(()) + } + } + } +} + +pub trait ToBlob {} + +impl AtomicReqType for &B { + fn to_value(&self, card: &Card) -> std::io::Result> { + // This needs to be dereferenced, otherwise it will serialize just a pointer to mode!!! + card.create_property_blob(*self) + } + + fn cleanup(self, value: property::Value<'static>) -> Cleanup { + let property::Value::Blob(blob) = value else { + unreachable!() + }; + + NonZeroU64::new(blob).map_or_default(Cleanup::Blob) + } +} + +impl AtomicReqType for Option<&B> { + fn to_value(&self, card: &Card) -> std::io::Result> { + match self { + Some(b) => b.to_value(card), + None => Ok(property::Value::Blob(0)), + } + } + + fn cleanup(self, value: property::Value<'static>) -> Cleanup { + self.map_or_default(|blob| blob.cleanup(value)) + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Fixed16(u64); + +impl Fixed16 { + pub const ZERO: Self = Fixed16(0); + pub const fn integer(value: u16) -> Self { + Fixed16((value as u64) << 16) + } + + pub const fn fraction(f: f32) -> Option { + if f < 0.0 || f > u16::MAX as f32 { + return None; + } + + let integral = f.trunc() as u16; + + const PRECISION: f32 = 1.0 / 65536.0; + let fractional = (f.fract() / PRECISION).round().min(65535.) as u16; + + Some(Self(((integral as u64) << 16u64) | (fractional as u64))) + } +} + +impl AtomicReqType for u32 { + fn to_value(&self, _: &Card) -> std::io::Result> { + Ok(property::Value::UnsignedRange(*self as u64)) + } +} + +impl AtomicReqType for i32 { + fn to_value(&self, _: &Card) -> std::io::Result> { + Ok(property::Value::SignedRange(*self as i64)) + } +} + +impl AtomicReqType for Fixed16 { + fn to_value(&self, _: &Card) -> std::io::Result> { + Ok(property::Value::UnsignedRange(self.0)) + } +} + +impl AtomicReqType for bool { + fn to_value(&self, _: &Card) -> std::io::Result> { + Ok(property::Value::Boolean(*self)) + } +} + +macro_rules! handle_impl { + ($($type: path => $var: ident);* $(;)?) => { + $( + impl AtomicReqType for $type { + fn to_value(&self, _: &Card) -> std::io::Result> { + Ok(property::Value::$var(Some(*self))) + } + } + + impl AtomicReqType for Option<$type> { + fn to_value(&self, _: &Card) -> std::io::Result> { + Ok(property::Value::$var(*self)) + } + } + )* + }; +} + +handle_impl! { + drm::control::crtc::Handle => CRTC; + drm::control::connector::Handle => Connector; + drm::control::framebuffer::Handle => Framebuffer; + drm::control::plane::Handle => Plane; + drm::control::encoder::Handle => Encoder; +} + +pub trait Property<'a, T> { + const NAME: &'static str; + type Type: AtomicReqType + 'a; +} + +macro_rules! properties { + ( + $( + $(#[$($attr: tt)*])* + $prop: ident: $type: ty => $($target: path),*);* $(;)? + ) => { + $( + $(#[$($attr)*])* + #[derive(Debug, Clone, Copy)] + pub struct $prop; + + $( + impl Property<'_, $target> for $prop { + const NAME: &'static str = stringify!($prop); + type Type = $type; + } + )* + + )* + }; +} + +impl AtomicReqType for Option { + fn to_value(&self, _: &Card) -> std::io::Result> { + match self { + // We will handle ownership transfer later... + Some(fd) => Ok(property::Value::SignedRange(fd.as_raw_fd().into())), + + // As per the spec, `-1` means: no "fence" to wait on. + None => Ok(property::Value::SignedRange(-1)), + } + } + + fn cleanup(self, _: property::Value<'static>) -> Cleanup { + match self { + None => Cleanup::Nothing, + Some(fd) => Cleanup::Fd(fd), + } + } +} + +#[allow(nonstandard_style)] +pub mod props { + use std::os::fd::OwnedFd; + + use crate::backend::tty::atomic_req::ToBlob; + + use super::{Fixed16, Property}; + use smithay::reexports::drm; + + /// Default atomic CRTC property to set the mode for a CRTC. A 0 mode implies that the CRTC is entirely disabled + /// - all connectors must be of and active must be set to disabled, too. + #[derive(Debug, Clone, Copy)] + pub struct MODE_ID; + + impl ToBlob for drm::control::Mode {} + + impl<'a> Property<'a, drm::control::crtc::Handle> for MODE_ID { + const NAME: &'static str = stringify!(MODE_ID); + type Type = Option<&'a drm::control::Mode>; + } + + properties! { + + + ACTIVE: bool => drm::control::crtc::Handle; + + + // PLANE PROPERTIES // + + /// X coordinate offset for the source rectangle within the `drm_framebuffer`, in 16.16 fixed point ([Fixed16]). Must be positive. + SRC_X: Fixed16 => drm::control::plane::Handle; + + /// Y coordinate offset for the source rectangle within the `drm_framebuffer`, in 16.16 fixed point ([Fixed16]). Must be positive. + SRC_Y: Fixed16 => drm::control::plane::Handle; + + /// Width for the source rectangle within the `drm_framebuffer`, in 16.16 fixed point ([Fixed16]). [SRC_X] plus [SRC_W] must be within the width of the source framebuffer. Must be positive. + SRC_W: Fixed16 => drm::control::plane::Handle; + + /// Height for the source rectangle within the `drm_framebuffer`, in 16.16 fixed point ([Fixed16]). [SRC_Y] plus [SRC_H] must be within the height of the source framebuffer. Must be positive. + SRC_H: Fixed16 => drm::control::plane::Handle; + + /// X coordinate offset for the destination rectangle. Can be negative. + CRTC_X: i32 => drm::control::plane::Handle; + + /// Y coordinate offset for the destination rectangle. Can be negative. + CRTC_Y: i32 => drm::control::plane::Handle; + + /// Width for the destination rectangle. [CRTC_X] plus [CRTC_W] can extend past the currently visible horizontal area of the `drm_crtc`. + CRTC_W: u32 => drm::control::plane::Handle; + + /// Height for the destination rectangle. [CRTC_Y] plus [CRTC_H] can extend past the currently visible vertical area of the `drm_crtc`. + CRTC_H: u32 => drm::control::plane::Handle; + + /// Mode object ID of the `drm_framebuffer` this plane should scan out. + /// + /// When a KMS client is performing front-buffer rendering, it should set [FB_ID] to the same front-buffer FB on each atomic commit. This implies to the driver that it needs to re-read the same FB again. Otherwise drivers which do not employ continuously repeated scanout cycles might not update the screen. + FB_ID: Option => drm::control::plane::Handle; + + /// Use this property to pass a fence that DRM should wait on before proceeding with the Atomic Commit request and show the framebuffer for the plane on the screen. + /// The fence can be either a normal fence or a merged one, the sync_file framework will handle both cases and use a fence_array if a merged fence is received. + /// Passing `-1` here means no fences to wait on. + /// + /// If the Atomic Commit request has the [drm::control::AtomicCommitFlags::TEST_ONLY] flag it will only check if the Sync File is a valid one. + IN_FENCE_FD: Option => drm::control::plane::Handle; + + /// Use this property to pass a file descriptor pointer to DRM. Once the Atomic Commit request call returns `OUT_FENCE_PTR` will be filled with the file descriptor number of a Sync File. + /// This Sync File contains the CRTC fence that will be signaled when all framebuffers present on the Atomic Commit * request for that given CRTC are scanned out on the screen. + /// + /// The Atomic Commit request fails if a invalid pointer is passed. If the Atomic Commit request fails for any other reason the out fence fd returned will be `-1`. + /// + /// On a Atomic Commit with the [drm::control::AtomicCommitFlags::TEST_ONLY] flag the out fence will also be set to `-1`. + OUT_FENCE_PTR: i32 => drm::control::crtc::Handle; + + /// CRTC that connector is attached to (atomic) + CRTC_ID: Option => drm::control::plane::Handle, drm::control::connector::Handle; + } +} + +pub use props::*; diff --git a/crates/compositor/src/backend/tty/format_modifier.rs b/crates/compositor/src/backend/tty/format_modifier.rs new file mode 100644 index 0000000..dcf0309 --- /dev/null +++ b/crates/compositor/src/backend/tty/format_modifier.rs @@ -0,0 +1,147 @@ +#![allow(non_camel_case_types)] + +use anyhow::Context; +use smithay::reexports::drm; + +#[derive(Debug)] +pub struct drm_format_modifier_blob { + pub version: u32, + pub flags: u32, + pub formats: Vec, + pub modifiers: Vec, +} + +type format = drm::buffer::DrmFourcc; + +#[repr(C)] +#[derive(Debug)] +pub struct drm_format_modifier { + pub formats: u64, + pub offset: u32, + _p: u32, + pub modifier: u64, +} + +#[repr(C)] +#[derive(Debug)] +struct drm_format_modifier_blob_header { + version: u32, + flags: u32, + count_formats: u32, + formats_offset: u32, + count_modifiers: u32, + modifiers_offset: u32, +} + +impl drm_format_modifier_blob { + pub fn read(blob: &[u8]) -> anyhow::Result { + // SAFETY: the header's contents are all integer types, so are valid for any bit pattern... + let drm_format_modifier_blob_header { + version, + flags, + count_formats, + formats_offset, + count_modifiers, + modifiers_offset, + } = unsafe { + extract_array( + blob, + 1, + 0, + "valid header not present in the blob provided by drm", + )? + .pop() + .unwrap() + }; + + // Check that this is v1 to not break under future versions + anyhow::ensure!( + version == 1, + "expected v1 of `drm_format_modifier_blob`, got {version}" + ); + + // SAFETY: u32-s are valid for any bit pattern. + let formats = unsafe { + extract_array( + blob, + count_formats, + formats_offset, + "formats slice out of bounds", + ) + }?; + + // SAFETY: drm_format_modifier-s are composed of only integer types, so this is okay... + let modifiers = unsafe { + extract_array( + blob, + count_modifiers, + modifiers_offset, + "modifiers slice out of bounds", + ) + }?; + + Ok(Self { + version, + flags, + formats, + modifiers, + }) + } + + pub fn modifiers_for(&self, fourcc: drm::buffer::DrmFourcc) -> Vec { + let Some(idx) = self.formats.iter().position(|&f| f == fourcc) else { + return Vec::new(); + }; + + let idx = idx as u64; + + self.modifiers + .iter() + .filter(|m| { + let offset = m.offset as u64; + + // Check if our format index is contained within this bitset. + let in_range = idx >= offset && idx < offset + 64; + + // Check if the corresponding bit for this format is set. + let is_enabled = (m.formats >> (idx - offset)) & 1 != 0; + + in_range && is_enabled + }) + .map(|m| m.modifier) + .collect() + } +} + +/// # Safety +/// `` must be valid for any bit pattern (e.g. a simple integer type). +unsafe fn extract_array( + blob: &[u8], + count: u32, + offset: u32, + msg: &'static str, +) -> Result, anyhow::Error> { + let src = blob + .get( + (offset as usize) + ..((offset as usize).checked_add( + size_of::() + .checked_mul(count as usize) + .context("overflow in pointer arithmetic")?, + )) + .context("overflow in pointer arithmetic")?, + ) + .context(msg)?; + + let mut dest = Vec::new(); + dest.reserve_exact(count as _); + unsafe { + // SAFETY: We have reserved exactly the correct amount of memory and checked that the + // source has enough bytes... + core::ptr::copy_nonoverlapping(src.as_ptr(), dest.as_mut_ptr() as *mut u8, src.len()); + + // SAFETY: We have set capacity to exactly `count` + dest.set_len(count as _); + } + Ok(dest) +} diff --git a/crates/compositor/src/backend/tty/mod.rs b/crates/compositor/src/backend/tty/mod.rs new file mode 100644 index 0000000..a792a61 --- /dev/null +++ b/crates/compositor/src/backend/tty/mod.rs @@ -0,0 +1,106 @@ +pub mod format_modifier; +pub mod utils; +pub mod vulkan; +pub mod atomic_req; + +use std::os::fd::AsFd; + +use smithay::{ + backend::drm::DrmNode, + reexports::drm::{self, Device, control::Device as _}, +}; + +#[derive(Debug)] +pub struct Card(std::fs::File, DrmNode); + +impl AsFd for Card { + fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl drm::Device for Card {} +impl drm::control::Device for Card {} + +const GRAPHICS_CARDS: &str = "/dev/dri/card*"; + +impl Card { + fn new(path: impl AsRef) -> anyhow::Result { + Ok(Self( + std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(path.as_ref())?, + DrmNode::from_path(path)?, + )) + } + + pub fn major(&self) -> u32 { + self.1.major() + } + + pub fn minor(&self) -> u32 { + self.1.minor() + } + + pub fn find_primary() -> anyhow::Result { + let overridden = match std::env::var("NADVA_DRM_DEVICE") { + Ok(path) => Some(std::path::PathBuf::from(path)), + Err(std::env::VarError::NotPresent) => None, + Err(std::env::VarError::NotUnicode(_)) => { + return Err(anyhow::anyhow!( + "Non-unicode string passed into `NADVA_DRM_DEVICE`" + )); + } + }; + + let is_overridden = overridden.is_some(); + + let iter = match overridden { + Some(overridden) => { + Box::new(std::iter::once(Ok(overridden))) as Box> + } + None => Box::new(glob::glob(GRAPHICS_CARDS).unwrap()), + }; + + let mut cards = iter + .filter_map(|res| res.map_err(anyhow::Error::from).and_then(Card::new).ok()) + .collect::>(); + + cards.retain(|card| { + match card.resource_handles() { + Ok(resource_handles) if !resource_handles.crtcs().is_empty() + // Try to set Atomic and UniversalPlanes (we need this for atomic modesetting, and separate cursor, overlay planes) + && let Ok(()) = card.set_client_capability(drm::ClientCapability::Atomic, true) + && let Ok(()) = card.set_client_capability(drm::ClientCapability::UniversalPlanes, true) + // Rule out GPUs without any displays connected to them for now. + && resource_handles.connectors().iter().any(|&con| card.get_connector(con, true).is_ok_and(|con| con.state() == drm::control::connector::State::Connected)) => true, + _ => false, + } + }); + + match cards.len() { + 0 if is_overridden => { + return Err(anyhow::anyhow!( + "device specified by NADVA_DRM_DEVICE is not usable" + )); + } + 0 => return Err(anyhow::anyhow!("No suitable graphics devices!")), + 1 => return Ok(cards.pop().unwrap()), + _ => (), + } + + // Tie-breaking by using the boot VGA device. + + let Some((i, _)) = cards.iter().enumerate().find(|(_, card)| { + let (major, minor) = (card.1.major(), card.1.minor()); + std::fs::read_to_string(format!("/sys/dev/char/{major}:{minor}/device/boot_vga")) + .is_ok_and(|s| s.trim() == "1") + }) else { + // Or just return the first device... + return Ok(cards.swap_remove(0)); + }; + + Ok(cards.swap_remove(i)) + } +} diff --git a/crates/compositor/src/backend/tty/utils.rs b/crates/compositor/src/backend/tty/utils.rs new file mode 100644 index 0000000..5aa97e6 --- /dev/null +++ b/crates/compositor/src/backend/tty/utils.rs @@ -0,0 +1,61 @@ +use std::ops::Deref; + +use smithay::reexports::{ash::vk, gbm}; + +pub struct Guard(Option<(T, F)>); + +impl Guard { + pub const fn new(value: T, cleanup: F) -> Self { + Self(Some((value, cleanup))) + } + + pub fn finish(mut self) -> T { + self.0.take().unwrap().0 + } +} + +impl Deref for Guard { + type Target = T; + + fn deref(&self) -> &Self::Target { + &self.0.as_ref().unwrap().0 + } +} + +impl Drop for Guard { + fn drop(&mut self) { + if let Some((t, f)) = self.0.take() { + f(t) + } + } +} + +#[derive(Debug, Clone, Copy)] +pub struct Format(pub gbm::Format); + +impl Format { + pub fn vk(&self) -> vk::Format { + use gbm::Format as G; + use vk::Format as F; + match self.0 { + G::Xrgb8888 => F::B8G8R8A8_UNORM, + G::Argb8888 => F::B8G8R8A8_UNORM, + G::Xbgr8888 => F::R8G8B8A8_UNORM, + G::Abgr8888 => F::R8G8B8A8_UNORM, + + // HDR + G::Xbgr2101010 => F::A2B10G10R10_UNORM_PACK32, + G::Abgr2101010 => F::A2B10G10R10_UNORM_PACK32, + + G::Rgb565 => F::R5G6B5_UNORM_PACK16, + + G::Abgr16161616f => F::R16G16B16A16_SFLOAT, + + _ => panic!("unsupported color format"), + } + } + + pub fn fourcc(&self) -> gbm::Format { + self.0 + } +} diff --git a/crates/compositor/src/backend/tty/vulkan.rs b/crates/compositor/src/backend/tty/vulkan.rs new file mode 100644 index 0000000..2333c8f --- /dev/null +++ b/crates/compositor/src/backend/tty/vulkan.rs @@ -0,0 +1,480 @@ +use std::{collections::HashSet, ffi::CStr, ops::Deref, os::fd::{FromRawFd, OwnedFd}, sync::Arc}; + +use smithay::reexports::{ash::{self, vk}, }; + +#[cfg(debug_assertions)] +const REQUIRED_INSTANCE_EXTENSIONS: &[&CStr] = &[ash::ext::debug_utils::NAME]; +#[cfg(debug_assertions)] +const REQUIRED_INSTANCE_LAYERS: &[&CStr] = &[c"VK_LAYER_KHRONOS_validation"]; + +const REQUIRED_DEVICE_EXTENSIONS: &[&CStr] = &[ + vk::EXT_PHYSICAL_DEVICE_DRM_NAME, + ash::khr::external_memory_fd::NAME, + vk::EXT_EXTERNAL_MEMORY_DMA_BUF_NAME, + vk::EXT_IMAGE_DRM_FORMAT_MODIFIER_NAME, + ash::khr::external_fence_fd::NAME, + ash::khr::external_semaphore_fd::NAME, +]; + +fn supports<'a, E>( + supported: impl IntoIterator>, + required: &'a [&'a CStr], +) -> Result, Vec<&'a CStr>> { + let supported = supported + .into_iter() + .filter_map(Result::ok) + .collect::>(); + + let required = required.iter().copied().collect::>(); + + let remaining = required.difference(&supported).copied().collect::>(); + + if remaining.is_empty() { + return Ok(required.iter().copied().map(CStr::as_ptr).collect()); + } + + Err(remaining) +} + +const APP_NAME: &CStr = c"Nadva"; +const APP_VERSION: u32 = { + let Ok(major) = u32::from_str_radix(env!("CARGO_PKG_VERSION_MAJOR"), 10) else { + panic!("Major version not set!") + }; + let Ok(minor) = u32::from_str_radix(env!("CARGO_PKG_VERSION_MINOR"), 10) else { + panic!("Major version not set!") + }; + let Ok(patch) = u32::from_str_radix(env!("CARGO_PKG_VERSION_PATCH"), 10) else { + panic!("Major version not set!") + }; + + vk::make_api_version(0, major, minor, patch) +}; + +const PREFERRED_VULKAN_VERSION: u32 = vk::make_api_version(0, 1, 3, 0); +const MINIMUM_VULKAN_VERSION: u32 = vk::make_api_version(0, 1, 1, 0); + +pub struct Instance { + // We're putting this in here for this to be destroyed before instance. + #[cfg(debug_assertions)] + #[cfg_attr(debug_assertions, expect(unused))] + debug: DebugUtils, + + instance: RawInstance, + + // We're putting this in here for this to be dropped last. + #[expect(unused)] + entry: ash::Entry, +} + +impl Deref for Instance { + type Target = ash::Instance; + + fn deref(&self) -> &Self::Target { + &self.instance.0 + } +} + +struct RawInstance(ash::Instance); + +impl Drop for RawInstance { + fn drop(&mut self) { + unsafe { + self.0.destroy_instance(None); + } + } +} + +struct DebugUtils { + instance: ash::ext::debug_utils::Instance, + messenger: ash::vk::DebugUtilsMessengerEXT, +} + +impl Drop for DebugUtils { + fn drop(&mut self) { + unsafe { + self.instance + .destroy_debug_utils_messenger(self.messenger, None); + } + } +} + +impl Instance { + pub fn load() -> anyhow::Result { + // SAFETY: We will not use functions on this instance after destroying it. + let entry = unsafe { ash::Entry::load()? }; + + let (raw_version, version) = unsafe { + entry + .try_enumerate_instance_version()? + .map(|v| { + ( + v, + ( + vk::api_version_major(v), + vk::api_version_minor(v), + vk::api_version_patch(v), + vk::api_version_variant(v), + ), + ) + }) + .unwrap_or((0, (1, 0, 0, 0))) + }; + + log::trace!("Vulkan Version: {version:?}"); + + if raw_version < MINIMUM_VULKAN_VERSION { + return Err(anyhow::anyhow!( + "your driver's Vulkan version ({}.{}.{}) is below the 1.1.x minimum", + version.0, + version.1, + version.2 + )); + } + + #[cfg(debug_assertions)] + let (layers, extensions, mut debug_create_info) = { + let layers = { + let supported_layers = unsafe { entry.enumerate_instance_layer_properties()? }; + supports( + supported_layers.iter().map(|a| a.layer_name_as_c_str()), + REQUIRED_INSTANCE_LAYERS, + ) + .map_err(|_| { + anyhow::anyhow!( + "the VK_LAYER_KHRONOS_validation layer is required for debug builds" + ) + })? + }; + let extensions = { + let supported_extensions = + unsafe { entry.enumerate_instance_extension_properties(None)? }; + supports( + supported_extensions + .iter() + .map(|a| a.extension_name_as_c_str()), + REQUIRED_INSTANCE_EXTENSIONS, + ) + .map_err(|_| { + anyhow::anyhow!("your graphics device should support VK_EXT_debug_utils") + })? + }; + + let debug_create_info = ash::vk::DebugUtilsMessengerCreateInfoEXT::default() + .message_severity({ + use vk::DebugUtilsMessageSeverityFlagsEXT as Flags; + Flags::ERROR | Flags::WARNING | Flags::INFO | Flags::VERBOSE + }) + .message_type({ + use vk::DebugUtilsMessageTypeFlagsEXT as Flags; + Flags::GENERAL | Flags::VALIDATION | Flags::PERFORMANCE + }) + .pfn_user_callback(Some(callback)); + + (layers, extensions, debug_create_info) + }; + + #[cfg(not(debug_assertions))] + let [layers, extensions]: [Vec<*const i8>; _] = const { [Vec::new(), Vec::new()] }; + + let app_info = vk::ApplicationInfo::default() + .application_name(APP_NAME) + .application_version(APP_VERSION) + .api_version(PREFERRED_VULKAN_VERSION); + + let create_info = vk::InstanceCreateInfo::default() + .application_info(&app_info) + .enabled_extension_names(&extensions) + .enabled_layer_names(&layers); + + #[cfg(debug_assertions)] + let create_info = create_info.push_next(&mut debug_create_info); + + let instance = unsafe { entry.create_instance(&create_info, None)? }; + + #[cfg(debug_assertions)] + let (debug_utils, debug_messenger) = { + let debug_utils = ash::ext::debug_utils::Instance::new(&entry, &instance); + let debug_messenger = + unsafe { debug_utils.create_debug_utils_messenger(&debug_create_info, None)? }; + + (debug_utils, debug_messenger) + }; + + Ok(Self { + #[cfg(debug_assertions)] + debug: DebugUtils { + instance: debug_utils, + messenger: debug_messenger, + }, + instance: RawInstance(instance), + entry, + }) + } + + pub fn device_for(self, card: &super::Card) -> anyhow::Result { + let instance = self; + let devices = unsafe { instance.enumerate_physical_devices()? }; + + let (api_version, physical_device) = devices + .into_iter() + .filter(|&device| { + let Ok(extensions) = + (unsafe { instance.enumerate_device_extension_properties(device) }) + else { + return false; + }; + + extensions.iter().any(|ext| { + ext.extension_name_as_c_str() + .is_ok_and(|ext| ext == vk::EXT_PHYSICAL_DEVICE_DRM_NAME) + }) + }) + .find_map(|device| { + let mut drm_props = vk::PhysicalDeviceDrmPropertiesEXT::default(); + let mut props = vk::PhysicalDeviceProperties2::default().push_next(&mut drm_props); + + // SAFETY: We checked that this physical device has the DRM extension. + unsafe { instance.get_physical_device_properties2(device, &mut props) }; + + let api_version = props.properties.api_version; + let found = drm_props.has_primary == vk::TRUE + && drm_props.primary_major == i64::from(card.major()) + && drm_props.primary_minor == i64::from(card.minor()); + + found.then_some((api_version, device)) + }) + .ok_or(anyhow::anyhow!("your graphics device does not support Vulkan, or you are using a multi-GPU setup which is not supported"))?; + + if api_version < PREFERRED_VULKAN_VERSION { + return Err(anyhow::anyhow!( + "graphics device only supports Vulkan {}.{}, but 1.3 is required", + vk::api_version_major(api_version), + vk::api_version_minor(api_version), + )); + } + + let queue_families = + unsafe { instance.get_physical_device_queue_family_properties(physical_device) }; + + let Some(graphics_family_idx) = queue_families + .iter() + .position(|queue| queue.queue_flags.contains(vk::QueueFlags::GRAPHICS)) + else { + return Err(anyhow::anyhow!( + "Cannot find GRAPHICS queue family on the (physical) graphics device." + )); + }; + + let exts = unsafe { instance.enumerate_device_extension_properties(physical_device)? }; + let exts = match supports( + exts.iter().map(|a| a.extension_name_as_c_str()), + REQUIRED_DEVICE_EXTENSIONS, + ) { + Ok(exts) => exts, + Err(missing) => { + return Err(anyhow::anyhow!( + "your graphics device is missing the following Vulkan device extensions: {missing:?}" + )); + } + }; + + // Check for the fancy Vulkan 1.2, 1.3 features. + { + let mut check12 = vk::PhysicalDeviceVulkan12Features::default(); + let mut check13 = vk::PhysicalDeviceVulkan13Features::default(); + let mut feats2 = vk::PhysicalDeviceFeatures2::default() + .push_next(&mut check12) + .push_next(&mut check13); + + unsafe { instance.get_physical_device_features2(physical_device, &mut feats2) }; + if check12.timeline_semaphore != vk::TRUE + || check13.synchronization2 != vk::TRUE + || check13.dynamic_rendering != vk::TRUE + { + return Err(anyhow::anyhow!( + "device lacks required Vulkan 1.2 and/or 1.3 features" + )); + } + } + + let features = vk::PhysicalDeviceFeatures::default(); + + let mut vk12 = vk::PhysicalDeviceVulkan12Features::default().timeline_semaphore(true); + + let mut vk13 = vk::PhysicalDeviceVulkan13Features::default() + .synchronization2(true) + .dynamic_rendering(true); + + let queue_info = vk::DeviceQueueCreateInfo::default() + .queue_family_index(graphics_family_idx as u32) + .queue_priorities(&[1.0]); + + let info = vk::DeviceCreateInfo::default() + .queue_create_infos(std::slice::from_ref(&queue_info)) + .enabled_extension_names(&exts) + .enabled_features(&features) + .push_next(&mut vk12) + .push_next(&mut vk13); + + let device = unsafe { instance.create_device(physical_device, &info, None)? }; + + let queue = unsafe { device.get_device_queue(graphics_family_idx as u32, 0) }; + + let external_semaphore_api = ash::khr::external_semaphore_fd::Device::new( + &instance, + &device + ); + + Ok(Device { + device: RawDevice(device), + physical: physical_device, + queue, + queue_family: graphics_family_idx as u32, + instance, + external_semaphore_api + }) + } +} + +pub struct Device { + device: RawDevice, + pub queue: vk::Queue, + pub physical: vk::PhysicalDevice, + pub queue_family: u32, + + pub instance: Instance, + + pub external_semaphore_api: ash::khr::external_semaphore_fd::Device, +} + +pub struct RawDevice(ash::Device); + +impl Drop for RawDevice { + fn drop(&mut self) { + unsafe { + let _ = self.0.device_wait_idle(); + self.0.destroy_device(None); + } + } +} + +impl Deref for Device { + type Target = ash::Device; + + fn deref(&self) -> &Self::Target { + &self.device.0 + } +} + +pub struct ExportableSemaphore { + device: Arc, + semaphore: vk::Semaphore, +} + +impl Drop for ExportableSemaphore { + fn drop(&mut self) { + // SAFETY: `self` is pinned till after dropped. + unsafe { + self.device.destroy_semaphore(self.semaphore, None); + } + } +} + +impl ExportableSemaphore { + pub fn new(device: Arc) -> ash::prelude::VkResult { + let semaphore = { + let mut export = vk::ExportSemaphoreCreateInfo::default() + .handle_types(vk::ExternalSemaphoreHandleTypeFlags::SYNC_FD); + let info = vk::SemaphoreCreateInfo::default().push_next(&mut export); + + (unsafe { device.create_semaphore(&info, None) })? + }; + + + + Ok(Self { semaphore, device }) + } + + pub fn as_slice(&self) -> &[vk::Semaphore] { + core::slice::from_ref(&self.semaphore) + } + + pub fn sync_fd(&self) -> ash::prelude::VkResult> { + let sync_fd = { + let info = vk::SemaphoreGetFdInfoKHR::default() + .semaphore(self.semaphore) + .handle_type(vk::ExternalSemaphoreHandleTypeFlags::SYNC_FD); + + unsafe { + self.device.external_semaphore_api.get_semaphore_fd(&info)? + } + }; + + // Okay, the Vulkan spec is a bit unclear about what to do here... + // See https://github.com/KhronosGroup/Vulkan-Docs/issues/2452 + // + // So, we'll just do another [Option] just in case some drivers do whatever. + if sync_fd == -1 { + return Ok(None); + } + + unsafe { + // SAFETY: Vulkan transfers ownership of this file descriptor to us, + // and we have checked that this is indeed a valid file descriptor. + Ok(Some(OwnedFd::from_raw_fd(sync_fd))) + } + } +} + + +#[cfg(debug_assertions)] +unsafe extern "system" fn callback( + message_severity: vk::DebugUtilsMessageSeverityFlagsEXT, + message_types: vk::DebugUtilsMessageTypeFlagsEXT, + p_callback_data: *const vk::DebugUtilsMessengerCallbackDataEXT<'_>, + _: *mut core::ffi::c_void, +) -> u32 { + use std::borrow::Cow; + use vk::DebugUtilsMessageSeverityFlagsEXT as Severity; + use vk::DebugUtilsMessageTypeFlagsEXT as Type; + + let Some(callback_data) = (unsafe { p_callback_data.as_ref() }) else { + return vk::FALSE; + }; + + // SAFETY: Spec guarantees this to be a valid (non-null) UTF-8 string (null terminated). + let message = unsafe { CStr::from_ptr(callback_data.p_message).to_string_lossy() }; + + let message_id = if callback_data.p_message_id_name.is_null() { + Cow::Borrowed("") + } else { + unsafe { CStr::from_ptr(callback_data.p_message_id_name).to_string_lossy() } + }; + + let mut kind = String::new(); + if message_types.is_empty() { + kind.push_str(" UNKNOWN"); + } + if message_types.contains(Type::GENERAL) { + kind.push_str(" GENERAL"); + } + if message_types.contains(Type::PERFORMANCE) { + kind.push_str(" PERFORMANCE"); + } + if message_types.contains(Type::VALIDATION) { + kind.push_str(" VALIDATION"); + } + + let kind = &kind[1..]; + + match message_severity { + Severity::ERROR => log::error!("[{kind}] [{message_id}]: {message}"), + Severity::WARNING => log::warn!("[{kind}] [{message_id}]: {message}"), + Severity::INFO => log::info!("[{kind}] [{message_id}]: {message}"), + Severity::VERBOSE => log::trace!("[{kind}] [{message_id}]: {message}"), + _ => log::debug!("[{kind}] [{message_id}]: {message}"), + } + + vk::FALSE +} diff --git a/crates/compositor/src/colors.rs b/crates/compositor/src/colors.rs new file mode 100644 index 0000000..b89fe1d --- /dev/null +++ b/crates/compositor/src/colors.rs @@ -0,0 +1,1002 @@ +pub const COLORS: [[f32; 4]; 1000] = [ + [0.176, 0.761, 0.494, 1.0], + [0.176, 0.761, 0.494, 1.0], + [0.176, 0.761, 0.494, 1.0], + [0.176, 0.761, 0.494, 1.0], + [0.176, 0.761, 0.494, 1.0], + [0.176, 0.761, 0.494, 1.0], + [0.176, 0.761, 0.498, 1.0], + [0.176, 0.757, 0.498, 1.0], + [0.176, 0.757, 0.498, 1.0], + [0.176, 0.757, 0.498, 1.0], + [0.176, 0.757, 0.498, 1.0], + [0.173, 0.757, 0.498, 1.0], + [0.173, 0.757, 0.498, 1.0], + [0.173, 0.757, 0.498, 1.0], + [0.173, 0.757, 0.498, 1.0], + [0.173, 0.757, 0.498, 1.0], + [0.173, 0.757, 0.498, 1.0], + [0.173, 0.757, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.173, 0.753, 0.502, 1.0], + [0.169, 0.753, 0.502, 1.0], + [0.169, 0.749, 0.502, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.749, 0.506, 1.0], + [0.169, 0.745, 0.506, 1.0], + [0.169, 0.745, 0.506, 1.0], + [0.169, 0.745, 0.51, 1.0], + [0.169, 0.745, 0.51, 1.0], + [0.165, 0.745, 0.51, 1.0], + [0.165, 0.745, 0.51, 1.0], + [0.165, 0.745, 0.51, 1.0], + [0.165, 0.745, 0.51, 1.0], + [0.165, 0.745, 0.51, 1.0], + [0.165, 0.745, 0.51, 1.0], + [0.165, 0.745, 0.51, 1.0], + [0.165, 0.741, 0.51, 1.0], + [0.165, 0.741, 0.51, 1.0], + [0.165, 0.741, 0.51, 1.0], + [0.165, 0.741, 0.514, 1.0], + [0.165, 0.741, 0.514, 1.0], + [0.165, 0.741, 0.514, 1.0], + [0.165, 0.741, 0.514, 1.0], + [0.165, 0.741, 0.514, 1.0], + [0.165, 0.741, 0.514, 1.0], + [0.161, 0.741, 0.514, 1.0], + [0.161, 0.741, 0.514, 1.0], + [0.161, 0.737, 0.514, 1.0], + [0.161, 0.737, 0.514, 1.0], + [0.161, 0.737, 0.514, 1.0], + [0.161, 0.737, 0.514, 1.0], + [0.161, 0.737, 0.518, 1.0], + [0.161, 0.737, 0.518, 1.0], + [0.161, 0.737, 0.518, 1.0], + [0.161, 0.737, 0.518, 1.0], + [0.161, 0.737, 0.518, 1.0], + [0.161, 0.737, 0.518, 1.0], + [0.161, 0.737, 0.518, 1.0], + [0.161, 0.733, 0.518, 1.0], + [0.161, 0.733, 0.518, 1.0], + [0.161, 0.733, 0.518, 1.0], + [0.157, 0.733, 0.518, 1.0], + [0.157, 0.733, 0.518, 1.0], + [0.157, 0.733, 0.522, 1.0], + [0.157, 0.733, 0.522, 1.0], + [0.157, 0.733, 0.522, 1.0], + [0.157, 0.733, 0.522, 1.0], + [0.157, 0.733, 0.522, 1.0], + [0.157, 0.733, 0.522, 1.0], + [0.157, 0.729, 0.522, 1.0], + [0.157, 0.729, 0.522, 1.0], + [0.157, 0.729, 0.522, 1.0], + [0.157, 0.729, 0.522, 1.0], + [0.157, 0.729, 0.522, 1.0], + [0.157, 0.729, 0.522, 1.0], + [0.157, 0.729, 0.522, 1.0], + [0.157, 0.729, 0.525, 1.0], + [0.153, 0.729, 0.525, 1.0], + [0.153, 0.729, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.525, 1.0], + [0.153, 0.725, 0.529, 1.0], + [0.153, 0.725, 0.529, 1.0], + [0.153, 0.722, 0.529, 1.0], + [0.153, 0.722, 0.529, 1.0], + [0.153, 0.722, 0.529, 1.0], + [0.149, 0.722, 0.529, 1.0], + [0.149, 0.722, 0.529, 1.0], + [0.149, 0.722, 0.529, 1.0], + [0.149, 0.722, 0.529, 1.0], + [0.149, 0.722, 0.529, 1.0], + [0.149, 0.722, 0.529, 1.0], + [0.149, 0.722, 0.529, 1.0], + [0.149, 0.722, 0.529, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.149, 0.718, 0.533, 1.0], + [0.145, 0.718, 0.533, 1.0], + [0.145, 0.718, 0.533, 1.0], + [0.145, 0.714, 0.533, 1.0], + [0.145, 0.714, 0.533, 1.0], + [0.145, 0.714, 0.533, 1.0], + [0.145, 0.714, 0.537, 1.0], + [0.145, 0.714, 0.537, 1.0], + [0.145, 0.714, 0.537, 1.0], + [0.145, 0.714, 0.537, 1.0], + [0.145, 0.714, 0.537, 1.0], + [0.145, 0.714, 0.537, 1.0], + [0.145, 0.714, 0.537, 1.0], + [0.145, 0.714, 0.537, 1.0], + [0.145, 0.71, 0.537, 1.0], + [0.145, 0.71, 0.537, 1.0], + [0.145, 0.71, 0.537, 1.0], + [0.145, 0.71, 0.537, 1.0], + [0.141, 0.71, 0.537, 1.0], + [0.141, 0.71, 0.541, 1.0], + [0.141, 0.71, 0.541, 1.0], + [0.141, 0.71, 0.541, 1.0], + [0.141, 0.71, 0.541, 1.0], + [0.141, 0.71, 0.541, 1.0], + [0.141, 0.706, 0.541, 1.0], + [0.141, 0.706, 0.541, 1.0], + [0.141, 0.706, 0.541, 1.0], + [0.141, 0.706, 0.541, 1.0], + [0.141, 0.706, 0.541, 1.0], + [0.141, 0.706, 0.541, 1.0], + [0.141, 0.706, 0.541, 1.0], + [0.141, 0.706, 0.541, 1.0], + [0.141, 0.706, 0.545, 1.0], + [0.141, 0.706, 0.545, 1.0], + [0.141, 0.706, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.702, 0.545, 1.0], + [0.137, 0.698, 0.549, 1.0], + [0.137, 0.698, 0.549, 1.0], + [0.137, 0.698, 0.549, 1.0], + [0.137, 0.698, 0.549, 1.0], + [0.137, 0.698, 0.549, 1.0], + [0.137, 0.698, 0.549, 1.0], + [0.133, 0.698, 0.549, 1.0], + [0.133, 0.698, 0.549, 1.0], + [0.133, 0.698, 0.549, 1.0], + [0.133, 0.698, 0.549, 1.0], + [0.133, 0.698, 0.549, 1.0], + [0.133, 0.694, 0.549, 1.0], + [0.133, 0.694, 0.549, 1.0], + [0.133, 0.694, 0.549, 1.0], + [0.133, 0.694, 0.553, 1.0], + [0.133, 0.694, 0.553, 1.0], + [0.133, 0.694, 0.553, 1.0], + [0.133, 0.694, 0.553, 1.0], + [0.133, 0.694, 0.553, 1.0], + [0.133, 0.694, 0.553, 1.0], + [0.133, 0.694, 0.553, 1.0], + [0.133, 0.69, 0.553, 1.0], + [0.133, 0.69, 0.553, 1.0], + [0.129, 0.69, 0.553, 1.0], + [0.129, 0.69, 0.553, 1.0], + [0.129, 0.69, 0.553, 1.0], + [0.129, 0.69, 0.553, 1.0], + [0.129, 0.69, 0.553, 1.0], + [0.129, 0.69, 0.557, 1.0], + [0.129, 0.69, 0.557, 1.0], + [0.129, 0.69, 0.557, 1.0], + [0.129, 0.69, 0.557, 1.0], + [0.129, 0.686, 0.557, 1.0], + [0.129, 0.686, 0.557, 1.0], + [0.129, 0.686, 0.557, 1.0], + [0.129, 0.686, 0.557, 1.0], + [0.129, 0.686, 0.557, 1.0], + [0.129, 0.686, 0.557, 1.0], + [0.129, 0.686, 0.557, 1.0], + [0.129, 0.686, 0.557, 1.0], + [0.125, 0.686, 0.557, 1.0], + [0.125, 0.686, 0.557, 1.0], + [0.125, 0.686, 0.557, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.682, 0.561, 1.0], + [0.125, 0.678, 0.561, 1.0], + [0.125, 0.678, 0.561, 1.0], + [0.125, 0.678, 0.561, 1.0], + [0.125, 0.678, 0.561, 1.0], + [0.122, 0.678, 0.565, 1.0], + [0.122, 0.678, 0.565, 1.0], + [0.122, 0.678, 0.565, 1.0], + [0.122, 0.678, 0.565, 1.0], + [0.122, 0.678, 0.565, 1.0], + [0.122, 0.678, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.565, 1.0], + [0.122, 0.675, 0.569, 1.0], + [0.122, 0.675, 0.569, 1.0], + [0.122, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.671, 0.569, 1.0], + [0.118, 0.667, 0.569, 1.0], + [0.118, 0.667, 0.569, 1.0], + [0.118, 0.667, 0.573, 1.0], + [0.118, 0.667, 0.573, 1.0], + [0.118, 0.667, 0.573, 1.0], + [0.118, 0.667, 0.573, 1.0], + [0.118, 0.667, 0.573, 1.0], + [0.118, 0.667, 0.573, 1.0], + [0.118, 0.667, 0.573, 1.0], + [0.114, 0.667, 0.573, 1.0], + [0.114, 0.663, 0.573, 1.0], + [0.114, 0.663, 0.573, 1.0], + [0.114, 0.663, 0.573, 1.0], + [0.114, 0.663, 0.573, 1.0], + [0.114, 0.663, 0.573, 1.0], + [0.114, 0.663, 0.573, 1.0], + [0.114, 0.663, 0.573, 1.0], + [0.114, 0.663, 0.576, 1.0], + [0.114, 0.663, 0.576, 1.0], + [0.114, 0.663, 0.576, 1.0], + [0.114, 0.663, 0.576, 1.0], + [0.114, 0.659, 0.576, 1.0], + [0.114, 0.659, 0.576, 1.0], + [0.114, 0.659, 0.576, 1.0], + [0.114, 0.659, 0.576, 1.0], + [0.114, 0.659, 0.576, 1.0], + [0.114, 0.659, 0.576, 1.0], + [0.114, 0.659, 0.576, 1.0], + [0.11, 0.659, 0.576, 1.0], + [0.11, 0.659, 0.576, 1.0], + [0.11, 0.659, 0.576, 1.0], + [0.11, 0.659, 0.576, 1.0], + [0.11, 0.655, 0.576, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.655, 0.58, 1.0], + [0.11, 0.651, 0.58, 1.0], + [0.11, 0.651, 0.58, 1.0], + [0.11, 0.651, 0.58, 1.0], + [0.11, 0.651, 0.58, 1.0], + [0.11, 0.651, 0.58, 1.0], + [0.106, 0.651, 0.58, 1.0], + [0.106, 0.651, 0.58, 1.0], + [0.106, 0.651, 0.584, 1.0], + [0.106, 0.651, 0.584, 1.0], + [0.106, 0.651, 0.584, 1.0], + [0.106, 0.651, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.647, 0.584, 1.0], + [0.106, 0.643, 0.584, 1.0], + [0.106, 0.643, 0.588, 1.0], + [0.106, 0.643, 0.588, 1.0], + [0.102, 0.643, 0.588, 1.0], + [0.102, 0.643, 0.588, 1.0], + [0.102, 0.643, 0.588, 1.0], + [0.102, 0.643, 0.588, 1.0], + [0.102, 0.643, 0.588, 1.0], + [0.102, 0.643, 0.588, 1.0], + [0.102, 0.643, 0.588, 1.0], + [0.102, 0.639, 0.588, 1.0], + [0.102, 0.639, 0.588, 1.0], + [0.102, 0.639, 0.588, 1.0], + [0.102, 0.639, 0.588, 1.0], + [0.102, 0.639, 0.588, 1.0], + [0.102, 0.639, 0.588, 1.0], + [0.102, 0.639, 0.588, 1.0], + [0.102, 0.639, 0.588, 1.0], + [0.102, 0.639, 0.592, 1.0], + [0.102, 0.639, 0.592, 1.0], + [0.102, 0.639, 0.592, 1.0], + [0.102, 0.635, 0.592, 1.0], + [0.102, 0.635, 0.592, 1.0], + [0.098, 0.635, 0.592, 1.0], + [0.098, 0.635, 0.592, 1.0], + [0.098, 0.635, 0.592, 1.0], + [0.098, 0.635, 0.592, 1.0], + [0.098, 0.635, 0.592, 1.0], + [0.098, 0.635, 0.592, 1.0], + [0.098, 0.635, 0.592, 1.0], + [0.098, 0.635, 0.592, 1.0], + [0.098, 0.631, 0.592, 1.0], + [0.098, 0.631, 0.592, 1.0], + [0.098, 0.631, 0.592, 1.0], + [0.098, 0.631, 0.592, 1.0], + [0.098, 0.631, 0.596, 1.0], + [0.098, 0.631, 0.596, 1.0], + [0.098, 0.631, 0.596, 1.0], + [0.098, 0.631, 0.596, 1.0], + [0.098, 0.631, 0.596, 1.0], + [0.098, 0.631, 0.596, 1.0], + [0.098, 0.631, 0.596, 1.0], + [0.098, 0.627, 0.596, 1.0], + [0.098, 0.627, 0.596, 1.0], + [0.098, 0.627, 0.596, 1.0], + [0.094, 0.627, 0.596, 1.0], + [0.094, 0.627, 0.596, 1.0], + [0.094, 0.627, 0.596, 1.0], + [0.094, 0.627, 0.596, 1.0], + [0.094, 0.627, 0.596, 1.0], + [0.094, 0.627, 0.596, 1.0], + [0.094, 0.627, 0.596, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.624, 0.6, 1.0], + [0.094, 0.62, 0.6, 1.0], + [0.094, 0.62, 0.6, 1.0], + [0.094, 0.62, 0.6, 1.0], + [0.094, 0.62, 0.6, 1.0], + [0.09, 0.62, 0.6, 1.0], + [0.09, 0.62, 0.6, 1.0], + [0.09, 0.62, 0.604, 1.0], + [0.09, 0.62, 0.604, 1.0], + [0.09, 0.62, 0.604, 1.0], + [0.09, 0.62, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.616, 0.604, 1.0], + [0.09, 0.612, 0.604, 1.0], + [0.09, 0.612, 0.604, 1.0], + [0.09, 0.612, 0.604, 1.0], + [0.09, 0.612, 0.608, 1.0], + [0.09, 0.612, 0.608, 1.0], + [0.09, 0.612, 0.608, 1.0], + [0.09, 0.612, 0.608, 1.0], + [0.086, 0.612, 0.608, 1.0], + [0.086, 0.612, 0.608, 1.0], + [0.086, 0.612, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.608, 0.608, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.604, 0.612, 1.0], + [0.086, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.612, 1.0], + [0.082, 0.6, 0.616, 1.0], + [0.082, 0.6, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.596, 0.616, 1.0], + [0.082, 0.592, 0.616, 1.0], + [0.082, 0.592, 0.616, 1.0], + [0.082, 0.592, 0.616, 1.0], + [0.082, 0.592, 0.616, 1.0], + [0.082, 0.592, 0.616, 1.0], + [0.082, 0.592, 0.616, 1.0], + [0.082, 0.592, 0.62, 1.0], + [0.078, 0.592, 0.62, 1.0], + [0.078, 0.592, 0.62, 1.0], + [0.078, 0.592, 0.62, 1.0], + [0.078, 0.592, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.588, 0.62, 1.0], + [0.078, 0.584, 0.62, 1.0], + [0.078, 0.584, 0.62, 1.0], + [0.078, 0.584, 0.62, 1.0], + [0.078, 0.584, 0.62, 1.0], + [0.078, 0.584, 0.624, 1.0], + [0.078, 0.584, 0.624, 1.0], + [0.078, 0.584, 0.624, 1.0], + [0.078, 0.584, 0.624, 1.0], + [0.078, 0.584, 0.624, 1.0], + [0.078, 0.584, 0.624, 1.0], + [0.078, 0.58, 0.624, 1.0], + [0.078, 0.58, 0.624, 1.0], + [0.078, 0.58, 0.624, 1.0], + [0.078, 0.58, 0.624, 1.0], + [0.078, 0.58, 0.624, 1.0], + [0.078, 0.58, 0.624, 1.0], + [0.078, 0.58, 0.624, 1.0], + [0.078, 0.58, 0.624, 1.0], + [0.075, 0.58, 0.624, 1.0], + [0.075, 0.58, 0.624, 1.0], + [0.075, 0.58, 0.624, 1.0], + [0.075, 0.576, 0.624, 1.0], + [0.075, 0.576, 0.624, 1.0], + [0.075, 0.576, 0.624, 1.0], + [0.075, 0.576, 0.627, 1.0], + [0.075, 0.576, 0.627, 1.0], + [0.075, 0.576, 0.627, 1.0], + [0.075, 0.576, 0.627, 1.0], + [0.075, 0.576, 0.627, 1.0], + [0.075, 0.576, 0.627, 1.0], + [0.075, 0.576, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.573, 0.627, 1.0], + [0.075, 0.569, 0.627, 1.0], + [0.075, 0.569, 0.627, 1.0], + [0.075, 0.569, 0.627, 1.0], + [0.075, 0.569, 0.631, 1.0], + [0.075, 0.569, 0.631, 1.0], + [0.075, 0.569, 0.631, 1.0], + [0.075, 0.569, 0.631, 1.0], + [0.075, 0.569, 0.631, 1.0], + [0.075, 0.569, 0.631, 1.0], + [0.075, 0.569, 0.631, 1.0], + [0.075, 0.569, 0.631, 1.0], + [0.075, 0.565, 0.631, 1.0], + [0.075, 0.565, 0.631, 1.0], + [0.075, 0.565, 0.631, 1.0], + [0.071, 0.565, 0.631, 1.0], + [0.071, 0.565, 0.631, 1.0], + [0.071, 0.565, 0.631, 1.0], + [0.071, 0.565, 0.631, 1.0], + [0.071, 0.565, 0.631, 1.0], + [0.071, 0.565, 0.631, 1.0], + [0.071, 0.565, 0.631, 1.0], + [0.071, 0.561, 0.631, 1.0], + [0.071, 0.561, 0.631, 1.0], + [0.071, 0.561, 0.635, 1.0], + [0.071, 0.561, 0.635, 1.0], + [0.071, 0.561, 0.635, 1.0], + [0.071, 0.561, 0.635, 1.0], + [0.071, 0.561, 0.635, 1.0], + [0.071, 0.561, 0.635, 1.0], + [0.071, 0.561, 0.635, 1.0], + [0.071, 0.561, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.557, 0.635, 1.0], + [0.071, 0.553, 0.635, 1.0], + [0.071, 0.553, 0.635, 1.0], + [0.071, 0.553, 0.639, 1.0], + [0.071, 0.553, 0.639, 1.0], + [0.071, 0.553, 0.639, 1.0], + [0.071, 0.553, 0.639, 1.0], + [0.071, 0.553, 0.639, 1.0], + [0.071, 0.553, 0.639, 1.0], + [0.071, 0.553, 0.639, 1.0], + [0.071, 0.553, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.549, 0.639, 1.0], + [0.071, 0.545, 0.639, 1.0], + [0.071, 0.545, 0.639, 1.0], + [0.071, 0.545, 0.643, 1.0], + [0.071, 0.545, 0.643, 1.0], + [0.071, 0.545, 0.643, 1.0], + [0.071, 0.545, 0.643, 1.0], + [0.067, 0.545, 0.643, 1.0], + [0.067, 0.545, 0.643, 1.0], + [0.067, 0.545, 0.643, 1.0], + [0.067, 0.545, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.541, 0.643, 1.0], + [0.067, 0.537, 0.643, 1.0], + [0.067, 0.537, 0.643, 1.0], + [0.067, 0.537, 0.643, 1.0], + [0.067, 0.537, 0.643, 1.0], + [0.067, 0.537, 0.647, 1.0], + [0.067, 0.537, 0.647, 1.0], + [0.067, 0.537, 0.647, 1.0], + [0.067, 0.537, 0.647, 1.0], + [0.067, 0.537, 0.647, 1.0], + [0.067, 0.537, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.533, 0.647, 1.0], + [0.067, 0.529, 0.647, 1.0], + [0.067, 0.529, 0.647, 1.0], + [0.067, 0.529, 0.647, 1.0], + [0.067, 0.529, 0.647, 1.0], + [0.067, 0.529, 0.647, 1.0], + [0.067, 0.529, 0.647, 1.0], + [0.067, 0.529, 0.651, 1.0], + [0.067, 0.529, 0.651, 1.0], + [0.067, 0.529, 0.651, 1.0], + [0.067, 0.529, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.525, 0.651, 1.0], + [0.067, 0.522, 0.651, 1.0], + [0.067, 0.522, 0.651, 1.0], + [0.067, 0.522, 0.651, 1.0], + [0.067, 0.522, 0.651, 1.0], + [0.067, 0.522, 0.651, 1.0], + [0.067, 0.522, 0.651, 1.0], + [0.067, 0.522, 0.651, 1.0], + [0.067, 0.522, 0.651, 1.0], + [0.067, 0.522, 0.655, 1.0], + [0.067, 0.522, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.518, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.514, 0.655, 1.0], + [0.067, 0.51, 0.655, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.51, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.506, 0.659, 1.0], + [0.067, 0.502, 0.659, 1.0], + [0.067, 0.502, 0.659, 1.0], + [0.067, 0.502, 0.659, 1.0], + [0.067, 0.502, 0.659, 1.0], + [0.067, 0.502, 0.663, 1.0], + [0.067, 0.502, 0.663, 1.0], + [0.067, 0.502, 0.663, 1.0], + [0.067, 0.502, 0.663, 1.0], + [0.067, 0.502, 0.663, 1.0], + [0.067, 0.502, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.498, 0.663, 1.0], + [0.067, 0.494, 0.663, 1.0], + [0.067, 0.494, 0.663, 1.0], + [0.067, 0.494, 0.663, 1.0], + [0.067, 0.494, 0.663, 1.0], + [0.067, 0.494, 0.663, 1.0], + [0.067, 0.494, 0.663, 1.0], + [0.067, 0.494, 0.663, 1.0], + [0.067, 0.494, 0.667, 1.0], + [0.067, 0.494, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.49, 0.667, 1.0], + [0.067, 0.486, 0.667, 1.0], + [0.067, 0.486, 0.667, 1.0], + [0.067, 0.486, 0.667, 1.0], + [0.067, 0.486, 0.667, 1.0], + [0.071, 0.486, 0.667, 1.0], + [0.071, 0.486, 0.667, 1.0], + [0.071, 0.486, 0.667, 1.0], + [0.071, 0.486, 0.667, 1.0], + [0.071, 0.486, 0.667, 1.0], + [0.071, 0.486, 0.667, 1.0], + [0.071, 0.482, 0.667, 1.0], + [0.071, 0.482, 0.667, 1.0], + [0.071, 0.482, 0.667, 1.0], + [0.071, 0.482, 0.671, 1.0], + [0.071, 0.482, 0.671, 1.0], + [0.071, 0.482, 0.671, 1.0], + [0.071, 0.482, 0.671, 1.0], + [0.071, 0.482, 0.671, 1.0], + [0.071, 0.482, 0.671, 1.0], + [0.071, 0.482, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.478, 0.671, 1.0], + [0.071, 0.475, 0.671, 1.0], + [0.071, 0.475, 0.671, 1.0], + [0.071, 0.475, 0.671, 1.0], + [0.071, 0.475, 0.671, 1.0], + [0.071, 0.475, 0.671, 1.0], + [0.071, 0.475, 0.671, 1.0], + [0.071, 0.475, 0.671, 1.0], + [0.071, 0.475, 0.671, 1.0], + [0.071, 0.475, 0.675, 1.0], + [0.071, 0.475, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.471, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.467, 0.675, 1.0], + [0.071, 0.463, 0.675, 1.0], + [0.071, 0.463, 0.675, 1.0], + [0.075, 0.463, 0.675, 1.0], + [0.075, 0.463, 0.675, 1.0], + [0.075, 0.463, 0.675, 1.0], + [0.075, 0.463, 0.678, 1.0], + [0.075, 0.463, 0.678, 1.0], + [0.075, 0.463, 0.678, 1.0], + [0.075, 0.463, 0.678, 1.0], + [0.075, 0.463, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.459, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.455, 0.678, 1.0], + [0.075, 0.451, 0.678, 1.0], + [0.075, 0.451, 0.682, 1.0], + [0.075, 0.451, 0.682, 1.0], + [0.075, 0.451, 0.682, 1.0], + [0.075, 0.451, 0.682, 1.0], + [0.075, 0.451, 0.682, 1.0], + [0.075, 0.451, 0.682, 1.0], + [0.075, 0.451, 0.682, 1.0], + [0.075, 0.451, 0.682, 1.0], + [0.075, 0.447, 0.682, 1.0], + [0.075, 0.447, 0.682, 1.0], + [0.075, 0.447, 0.682, 1.0], + [0.075, 0.447, 0.682, 1.0], + [0.075, 0.447, 0.682, 1.0], + [0.078, 0.447, 0.682, 1.0], + [0.078, 0.447, 0.682, 1.0], + [0.078, 0.447, 0.682, 1.0], + [0.078, 0.447, 0.682, 1.0], + [0.078, 0.447, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.443, 0.682, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.439, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.435, 0.686, 1.0], + [0.078, 0.431, 0.686, 1.0], + [0.082, 0.431, 0.686, 1.0], + [0.082, 0.431, 0.686, 1.0], + [0.082, 0.431, 0.686, 1.0], + [0.082, 0.431, 0.686, 1.0], + [0.082, 0.431, 0.686, 1.0], + [0.082, 0.431, 0.686, 1.0], + [0.082, 0.431, 0.686, 1.0], + [0.082, 0.431, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.427, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.424, 0.69, 1.0], + [0.082, 0.42, 0.69, 1.0], + [0.082, 0.42, 0.69, 1.0], + [0.082, 0.42, 0.69, 1.0], + [0.082, 0.42, 0.69, 1.0], + [0.082, 0.42, 0.69, 1.0], + [0.086, 0.42, 0.69, 1.0], + [0.086, 0.42, 0.69, 1.0], + [0.086, 0.42, 0.69, 1.0], + [0.086, 0.42, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.416, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.412, 0.694, 1.0], + [0.086, 0.408, 0.694, 1.0], + [0.086, 0.408, 0.694, 1.0], + [0.086, 0.408, 0.694, 1.0], + [0.086, 0.408, 0.694, 1.0], + [0.086, 0.408, 0.694, 1.0], + [0.086, 0.408, 0.694, 1.0], + [0.086, 0.408, 0.694, 1.0], + [0.09, 0.408, 0.694, 1.0], + [0.09, 0.408, 0.694, 1.0], + [0.09, 0.404, 0.694, 1.0], + [0.09, 0.404, 0.698, 1.0], + [0.09, 0.404, 0.698, 1.0], + [0.09, 0.404, 0.698, 1.0], + [0.09, 0.404, 0.698, 1.0], + [0.09, 0.404, 0.698, 1.0], + [0.09, 0.404, 0.698, 1.0], + [0.09, 0.404, 0.698, 1.0], + [0.09, 0.404, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.4, 0.698, 1.0], + [0.09, 0.396, 0.698, 1.0], + [0.09, 0.396, 0.698, 1.0], + [0.09, 0.396, 0.698, 1.0], + [0.09, 0.396, 0.698, 1.0], + [0.09, 0.396, 0.698, 1.0], + [0.09, 0.396, 0.698, 1.0], + [0.09, 0.396, 0.698, 1.0], + [0.094, 0.396, 0.698, 1.0], + [0.094, 0.396, 0.698, 1.0], + [0.094, 0.392, 0.698, 1.0], + [0.094, 0.392, 0.698, 1.0], + [0.094, 0.392, 0.698, 1.0], + [0.094, 0.392, 0.702, 1.0], + [0.094, 0.392, 0.702, 1.0], + [0.094, 0.392, 0.702, 1.0], + [0.094, 0.392, 0.702, 1.0], + [0.094, 0.392, 0.702, 1.0], + [0.094, 0.392, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.388, 0.702, 1.0], + [0.094, 0.384, 0.702, 1.0], + [0.094, 0.384, 0.702, 1.0], + [0.094, 0.384, 0.702, 1.0], + [0.094, 0.384, 0.702, 1.0], + [0.094, 0.384, 0.702, 1.0], + [0.094, 0.384, 0.702, 1.0], + [0.098, 0.384, 0.702, 1.0], + [0.098, 0.384, 0.702, 1.0], + [0.098, 0.38, 0.702, 1.0], + [0.098, 0.38, 0.702, 1.0], + [0.098, 0.38, 0.702, 1.0], + [0.098, 0.38, 0.702, 1.0], + [0.098, 0.38, 0.702, 1.0], + [0.098, 0.38, 0.702, 1.0], + [0.098, 0.38, 0.706, 1.0], + [0.098, 0.38, 0.706, 1.0], + [0.098, 0.38, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.376, 0.706, 1.0], + [0.098, 0.373, 0.706, 1.0], + [0.098, 0.373, 0.706, 1.0], + [0.098, 0.373, 0.706, 1.0], + [0.098, 0.373, 0.706, 1.0], +]; diff --git a/crates/compositor/src/config/mod.rs b/crates/compositor/src/config/mod.rs new file mode 100644 index 0000000..e69de29 diff --git a/crates/compositor/src/main.rs b/crates/compositor/src/main.rs new file mode 100644 index 0000000..d15fe5a --- /dev/null +++ b/crates/compositor/src/main.rs @@ -0,0 +1,767 @@ +pub mod backend; +pub mod colors; +pub mod config; + +use std::{collections::HashSet, os::fd::AsRawFd, sync::Arc}; + +use smithay::reexports::{ + ash::{self, vk}, + drm::{self, Device, buffer::Buffer, control::Device as _}, + gbm, +}; + +use crate::{ + backend::tty::{self, utils::Guard}, + colors::COLORS, +}; + +fn main() -> anyhow::Result<()> { + pretty_env_logger::init(); + + let card = tty::Card::find_primary()?; + + log::trace!("DRM Driver: {:?}", card.get_driver()); + let resources = card.resource_handles()?; + + let card = Arc::new(gbm::Device::new(card)?); + + let instance = tty::vulkan::Instance::load()?; + let device = Arc::new(instance.device_for(&card)?); + + // Pick a suitable CRTC and mode for the active connector. + let connector = resources + .connectors() + .iter() + .filter_map(|&con| card.get_connector(con, true).ok()) + // TODO: Make this slightly more configurable... + .find(|con| con.state() == drm::control::connector::State::Connected) + .ok_or(anyhow::anyhow!("Not connected to any display!"))?; + + let crtc = { + let compatible_crtcs = connector + .encoders() + .iter() + .filter_map(|&enc| card.get_encoder(enc).ok()) + .flat_map(|enc| resources.filter_crtcs(enc.possible_crtcs())) + .collect::>(); + + let crtc_before = connector + .current_encoder() + .and_then(|enc| card.get_encoder(enc).ok()) + .and_then(|encoder| encoder.crtc()); + compatible_crtcs + .iter() + .find(|&crtc| crtc_before.as_ref().is_some_and(|old| old == crtc)) + .or(compatible_crtcs.first()) + .copied() + .ok_or(anyhow::anyhow!("Cannot find a suitable CRTC!"))? + }; + + let mode = { + let modes = connector.modes(); + modes + .iter() + .find(|&mode| { + mode.mode_type() + .contains(drm::control::ModeTypeFlags::PREFERRED) + }) + .or(modes.first()) + .copied() + .ok_or(anyhow::anyhow!( + "Cannot find suitable mode for connector {}", + connector.interface().as_str() + ))? + }; + + let planes = card + .plane_handles()? + .iter() + .filter_map(|&p| card.get_plane(p).ok()) + .filter(|p| resources.filter_crtcs(p.possible_crtcs()).contains(&crtc)) + .collect::>(); + + let (plane, plane_props) = planes + .iter() + .filter_map(|p| card.get_properties(p.handle()).ok().map(|info| (p, info))) + .filter_map(|(p, info)| { + let ty = info.iter().find_map(|(k, v)| { + if let Ok(info) = card.get_property(*k) + && info.name() == c"type" + { + use drm::control::PlaneType as Type; + + const PRIMARY: u64 = Type::Primary as u64; + const CURSOR: u64 = Type::Cursor as u64; + const OVERLAY: u64 = Type::Overlay as u64; + return match *v { + PRIMARY => Some(Type::Primary), + CURSOR => Some(Type::Cursor), + OVERLAY => Some(Type::Overlay), + _ => None, + }; + } + + None + }); + + ty.map(move |ty| (p.handle(), ty, info)) + }) + .find_map(|(p, ty, props)| (ty == drm::control::PlaneType::Primary).then_some((p, props))) + .ok_or(anyhow::anyhow!("cannot find suitable primary plane"))?; + + let format = tty::utils::Format(gbm::Format::Xrgb8888); + + // Format modifiers... + let usable_modifiers = { + // DRM + let drm_modifiers = drm_modifiers(&card, format.fourcc(), &plane_props)?; + + // Vulkan + let vulkan_modifiers = vulkan_modifiers(&device, format.vk()); + + // Intersect the DRM- and Vulkan-provided format modifiers + let usable_modifiers = vulkan_modifiers + .intersection(&drm_modifiers) + .copied() + .collect::>(); + + if usable_modifiers.is_empty() { + log::warn!("no common modifier between DRM plane and Vulkan; falling back to linear"); + vec![u64::from(gbm::Modifier::Linear)] + } else { + usable_modifiers + } + }; + + let usable_modifiers = usable_modifiers + .iter() + .copied() + .map(gbm::Modifier::from) + .collect::>(); + + let buffers = { + let create_scan = || { + ScanoutBuffer::new( + device.clone(), + card.clone(), + format, + mode, + &usable_modifiers, + ) + }; + + [create_scan()?, create_scan()?] + }; + + anyhow::ensure!( + buffers[0].bo.modifier() == buffers[1].bo.modifier(), + "Both buffers should have the same format modifiers!" + ); + + let mut output = + BufferedOutput::new(card, device, crtc, plane, mode, connector.handle(), buffers)?; + + let start = std::time::Instant::now(); + loop { + if start.elapsed() > std::time::Duration::from_secs(14) { + break; + } + + output.render_and_flip()?; + } + Ok(()) +} + +pub struct BufferedOutput { + // DRM + card: Arc>, + cache: tty::atomic_req::AtomicRequestCache, + connector: drm::control::connector::Handle, + crtc: drm::control::crtc::Handle, + plane: drm::control::plane::Handle, + mode: drm::control::Mode, + + previous_state: drm::control::crtc::Info, + + // Vulkan + device: Arc, + render_semaphore: tty::vulkan::ExportableSemaphore, + render_fence: vk::Fence, + command_buffer: vk::CommandBuffer, + command_pool: vk::CommandPool, + + front: usize, + frame: usize, + buffers: [ScanoutBuffer; 2], +} + +impl Drop for BufferedOutput { + fn drop(&mut self) { + // Reset the state of the CRTC + { + let prev = &self.previous_state; + let _ = self.card.set_crtc( + self.crtc, + prev.framebuffer(), + prev.position(), + core::slice::from_ref(&self.connector), + prev.mode(), + ); + } + + unsafe { + let _ = self.device.device_wait_idle(); + self.device.destroy_fence(self.render_fence, None); + self.device + .free_command_buffers(self.command_pool, &[self.command_buffer]); + self.device.destroy_command_pool(self.command_pool, None); + } + } +} + +impl BufferedOutput { + pub fn new( + card: Arc>, + device: Arc, + crtc: drm::control::crtc::Handle, + plane: drm::control::plane::Handle, + mode: drm::control::Mode, + connector: drm::control::connector::Handle, + buffers: [ScanoutBuffer; 2], + ) -> anyhow::Result { + // Render =:render_sem:=> Scanout + // Create a Vulkan semaphore that we can export to DRM via a SYNC_FD. + let render_semaphore = tty::vulkan::ExportableSemaphore::new(device.clone())?; + + let command_pool = { + let info = vk::CommandPoolCreateInfo::default() + .queue_family_index(device.queue_family) + .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); + + unsafe { + Guard::new(device.create_command_pool(&info, None)?, |pool| { + device.destroy_command_pool(pool, None) + }) + } + }; + + let cmd = { + let info = vk::CommandBufferAllocateInfo::default() + .command_buffer_count(1) + .command_pool(*command_pool) + .level(vk::CommandBufferLevel::PRIMARY); + + unsafe { + let [cmd] = device.allocate_command_buffers(&info)?.try_into().unwrap(); + + Guard::new(cmd, |cmd| { + device.free_command_buffers(*command_pool, &[cmd]) + }) + } + }; + + // Clear the first buffer to avoid displaying garbage. + { + let clear_fence = unsafe { + Guard::new( + device.create_fence(&vk::FenceCreateInfo::default(), None)?, + |fence| { + let _ = device.wait_for_fences( + core::slice::from_ref(&fence), + true, + 1_000_000_000, + ); + device.destroy_fence(fence, None); + }, + ) + }; + + unsafe { + device.begin_command_buffer( + *cmd, + &vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )? + } + + let barrier = vk::ImageMemoryBarrier2::default() + .src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(vk::ImageLayout::GENERAL) + .image(buffers[0].image) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .level_count(1) + .layer_count(1), + ); + + // UNDEFINED -> GENERAL layout + unsafe { + device.cmd_pipeline_barrier2( + *cmd, + &vk::DependencyInfo::default() + .image_memory_barriers(core::slice::from_ref(&barrier)), + ) + }; + + // Fill with RED. + { + let attachment = vk::RenderingAttachmentInfo::default() + .image_layout(vk::ImageLayout::GENERAL) + .image_view(buffers[0].image_view) + // RED + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::STORE) + .clear_value(vk::ClearValue { + color: vk::ClearColorValue { + float32: [1.0, 0.0, 0.0, 1.0], + }, + }); + + let (width, height) = mode.size(); + let render = vk::RenderingInfo::default() + .render_area(vk::Rect2D { + offset: vk::Offset2D { x: 0, y: 0 }, + extent: vk::Extent2D { + width: width as _, + height: height as _, + }, + }) + .layer_count(1) + .color_attachments(core::slice::from_ref(&attachment)); + + unsafe { + device.cmd_begin_rendering(*cmd, &render); + } + unsafe { device.cmd_end_rendering(*cmd) }; + } + + unsafe { device.end_command_buffer(*cmd)? } + + unsafe { + device.queue_submit( + device.queue, + &[vk::SubmitInfo::default().command_buffers(core::slice::from_ref(&cmd))], + *clear_fence, + )? + }; + } + + let render_fence = unsafe { + device.create_fence( + &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), + None, + )? + }; + + let previous_state = card.get_crtc(crtc)?; + + let mut output = Self { + cache: tty::atomic_req::AtomicRequestCache::new(card.clone()), + front: 0, + frame: 0, + connector, + crtc, + plane, + mode, + previous_state, + buffers, + command_buffer: cmd.finish(), + command_pool: command_pool.finish(), + render_semaphore, + render_fence, + card, + device, + }; + + output.modeset()?; + + Ok(output) + } + + pub fn modeset(&mut self) -> anyhow::Result<()> { + use tty::atomic_req::*; + + let fb = self.buffers[self.front].fb; + let (w, h) = self.mode.size(); + + // This blocks to prevent a race condition with the cleanup of the `MODE_ID` blob. + // It's only done once anyway... + self.cache + .request() + .set(self.connector, CRTC_ID, Some(self.crtc))? + .set(self.crtc, ACTIVE, true)? + .set(self.crtc, MODE_ID, Some(&self.mode))? + .set(self.plane, CRTC_ID, Some(self.crtc))? + .set(self.plane, FB_ID, Some(fb))? + .set(self.plane, SRC_X, Fixed16::ZERO)? + .set(self.plane, SRC_Y, Fixed16::ZERO)? + .set(self.plane, SRC_W, Fixed16::integer(w))? + .set(self.plane, SRC_H, Fixed16::integer(h))? + .set(self.plane, CRTC_X, 0)? + .set(self.plane, CRTC_Y, 0)? + .set(self.plane, CRTC_W, w as u32)? + .set(self.plane, CRTC_H, h as u32)? + .commit(AtomicCommitFlags::ALLOW_MODESET)?; + + Ok(()) + } + + fn render_and_flip(&mut self) -> anyhow::Result<()> { + let back = 1 - self.front; + self.frame = (self.frame + 1) % COLORS.len(); + let color = COLORS[self.frame]; + + unsafe { + self.device + .wait_for_fences(&[self.render_fence], true, u64::MAX)?; + self.device.reset_fences(&[self.render_fence])?; + self.device.reset_command_buffer( + self.command_buffer, + vk::CommandBufferResetFlags::default(), + )?; + } + + { + unsafe { + self.device.begin_command_buffer( + self.command_buffer, + &vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )? + } + + let barrier = vk::ImageMemoryBarrier2::default() + .src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(vk::ImageLayout::GENERAL) + .image(self.buffers[back].image) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .level_count(1) + .layer_count(1), + ); + + // UNDEFINED -> GENERAL layout + unsafe { + self.device.cmd_pipeline_barrier2( + self.command_buffer, + &vk::DependencyInfo::default() + .image_memory_barriers(core::slice::from_ref(&barrier)), + ) + }; + + // Fill with some color. + { + let attachment = vk::RenderingAttachmentInfo::default() + .image_layout(vk::ImageLayout::GENERAL) + .image_view(self.buffers[back].image_view) + // RED + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::STORE) + .clear_value(vk::ClearValue { + color: vk::ClearColorValue { float32: color }, + }); + + let (width, height) = self.mode.size(); + let render = vk::RenderingInfo::default() + .render_area(vk::Rect2D { + offset: vk::Offset2D { x: 0, y: 0 }, + extent: vk::Extent2D { + width: width as _, + height: height as _, + }, + }) + .layer_count(1) + .color_attachments(core::slice::from_ref(&attachment)); + + unsafe { + self.device + .cmd_begin_rendering(self.command_buffer, &render); + } + unsafe { self.device.cmd_end_rendering(self.command_buffer) }; + } + + unsafe { self.device.end_command_buffer(self.command_buffer)? } + } + + unsafe { + self.device.queue_submit( + self.device.queue, + &[vk::SubmitInfo::default() + .command_buffers(&[self.command_buffer]) + .signal_semaphores(self.render_semaphore.as_slice())], + self.render_fence, + )?; + } + + { + use tty::atomic_req::*; + let sync_fd = self.render_semaphore.sync_fd()?; + + self.cache + .request() + .set(self.plane, FB_ID, Some(self.buffers[back].fb))? + .set(self.plane, IN_FENCE_FD, sync_fd)? + .commit(AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK)?; + } + + self.wait_for_flip()?; + + self.front = back; + + Ok(()) + } + + fn wait_for_flip(&self) -> std::io::Result<()> { + loop { + let mut events = match self.card.receive_events() { + Ok(events) => events, + Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue, + Err(err) => return Err(err), + }; + + if events.any(|ev| matches!(ev, drm::control::Event::PageFlip(_))) { + return Ok(()); + } + } + } +} + +pub struct ScanoutBuffer { + device: Arc, + card: Arc>, + memory: vk::DeviceMemory, + image: vk::Image, + image_view: vk::ImageView, + fb: drm::control::framebuffer::Handle, + bo: gbm::BufferObject<()>, +} + +impl Drop for ScanoutBuffer { + fn drop(&mut self) { + let _ = self.card.destroy_framebuffer(self.fb); + unsafe { + self.device.destroy_image_view(self.image_view, None); + self.device.destroy_image(self.image, None); + self.device.free_memory(self.memory, None); + } + } +} + +impl ScanoutBuffer { + pub fn new( + device: Arc, + card: Arc>, + format: tty::utils::Format, + mode: drm::control::Mode, + usable_modifiers: &[gbm::Modifier], + ) -> anyhow::Result { + // Create a BufferObject with + let (width, height) = mode.size(); + let bo = card.create_buffer_object_with_modifiers2::<()>( + width as _, + height as _, + format.fourcc(), + usable_modifiers.iter().copied(), + gbm::BufferObjectFlags::SCANOUT | gbm::BufferObjectFlags::RENDERING, + )?; + + // Create vk::Image + let image = { + let plane_layouts = (0..bo.plane_count()) + .map(|i| i as i32) + .map(|i| { + vk::SubresourceLayout::default() + .offset(bo.offset(i) as _) + .row_pitch(bo.stride_for_plane(i) as _) + }) + .collect::>(); + + let mut mod_info = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default() + .drm_format_modifier(bo.modifier().into()) + .plane_layouts(&plane_layouts); + + let mut ext_info = vk::ExternalMemoryImageCreateInfo::default() + .handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT); + + let (width, height) = bo.size(); + let info = vk::ImageCreateInfo::default() + .image_type(vk::ImageType::TYPE_2D) + .format(format.vk()) + .extent(vk::Extent3D { + width, + height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .initial_layout(vk::ImageLayout::UNDEFINED) + .push_next(&mut ext_info) + .push_next(&mut mod_info); + + let img = unsafe { device.create_image(&info, None)? }; + + Guard::new(img, |img| unsafe { device.destroy_image(img, None) }) + }; + + // Use the external memory via the fd provided by GBM + let (imported_fd, memory) = { + // (A) Create a DMA file descriptor (owned by Vulkan) + let fd = bo.fd()?; + let fd_props = { + let mut props = vk::MemoryFdPropertiesKHR::default(); + let ext_mem_device = + ash::khr::external_memory_fd::Device::new(&device.instance, device.as_ref()); + unsafe { + ext_mem_device.get_memory_fd_properties( + vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, + fd.as_raw_fd(), + &mut props, + )?; + }; + + props + }; + + let reqs = unsafe { device.get_image_memory_requirements(*image) }; + + // Get first mem_type compatible with Image and fd. + let compatible_mem_types = reqs.memory_type_bits & fd_props.memory_type_bits; + if compatible_mem_types == 0 { + anyhow::bail!("no supported DMA memory type can be imported into Vulkan") + } + let mem_type_index = compatible_mem_types.trailing_zeros(); + + // (A) Vulkan will take ownership over our fd. + let mut import = vk::ImportMemoryFdInfoKHR::default() + .handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) + .fd(fd.as_raw_fd()); + + let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(*image); + + let info = vk::MemoryAllocateInfo::default() + .allocation_size(reqs.size) + .memory_type_index(mem_type_index) + .push_next(&mut import) + .push_next(&mut dedicated); + + let memory = unsafe { device.allocate_memory(&info, None)? }; + + ( + fd, + Guard::new(memory, |memory| unsafe { device.free_memory(memory, None) }), + ) + }; + + unsafe { device.bind_image_memory(*image, *memory, 0)? }; + + // (A) Okay, now everything is good, we can forget the file descriptor here, since Vulkan will close it. + std::mem::forget(imported_fd); + + let image_view = { + let info = vk::ImageViewCreateInfo::default() + .components(vk::ComponentMapping::default()) + .format(format.vk()) + .image(*image) + .view_type(vk::ImageViewType::TYPE_2D) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_array_layer(0) + .layer_count(1) + .base_mip_level(0) + .level_count(1), + ); + + let image_view = unsafe { device.create_image_view(&info, None)? }; + + Guard::new(image_view, |view| unsafe { + device.destroy_image_view(view, None) + }) + }; + + let framebuffer = card.add_planar_framebuffer(&bo, drm::control::FbCmd2Flags::MODIFIERS)?; + + Ok(ScanoutBuffer { + memory: memory.finish(), + image: image.finish(), + image_view: image_view.finish(), + fb: framebuffer, + bo, + device, + card, + }) + } +} +fn drm_modifiers( + card: &gbm::Device, + fourcc: gbm::Format, + plane_props: &drm::control::PropertyValueSet, +) -> Result, anyhow::Error> { + let in_formats = plane_props + .iter() + .find_map(|(k, v)| (card.get_property(*k).ok()?.name() == c"IN_FORMATS").then_some(*v)) + .and_then(|blob| card.get_property_blob(blob).ok()) + .ok_or(anyhow::anyhow!("cannot get supported formats"))?; + let in_formats = tty::format_modifier::drm_format_modifier_blob::read(&in_formats)?; + Ok(HashSet::::from_iter( + in_formats + .modifiers_for(fourcc) + .into_iter() + .filter(|&m| m != u64::from(gbm::Modifier::Invalid)), + )) +} + +fn vulkan_modifiers(device: &tty::vulkan::Device, format: vk::Format) -> HashSet { + // Get the number of available format modifiers. + let format_modifier_len = { + let mut modifiers = vk::DrmFormatModifierPropertiesListEXT::default(); + let mut fmt_props2 = vk::FormatProperties2::default().push_next(&mut modifiers); + unsafe { + device.instance.get_physical_device_format_properties2( + device.physical, + format, + &mut fmt_props2, + ) + }; + + modifiers.drm_format_modifier_count as usize + }; + // Then, load all the format modifiers into a list. + let modifier_properties = { + let mut mod_props = + vec![vk::DrmFormatModifierPropertiesEXT::default(); format_modifier_len]; + let mut modifier_list = vk::DrmFormatModifierPropertiesListEXT::default() + .drm_format_modifier_properties(&mut mod_props); + let mut fmt_props2 = vk::FormatProperties2::default().push_next(&mut modifier_list); + unsafe { + device.instance.get_physical_device_format_properties2( + device.physical, + format, + &mut fmt_props2, + ) + }; + + mod_props + }; + // Only select the modifiers for formats with COLOR + modifier_properties + .into_iter() + .filter(|a| { + a.drm_format_modifier_tiling_features + .contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT) + }) + .map(|a| a.drm_format_modifier) + .collect::>() +} diff --git a/src/main.rs b/src/main.rs deleted file mode 100644 index f328e4d..0000000 --- a/src/main.rs +++ /dev/null @@ -1 +0,0 @@ -fn main() {} From bc64d35e284432081556c41572c5cc0de1e5353c Mon Sep 17 00:00:00 2001 From: Sammy99jsp Date: Tue, 28 Jul 2026 23:28:40 +0100 Subject: [PATCH 3/8] Implement Triple-Buffered Rendering + Update Skia --- Cargo.lock | 254 ++++++++++- crates/compositor/Cargo.toml | 3 + .../compositor/src/backend/tty/atomic_req.rs | 10 +- crates/compositor/src/backend/tty/mod.rs | 85 +++- crates/compositor/src/backend/tty/skia.rs | 46 ++ crates/compositor/src/backend/tty/utils.rs | 21 + crates/compositor/src/backend/tty/vulkan.rs | 109 ++++- crates/compositor/src/main.rs | 400 +++++++++++++----- 8 files changed, 808 insertions(+), 120 deletions(-) create mode 100644 crates/compositor/src/backend/tty/skia.rs diff --git a/Cargo.lock b/Cargo.lock index 32d332b..56751d9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,6 +2,12 @@ # It is not intended for manual editing. version = 4 +[[package]] +name = "adler2" +version = "2.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa" + [[package]] name = "ahash" version = "0.8.12" @@ -102,10 +108,31 @@ dependencies = [ "glob", "log", "pretty_env_logger", + "skia-safe", "smithay", "thiserror 2.0.19", ] +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags", + "cexpr", + "clang-sys", + "itertools", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex 1.3.0", + "syn 2.0.119", +] + [[package]] name = "bitflags" version = "2.13.1" @@ -206,7 +233,16 @@ dependencies = [ "find-msvc-tools", "jobserver", "libc", - "shlex", + "shlex 2.0.1", +] + +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom", ] [[package]] @@ -221,6 +257,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f079e83a288787bcd14a6aea84cee5c87a67c5a3e660c30f557a3d24761b3527" +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "combine" version = "4.6.7" @@ -249,6 +296,15 @@ dependencies = [ "libc", ] +[[package]] +name = "crc32fast" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511" +dependencies = [ + "cfg-if", +] + [[package]] name = "crossbeam-utils" version = "0.8.22" @@ -352,6 +408,12 @@ dependencies = [ "linux-raw-sys 0.9.4", ] +[[package]] +name = "either" +version = "1.16.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e" + [[package]] name = "encoding_rs" version = "0.8.35" @@ -416,12 +478,32 @@ version = "2.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9f1f227452a390804cdb637b74a86990f2a7d7ba4b7d5693aac9b4dd6defd8d6" +[[package]] +name = "filetime" +version = "0.2.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c287a33c7f0a620c38e641e7f60827713987b3c0f26e8ddc9462cc69cf75759" +dependencies = [ + "cfg-if", + "libc", +] + [[package]] name = "find-msvc-tools" version = "0.1.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5baebc0774151f905a1a2cc41989300b1e6fbb29aff0ceffa1064fdd3088d582" +[[package]] +name = "flate2" +version = "1.1.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c" +dependencies = [ + "crc32fast", + "miniz_oxide", +] + [[package]] name = "futures-core" version = "0.3.32" @@ -540,6 +622,12 @@ version = "0.17.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a" +[[package]] +name = "heck" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -608,6 +696,21 @@ dependencies = [ "windows-sys 0.61.2", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + +[[package]] +name = "itoa" +version = "1.0.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8f42a60cbdf9a97f5d2305f08a87dc4e09308d1276d28c869c684d7777685682" + [[package]] name = "jni" version = "0.22.4" @@ -800,6 +903,22 @@ dependencies = [ "libc", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + +[[package]] +name = "miniz_oxide" +version = "0.8.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316" +dependencies = [ + "adler2", + "simd-adler32", +] + [[package]] name = "ndk" version = "0.9.0" @@ -830,6 +949,16 @@ dependencies = [ "jni-sys 0.3.1", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + [[package]] name = "num_enum" version = "0.7.6" @@ -1054,6 +1183,16 @@ dependencies = [ "log", ] +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.119", +] + [[package]] name = "proc-macro-crate" version = "3.5.0" @@ -1215,6 +1354,12 @@ dependencies = [ "rustix 1.1.4", ] +[[package]] +name = "rustc-hash" +version = "2.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6b1e7f9a428571be2dc5bc0505c13fb6bf936822b894ec87abf8a08a4e51742d" + [[package]] name = "rustc_version" version = "0.4.1" @@ -1313,6 +1458,28 @@ dependencies = [ "syn 2.0.119", ] +[[package]] +name = "serde_json" +version = "1.0.151" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c841b55ecdae098c80dcae9cf767f6f8a0c2cdb3416bbef72181df4d0fe73f14" +dependencies = [ + "itoa", + "memchr", + "serde", + "serde_core", + "zmij", +] + +[[package]] +name = "serde_spanned" +version = "1.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6662b5879511e06e8999a8a235d848113e942c9124f211511b16466ee2995f26" +dependencies = [ + "serde_core", +] + [[package]] name = "sha2" version = "0.10.9" @@ -1324,12 +1491,24 @@ dependencies = [ "digest", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "shlex" version = "2.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f8fadd59c855ef2080decdef8ff161eb6661b86933c9d82e5ba29dc602a55aba" +[[package]] +name = "simd-adler32" +version = "0.3.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a219298ac11a56ea9a6d2120044824d6f01aeb034955e7af7bc16858527deea" + [[package]] name = "simd_cesu8" version = "1.2.0" @@ -1346,6 +1525,31 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e3a9fe34e3e7a50316060351f37187a3f546bce95496156754b601a5fa71b76e" +[[package]] +name = "skia-bindings" +version = "0.100.0" +source = "git+https://github.com/rust-skia/rust-skia.git?rev=7307c835#7307c835f2eb292338fb5e4b45740a07e59dac68" +dependencies = [ + "bindgen", + "cc", + "flate2", + "heck", + "pkg-config", + "regex", + "serde_json", + "tar", + "toml", +] + +[[package]] +name = "skia-safe" +version = "0.100.0" +source = "git+https://github.com/rust-skia/rust-skia.git?rev=7307c835#7307c835f2eb292338fb5e4b45740a07e59dac68" +dependencies = [ + "bitflags", + "skia-bindings", +] + [[package]] name = "slab" version = "0.4.12" @@ -1469,6 +1673,17 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "tar" +version = "0.4.46" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f6221d9a6003c78398e3b239969f352578258df48c8eb051caadae0015bc840" +dependencies = [ + "filetime", + "libc", + "xattr", +] + [[package]] name = "tempfile" version = "3.27.0" @@ -1531,6 +1746,21 @@ dependencies = [ "syn 3.0.2", ] +[[package]] +name = "toml" +version = "1.1.3+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "53c96ecdfa941c8fc4fcaed14f99ada8ebed502eef533015095a07e3301d4c3c" +dependencies = [ + "indexmap", + "serde_core", + "serde_spanned", + "toml_datetime", + "toml_parser", + "toml_writer", + "winnow", +] + [[package]] name = "toml_datetime" version = "1.1.1+spec-1.1.0" @@ -1561,6 +1791,12 @@ dependencies = [ "winnow", ] +[[package]] +name = "toml_writer" +version = "1.1.2+spec-1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d56353a2a665ad0f41a421187180aab746c8c325620617ad883a99a1cbe66d2" + [[package]] name = "tracing" version = "0.1.44" @@ -2319,6 +2555,16 @@ version = "0.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ea6fc2961e4ef194dcbfe56bb845534d0dc8098940c7e5c012a258bfec6701bd" +[[package]] +name = "xattr" +version = "1.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32e45ad4206f6d2479085147f02bc2ef834ac85886624a23575ae137c8aa8156" +dependencies = [ + "libc", + "rustix 1.1.4", +] + [[package]] name = "xcursor" version = "0.3.10" @@ -2380,3 +2626,9 @@ dependencies = [ "quote", "syn 2.0.119", ] + +[[package]] +name = "zmij" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "29666d0abbfad1e3dc4dcf6144730dd3a3ab225bbbdac83319345b1b44ccfc1b" diff --git a/crates/compositor/Cargo.toml b/crates/compositor/Cargo.toml index a2bf51f..17a5d3e 100644 --- a/crates/compositor/Cargo.toml +++ b/crates/compositor/Cargo.toml @@ -9,5 +9,8 @@ anyhow = "1.0.103" glob = "0.3.3" log = "0.4.33" pretty_env_logger = "0.5.0" +skia-safe = { git = "https://github.com/rust-skia/rust-skia.git", rev = "7307c835", features = [ + "vulkan", +] } smithay = { git = "https://github.com/Smithay/smithay.git", rev = "812bd332" } thiserror = "2.0.19" diff --git a/crates/compositor/src/backend/tty/atomic_req.rs b/crates/compositor/src/backend/tty/atomic_req.rs index 90a8ecd..8b9f691 100644 --- a/crates/compositor/src/backend/tty/atomic_req.rs +++ b/crates/compositor/src/backend/tty/atomic_req.rs @@ -248,6 +248,12 @@ impl AtomicReqType for bool { } } +impl AtomicReqType for *mut T { + fn to_value(&self, _: &Card) -> std::io::Result> { + Ok(property::Value::UnsignedRange(self.addr() as u64)) + } +} + macro_rules! handle_impl { ($($type: path => $var: ident);* $(;)?) => { $( @@ -322,7 +328,7 @@ impl AtomicReqType for Option { #[allow(nonstandard_style)] pub mod props { - use std::os::fd::OwnedFd; + use std::os::fd::{OwnedFd, RawFd}; use crate::backend::tty::atomic_req::ToBlob; @@ -391,7 +397,7 @@ pub mod props { /// The Atomic Commit request fails if a invalid pointer is passed. If the Atomic Commit request fails for any other reason the out fence fd returned will be `-1`. /// /// On a Atomic Commit with the [drm::control::AtomicCommitFlags::TEST_ONLY] flag the out fence will also be set to `-1`. - OUT_FENCE_PTR: i32 => drm::control::crtc::Handle; + OUT_FENCE_PTR: *mut RawFd => drm::control::crtc::Handle; /// CRTC that connector is attached to (atomic) CRTC_ID: Option => drm::control::plane::Handle, drm::control::connector::Handle; diff --git a/crates/compositor/src/backend/tty/mod.rs b/crates/compositor/src/backend/tty/mod.rs index a792a61..a4a3f39 100644 --- a/crates/compositor/src/backend/tty/mod.rs +++ b/crates/compositor/src/backend/tty/mod.rs @@ -1,13 +1,17 @@ +pub mod atomic_req; pub mod format_modifier; +pub mod skia; pub mod utils; pub mod vulkan; -pub mod atomic_req; -use std::os::fd::AsFd; +use std::{ops::Deref, os::fd::AsFd, sync::Arc}; use smithay::{ backend::drm::DrmNode, - reexports::drm::{self, Device, control::Device as _}, + reexports::{ + calloop, + drm::{self, Device, control::Device as _}, + }, }; #[derive(Debug)] @@ -104,3 +108,78 @@ impl Card { Ok(cards.swap_remove(i)) } } + +pub struct DrmEventNotifier { + token: Option, + card: Arc, +} + +impl> DrmEventNotifier { + pub fn new(card: Arc) -> Self { + Self { token: None, card } + } +} + +impl> calloop::EventSource for DrmEventNotifier { + type Event = drm::control::Event; + type Metadata = (); + type Ret = (); + type Error = std::io::Error; + + fn process_events( + &mut self, + _: calloop::Readiness, + token: calloop::Token, + mut callback: F, + ) -> Result + where + F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, + { + if Some(token) != self.token { + return Ok(calloop::PostAction::Continue); + } + + log::trace!("Pooling for DRM events..."); + let events = self.card.receive_events()?; + events.for_each(|event| callback(event, &mut ())); + + Ok(calloop::PostAction::Continue) + } + + fn register( + &mut self, + poll: &mut calloop::Poll, + factory: &mut calloop::TokenFactory, + ) -> calloop::Result<()> { + self.token = Some(factory.token()); + + // Safety: the FD cannot be closed without removing the DrmDeviceNotifier from the event loop + unsafe { + poll.register( + self.card.as_fd(), + calloop::Interest::READ, + calloop::Mode::Level, + self.token.unwrap(), + ) + } + } + + fn reregister( + &mut self, + poll: &mut calloop::Poll, + factory: &mut calloop::TokenFactory, + ) -> calloop::Result<()> { + self.token = Some(factory.token()); + poll.reregister( + self.card.as_fd(), + calloop::Interest::READ, + calloop::Mode::Level, + self.token.unwrap(), + ) + } + + fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> { + self.token = None; + poll.unregister(self.card.as_fd()) + } +} diff --git a/crates/compositor/src/backend/tty/skia.rs b/crates/compositor/src/backend/tty/skia.rs new file mode 100644 index 0000000..429170b --- /dev/null +++ b/crates/compositor/src/backend/tty/skia.rs @@ -0,0 +1,46 @@ +use std::sync::Arc; + +use anyhow::Context; +use skia_safe; +use smithay::reexports::ash::{self, vk::Handle}; + +impl super::vulkan::Device { + pub fn skia_context(self: Arc) -> anyhow::Result { + let device = self; + let instance = device.instance.handle().as_raw() as _; + let physical_device = device.physical.as_raw() as _; + let queue = (device.queue.as_raw() as _, device.queue_family as usize); + let raw_device = device.handle().as_raw() as _; + + let get_proc = move |of: skia_safe::gpu::vk::GetProcOf| unsafe { + let instance = &device.instance; + let entry = &instance.entry; + match of { + skia_safe::gpu::vk::GetProcOf::Instance(inst, name) => { + entry.get_instance_proc_addr(ash::vk::Instance::from_raw(inst as _), name) + } + skia_safe::gpu::vk::GetProcOf::Device(dev, name) => { + instance.get_device_proc_addr(ash::vk::Device::from_raw(dev as _), name) + } + } + .map(|f| f as _) + .unwrap_or(std::ptr::null()) + }; + + // SAFETY: the Vulkan handles will outlive this backend context + let ctx = unsafe { + skia_safe::gpu::vk::BackendContext::new_builder( + instance, + physical_device, + raw_device, + queue, + &get_proc, + None, + ) + .build() + }; + + skia_safe::gpu::direct_contexts::make_vulkan(&ctx, None) + .context("could not create skia vulkan direct context") + } +} diff --git a/crates/compositor/src/backend/tty/utils.rs b/crates/compositor/src/backend/tty/utils.rs index 5aa97e6..6429e4d 100644 --- a/crates/compositor/src/backend/tty/utils.rs +++ b/crates/compositor/src/backend/tty/utils.rs @@ -58,4 +58,25 @@ impl Format { pub fn fourcc(&self) -> gbm::Format { self.0 } + + pub fn skia(&self) -> skia_safe::ColorType { + use gbm::Format as G; + use skia_safe::ColorType as S; + + match self.0 { + G::Xrgb8888 => S::RGB888x, + G::Argb8888 => S::RGBA8888, + G::Xbgr8888 => S::BGRA8888, + G::Abgr8888 => S::BGRA8888, + + // HDR + G::Xbgr2101010 => S::BGR101010x, + G::Abgr2101010 => S::BGRA1010102, + G::Abgr16161616f => S::RGBAF16, + + G::Rgb565 => S::RGB565, + + _ => panic!("unsupported color format"), + } + } } diff --git a/crates/compositor/src/backend/tty/vulkan.rs b/crates/compositor/src/backend/tty/vulkan.rs index 2333c8f..0b8c902 100644 --- a/crates/compositor/src/backend/tty/vulkan.rs +++ b/crates/compositor/src/backend/tty/vulkan.rs @@ -1,6 +1,6 @@ -use std::{collections::HashSet, ffi::CStr, ops::Deref, os::fd::{FromRawFd, OwnedFd}, sync::Arc}; +use std::{collections::HashSet, ffi::CStr, ops::Deref, os::fd::{AsRawFd, FromRawFd, OwnedFd}, sync::Arc}; -use smithay::reexports::{ash::{self, vk}, }; +use smithay::reexports::ash::{self, vk::{self, Handle}}; #[cfg(debug_assertions)] const REQUIRED_INSTANCE_EXTENSIONS: &[&CStr] = &[ash::ext::debug_utils::NAME]; @@ -63,8 +63,7 @@ pub struct Instance { instance: RawInstance, // We're putting this in here for this to be dropped last. - #[expect(unused)] - entry: ash::Entry, + pub entry: ash::Entry, } impl Deref for Instance { @@ -326,13 +325,19 @@ impl Instance { &device ); + let external_fence_api = ash::khr::external_fence_fd::Device::new( + &instance, + &device + ); + Ok(Device { device: RawDevice(device), physical: physical_device, queue, queue_family: graphics_family_idx as u32, instance, - external_semaphore_api + external_semaphore_api, + external_fence_api }) } } @@ -346,6 +351,7 @@ pub struct Device { pub instance: Instance, pub external_semaphore_api: ash::khr::external_semaphore_fd::Device, + pub external_fence_api: ash::khr::external_fence_fd::Device, } pub struct RawDevice(ash::Device); @@ -370,6 +376,7 @@ impl Deref for Device { pub struct ExportableSemaphore { device: Arc, semaphore: vk::Semaphore, + skia: skia_safe::gpu::ganesh::BackendSemaphore, } impl Drop for ExportableSemaphore { @@ -381,6 +388,14 @@ impl Drop for ExportableSemaphore { } } +impl Deref for ExportableSemaphore { + type Target = vk::Semaphore; + + fn deref(&self) -> &Self::Target { + &self.semaphore + } +} + impl ExportableSemaphore { pub fn new(device: Arc) -> ash::prelude::VkResult { let semaphore = { @@ -391,9 +406,14 @@ impl ExportableSemaphore { (unsafe { device.create_semaphore(&info, None) })? }; - + // SAFETY: This semaphore will outlive the GrBackendSemaphore. + let skia = unsafe { skia_safe::gpu::ganesh::vk::backend_semaphores::make_vk(semaphore.as_raw() as *mut _) }; + + Ok(Self { semaphore, device, skia }) + } - Ok(Self { semaphore, device }) + pub fn skia(&mut self) -> &mut [skia_safe::gpu::ganesh::BackendSemaphore] { + core::slice::from_mut(&mut self.skia) } pub fn as_slice(&self) -> &[vk::Semaphore] { @@ -427,6 +447,81 @@ impl ExportableSemaphore { } } +pub struct ImportableFence { + device: Arc, + fence: vk::Fence +} + +impl Drop for ImportableFence { + fn drop(&mut self) { + unsafe { self.device.destroy_fence(self.fence, None); } + } +} + + +impl Deref for ImportableFence { + type Target = vk::Fence; + + fn deref(&self) -> &Self::Target { + &self.fence + } +} + + +impl ImportableFence { + pub fn new(device: Arc) -> ash::prelude::VkResult { + let fence = { + let info = vk::FenceCreateInfo::default() + .flags(vk::FenceCreateFlags::SIGNALED); + + unsafe { device.create_fence(&info, None)? } + }; + + + Ok(Self { + device, + fence, + }) + } + + pub fn import(&mut self, fd: OwnedFd) -> ash::prelude::VkResult<()> { + let raw_fd = fd.as_raw_fd(); + let info = vk::ImportFenceFdInfoKHR::default() + .fence(self.fence) + .fd(raw_fd) + .handle_type(vk::ExternalFenceHandleTypeFlags::SYNC_FD) + .flags(vk::FenceImportFlags::TEMPORARY); + + unsafe { + // self.device.reset_fences(self.as_slice())?; + self.device.external_fence_api.import_fence_fd(&info)?; + } + + // Only transfer ownership of the fd to Vulkan if the call is successful. + std::mem::forget(fd); + + Ok(()) + } + + pub fn as_slice(&self) -> &[vk::Fence] { + core::slice::from_ref(&self.fence) + } +} + +pub struct CommandPool(Arc, vk::CommandPool); + +impl CommandPool { + pub fn new(device: Arc, command_pool: vk::CommandPool) -> Self { + Self(device, command_pool) + } +} + +impl Drop for CommandPool { + fn drop(&mut self) { + unsafe {self.0.destroy_command_pool(self.1, None); } + } +} + #[cfg(debug_assertions)] unsafe extern "system" fn callback( diff --git a/crates/compositor/src/main.rs b/crates/compositor/src/main.rs index d15fe5a..d8dd0b3 100644 --- a/crates/compositor/src/main.rs +++ b/crates/compositor/src/main.rs @@ -2,10 +2,20 @@ pub mod backend; pub mod colors; pub mod config; -use std::{collections::HashSet, os::fd::AsRawFd, sync::Arc}; +use std::{ + collections::{HashSet, VecDeque}, + os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd}, + sync::Arc, + task::Poll, + time::Duration, +}; + +// use skia_safe::prelude::NativeAccess; +use anyhow::Context as _; use smithay::reexports::{ ash::{self, vk}, + calloop, drm::{self, Device, buffer::Buffer, control::Device as _}, gbm, }; @@ -139,39 +149,78 @@ fn main() -> anyhow::Result<()> { .map(gbm::Modifier::from) .collect::>(); - let buffers = { - let create_scan = || { - ScanoutBuffer::new( - device.clone(), - card.clone(), - format, - mode, - &usable_modifiers, - ) - }; + // let mut skia = device.clone().skia_context()?; + + let mut output = BufferedOutput::new( + card.clone(), + device, + crtc, + plane, + mode, + connector.handle(), + format, + &usable_modifiers, + )?; + + let mut event_loop = calloop::EventLoop::try_new()?; + + event_loop.handle().insert_source( + tty::DrmEventNotifier::new(card.clone()), + |event, _, output: &mut BufferedOutput| { + if let drm::control::Event::PageFlip(page_flip_event) = event { + log::trace!( + "Flip event! {:?} @ {:?}", + page_flip_event.frame, + page_flip_event.duration + ); - [create_scan()?, create_scan()?] + if let Some(pending_req) = output.queue.pop_front() + && let Err(err) = output.flip(pending_req) + { + println!("Error during page flip: {err:?}"); + } + } + }, + )?; + + let signal = event_loop.get_signal(); + + event_loop + .handle() + .insert_source( + calloop::timer::Timer::from_duration(Duration::from_secs(10)), + move |_, _, _| { + signal.stop(); + calloop::timer::TimeoutAction::Drop + }, + ) + .map_err(|a| a.error)?; + + let Poll::Ready(req) = output.try_render()? else { + panic!("First buffer is still being scanned! Should not be the case!") }; + output.flip(req)?; - anyhow::ensure!( - buffers[0].bo.modifier() == buffers[1].bo.modifier(), - "Both buffers should have the same format modifiers!" - ); + let now = std::time::Instant::now(); + event_loop.run( + Some(Duration::from_millis(5)), + &mut output, + |output| match output.try_render().expect("No error within render loop") { + Poll::Ready(req) => output.queue.push_back(req), + Poll::Pending => (), + }, + )?; - let mut output = - BufferedOutput::new(card, device, crtc, plane, mode, connector.handle(), buffers)?; + let fps = output.frame as f64 / now.elapsed().as_secs_f64(); + println!("Average FPS: {fps:.3}"); - let start = std::time::Instant::now(); - loop { - if start.elapsed() > std::time::Duration::from_secs(14) { - break; - } - - output.render_and_flip()?; - } Ok(()) } +pub struct ScanoutRequest { + pub frame_i: usize, +} + pub struct BufferedOutput { // DRM card: Arc>, @@ -184,15 +233,11 @@ pub struct BufferedOutput { previous_state: drm::control::crtc::Info, // Vulkan - device: Arc, - render_semaphore: tty::vulkan::ExportableSemaphore, - render_fence: vk::Fence, - command_buffer: vk::CommandBuffer, - command_pool: vk::CommandPool, - - front: usize, frame: usize, - buffers: [ScanoutBuffer; 2], + buffers: [ScanoutBuffer; 3], + pool: tty::vulkan::CommandPool, + device: Arc, + queue: VecDeque, } impl Drop for BufferedOutput { @@ -211,10 +256,6 @@ impl Drop for BufferedOutput { unsafe { let _ = self.device.device_wait_idle(); - self.device.destroy_fence(self.render_fence, None); - self.device - .free_command_buffers(self.command_pool, &[self.command_buffer]); - self.device.destroy_command_pool(self.command_pool, None); } } } @@ -227,11 +268,11 @@ impl BufferedOutput { plane: drm::control::plane::Handle, mode: drm::control::Mode, connector: drm::control::connector::Handle, - buffers: [ScanoutBuffer; 2], + format: tty::utils::Format, + modifiers: &[gbm::Modifier], ) -> anyhow::Result { // Render =:render_sem:=> Scanout // Create a Vulkan semaphore that we can export to DRM via a SYNC_FD. - let render_semaphore = tty::vulkan::ExportableSemaphore::new(device.clone())?; let command_pool = { let info = vk::CommandPoolCreateInfo::default() @@ -245,21 +286,58 @@ impl BufferedOutput { } }; - let cmd = { + let (cmd, cmd_frames) = { let info = vk::CommandBufferAllocateInfo::default() .command_buffer_count(1) .command_pool(*command_pool) .level(vk::CommandBufferLevel::PRIMARY); - unsafe { + let cmd = unsafe { let [cmd] = device.allocate_command_buffers(&info)?.try_into().unwrap(); Guard::new(cmd, |cmd| { device.free_command_buffers(*command_pool, &[cmd]) }) - } + }; + + let info = vk::CommandBufferAllocateInfo::default() + .command_buffer_count(3) + .command_pool(*command_pool) + .level(vk::CommandBufferLevel::PRIMARY); + + let cmd_frames = unsafe { + let cmds @ [_, _, _] = device.allocate_command_buffers(&info)?.try_into().unwrap(); + + Guard::new(cmds, |cmds| { + device.free_command_buffers(*command_pool, cmds.as_slice()) + }) + }; + + (cmd, cmd_frames) }; + let buffers = { + let [cmd1, cmd2, cmd3] = cmd_frames.finish(); + let create_scan = |cmd| { + ScanoutBuffer::new( + device.clone(), + card.clone(), + format, + mode, + modifiers, + *command_pool, + cmd, // &mut skia, + ) + }; + + [create_scan(cmd1)?, create_scan(cmd2)?, create_scan(cmd3)?] + }; + + anyhow::ensure!( + buffers[0].bo.modifier() == buffers[1].bo.modifier(), + "Both buffers should have the same format modifiers!" + ); + // Clear the first buffer to avoid displaying garbage. { let clear_fence = unsafe { @@ -290,7 +368,7 @@ impl BufferedOutput { .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) .old_layout(vk::ImageLayout::UNDEFINED) .new_layout(vk::ImageLayout::GENERAL) - .image(buffers[0].image) + .image(buffers[2].image) .subresource_range( vk::ImageSubresourceRange::default() .aspect_mask(vk::ImageAspectFlags::COLOR) @@ -307,17 +385,17 @@ impl BufferedOutput { ) }; - // Fill with RED. + // Fill with BLACK. { let attachment = vk::RenderingAttachmentInfo::default() .image_layout(vk::ImageLayout::GENERAL) - .image_view(buffers[0].image_view) + .image_view(buffers[2].image_view) // RED .load_op(vk::AttachmentLoadOp::CLEAR) .store_op(vk::AttachmentStoreOp::STORE) .clear_value(vk::ClearValue { color: vk::ClearColorValue { - float32: [1.0, 0.0, 0.0, 1.0], + float32: [0.0, 0.0, 0.0, 1.0], }, }); @@ -350,18 +428,12 @@ impl BufferedOutput { }; } - let render_fence = unsafe { - device.create_fence( - &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), - None, - )? - }; - let previous_state = card.get_crtc(crtc)?; + drop(cmd); + let mut output = Self { cache: tty::atomic_req::AtomicRequestCache::new(card.clone()), - front: 0, frame: 0, connector, crtc, @@ -369,12 +441,10 @@ impl BufferedOutput { mode, previous_state, buffers, - command_buffer: cmd.finish(), - command_pool: command_pool.finish(), - render_semaphore, - render_fence, card, + pool: tty::vulkan::CommandPool::new(device.clone(), command_pool.finish()), device, + queue: VecDeque::with_capacity(3), }; output.modeset()?; @@ -385,7 +455,7 @@ impl BufferedOutput { pub fn modeset(&mut self) -> anyhow::Result<()> { use tty::atomic_req::*; - let fb = self.buffers[self.front].fb; + let fb = self.buffers[self.frame].fb; let (w, h) = self.mode.size(); // This blocks to prevent a race condition with the cleanup of the `MODE_ID` blob. @@ -410,25 +480,36 @@ impl BufferedOutput { Ok(()) } - fn render_and_flip(&mut self) -> anyhow::Result<()> { - let back = 1 - self.front; - self.frame = (self.frame + 1) % COLORS.len(); - let color = COLORS[self.frame]; + fn try_render(&mut self) -> anyhow::Result> { + let (frame_i, buffer, color) = { + let frame_i = self.frame % 3; + let color = COLORS[self.frame % COLORS.len()]; + (frame_i, &mut self.buffers[frame_i], color) + }; + + let fence_signal = unsafe { + self.device.get_fence_status(*buffer.fence)? + && self.device.get_fence_status(buffer.cmd_fence)? + }; + + if !fence_signal { + return Ok(Poll::Pending); + } + + log::trace!("Frame #{} in buffer #{}", self.frame, frame_i); + self.frame += 1; unsafe { self.device - .wait_for_fences(&[self.render_fence], true, u64::MAX)?; - self.device.reset_fences(&[self.render_fence])?; - self.device.reset_command_buffer( - self.command_buffer, - vk::CommandBufferResetFlags::default(), - )?; + .reset_fences(&[*buffer.fence, buffer.cmd_fence])?; + self.device + .reset_command_buffer(buffer.cmd, vk::CommandBufferResetFlags::default())?; } { unsafe { self.device.begin_command_buffer( - self.command_buffer, + buffer.cmd, &vk::CommandBufferBeginInfo::default() .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), )? @@ -440,7 +521,7 @@ impl BufferedOutput { .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) .old_layout(vk::ImageLayout::UNDEFINED) .new_layout(vk::ImageLayout::GENERAL) - .image(self.buffers[back].image) + .image(buffer.image) .subresource_range( vk::ImageSubresourceRange::default() .aspect_mask(vk::ImageAspectFlags::COLOR) @@ -451,7 +532,7 @@ impl BufferedOutput { // UNDEFINED -> GENERAL layout unsafe { self.device.cmd_pipeline_barrier2( - self.command_buffer, + buffer.cmd, &vk::DependencyInfo::default() .image_memory_barriers(core::slice::from_ref(&barrier)), ) @@ -461,7 +542,7 @@ impl BufferedOutput { { let attachment = vk::RenderingAttachmentInfo::default() .image_layout(vk::ImageLayout::GENERAL) - .image_view(self.buffers[back].image_view) + .image_view(buffer.image_view) // RED .load_op(vk::AttachmentLoadOp::CLEAR) .store_op(vk::AttachmentStoreOp::STORE) @@ -482,56 +563,101 @@ impl BufferedOutput { .color_attachments(core::slice::from_ref(&attachment)); unsafe { - self.device - .cmd_begin_rendering(self.command_buffer, &render); + self.device.cmd_begin_rendering(buffer.cmd, &render); } - unsafe { self.device.cmd_end_rendering(self.command_buffer) }; + unsafe { self.device.cmd_end_rendering(buffer.cmd) }; } - unsafe { self.device.end_command_buffer(self.command_buffer)? } + unsafe { self.device.end_command_buffer(buffer.cmd)? } } unsafe { self.device.queue_submit( self.device.queue, &[vk::SubmitInfo::default() - .command_buffers(&[self.command_buffer]) - .signal_semaphores(self.render_semaphore.as_slice())], - self.render_fence, + .command_buffers(&[buffer.cmd]) + .signal_semaphores(buffer.semaphore.as_slice())], + buffer.cmd_fence, )?; } - { - use tty::atomic_req::*; - let sync_fd = self.render_semaphore.sync_fd()?; - - self.cache - .request() - .set(self.plane, FB_ID, Some(self.buffers[back].fb))? - .set(self.plane, IN_FENCE_FD, sync_fd)? - .commit(AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK)?; - } + // let surface = &mut self.buffers[back].surface; + // let canvas = surface.canvas(); + // let paint = skia_safe::Paint::new(skia_safe::Color4f::from(skia_safe::Color::BLACK), None); + // canvas + // .clear(skia_safe::Color::WHITE) // + // .draw_text_align( + // self.frame.to_string(), + // ( 0.0, 100.0), + // &skia_safe::Font::default(), + // &paint, + // skia_safe::utils::text_utils::Align::Left, + // ); + + // { + // let semaphores = self.render_semaphore.skia(); + // let flush_info = { + // let mut info = skia_safe::gpu::FlushInfo::default(); + // unsafe { info.set_signal_semaphores(semaphores) }; + // info + // }; + + // let mut ctx = surface.direct_context().unwrap(); + // let submitted_semaphores = ctx.flush_surface_with_access( + // surface, + // skia_safe::surface::BackendSurfaceAccess::NoAccess, + // &flush_info, + // ); + // assert_eq!( + // submitted_semaphores, + // skia_safe::gpu::ganesh::SemaphoresSubmitted::Yes + // ); + + // let can_wait_on_semaphore = ctx.submit(Some(skia_safe::gpu::SyncCpu::No)); + // assert!(can_wait_on_semaphore); + // } + + Ok(Poll::Ready(ScanoutRequest { frame_i })) + } + + fn flip(&mut self, req: ScanoutRequest) -> anyhow::Result<()> { + use tty::atomic_req::*; + + let ScanoutRequest { frame_i } = req; + let buffer = &mut self.buffers[frame_i]; + let mut out_fd: RawFd = -1; - self.wait_for_flip()?; + self.cache + .request() + .set(self.plane, FB_ID, Some(buffer.fb))? + .set(self.plane, IN_FENCE_FD, buffer.semaphore.sync_fd()?)? + .set(self.crtc, OUT_FENCE_PTR, &raw mut out_fd)? + .commit(AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK) + .context(format!("Submitting page flip for page #{}", self.frame))?; + + if out_fd == -1 { + panic!("OUT_FENCE_PTR is invalid!"); + } - self.front = back; + let out_fd = unsafe { OwnedFd::from_raw_fd(out_fd) }; + buffer.fence.import(out_fd)?; Ok(()) } - fn wait_for_flip(&self) -> std::io::Result<()> { - loop { - let mut events = match self.card.receive_events() { - Ok(events) => events, - Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue, - Err(err) => return Err(err), - }; - - if events.any(|ev| matches!(ev, drm::control::Event::PageFlip(_))) { - return Ok(()); - } - } - } + // fn wait_for_flip(&self) -> std::io::Result<()> { + // loop { + // let mut events = match self.card.receive_events() { + // Ok(events) => events, + // Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue, + // Err(err) => return Err(err), + // }; + + // if events.any(|ev| matches!(ev, drm::control::Event::PageFlip(_))) { + // return Ok(()); + // } + // } + // } } pub struct ScanoutBuffer { @@ -542,12 +668,19 @@ pub struct ScanoutBuffer { image_view: vk::ImageView, fb: drm::control::framebuffer::Handle, bo: gbm::BufferObject<()>, + fence: tty::vulkan::ImportableFence, + semaphore: tty::vulkan::ExportableSemaphore, + pool: vk::CommandPool, + cmd: vk::CommandBuffer, // surface: skia_safe::Surface, + cmd_fence: vk::Fence, } impl Drop for ScanoutBuffer { fn drop(&mut self) { let _ = self.card.destroy_framebuffer(self.fb); unsafe { + self.device.destroy_fence(self.cmd_fence, None); + self.device.free_command_buffers(self.pool, &[self.cmd]); self.device.destroy_image_view(self.image_view, None); self.device.destroy_image(self.image, None); self.device.free_memory(self.memory, None); @@ -562,9 +695,16 @@ impl ScanoutBuffer { format: tty::utils::Format, mode: drm::control::Mode, usable_modifiers: &[gbm::Modifier], + pool: vk::CommandPool, + cmd: vk::CommandBuffer, // skia_context: &mut skia_safe::gpu::DirectContext, ) -> anyhow::Result { // Create a BufferObject with let (width, height) = mode.size(); + + let cmd = Guard::new(cmd, |cmd| { + unsafe { device.free_command_buffers(pool, &[cmd]) }; + }); + let bo = card.create_buffer_object_with_modifiers2::<()>( width as _, height as _, @@ -693,14 +833,60 @@ impl ScanoutBuffer { let framebuffer = card.add_planar_framebuffer(&bo, drm::control::FbCmd2Flags::MODIFIERS)?; + let cmd_fence = unsafe { + Guard::new( + device.create_fence( + &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), + None, + )?, + |fence| device.destroy_fence(fence, None), + ) + }; + // let surface = { + // let image_info = unsafe { + // skia_safe::gpu::vk::ImageInfo::new( + // image.as_raw() as _, + // Default::default(), + // skia_safe::gpu::vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT, + // skia_safe::gpu::vk::ImageLayout::UNDEFINED, + // std::mem::transmute::(format.vk()), + // 1, + // device.queue_family, + // None, + // None, + // None, + // ) + // }; + + // let render_target = skia_safe::gpu::backend_render_targets::make_vk( + // (width as i32, height as i32), + // &image_info, + // ); + + // skia_safe::gpu::surfaces::wrap_backend_render_target( + // skia_context, + // &render_target, + // skia_safe::gpu::SurfaceOrigin::TopLeft, + // format.skia(), + // None, + // None, + // ) + // .context("wrap surface")? + // }; + Ok(ScanoutBuffer { + fence: tty::vulkan::ImportableFence::new(device.clone())?, + semaphore: tty::vulkan::ExportableSemaphore::new(device.clone())?, memory: memory.finish(), image: image.finish(), image_view: image_view.finish(), fb: framebuffer, bo, - device, card, + pool, + cmd: cmd.finish(), + cmd_fence: cmd_fence.finish(), + device, }) } } From f780b1284864e0b8cb9587a2c47c9d334fd59d03 Mon Sep 17 00:00:00 2001 From: Sammy99jsp Date: Sat, 1 Aug 2026 02:31:20 +0100 Subject: [PATCH 4/8] Refactor into a more backend-agnostic API + clean-up. --- .../src/backend/tty/{ => drmx}/atomic_req.rs | 43 +- .../compositor/src/backend/tty/drmx/format.rs | 31 + .../backend/tty/{ => drmx}/format_modifier.rs | 51 +- crates/compositor/src/backend/tty/drmx/mod.rs | 185 ++++ crates/compositor/src/backend/tty/mod.rs | 448 +++++++--- crates/compositor/src/backend/tty/skia.rs | 46 - crates/compositor/src/backend/tty/utils.rs | 53 -- .../compositor/src/backend/tty/vulkan/mod.rs | 435 +++++++++ .../tty/{vulkan.rs => vulkan/utils.rs} | 85 +- crates/compositor/src/main.rs | 823 +----------------- 10 files changed, 1102 insertions(+), 1098 deletions(-) rename crates/compositor/src/backend/tty/{ => drmx}/atomic_req.rs (92%) create mode 100644 crates/compositor/src/backend/tty/drmx/format.rs rename crates/compositor/src/backend/tty/{ => drmx}/format_modifier.rs (73%) create mode 100644 crates/compositor/src/backend/tty/drmx/mod.rs delete mode 100644 crates/compositor/src/backend/tty/skia.rs create mode 100644 crates/compositor/src/backend/tty/vulkan/mod.rs rename crates/compositor/src/backend/tty/{vulkan.rs => vulkan/utils.rs} (87%) diff --git a/crates/compositor/src/backend/tty/atomic_req.rs b/crates/compositor/src/backend/tty/drmx/atomic_req.rs similarity index 92% rename from crates/compositor/src/backend/tty/atomic_req.rs rename to crates/compositor/src/backend/tty/drmx/atomic_req.rs index 8b9f691..3d8ef02 100644 --- a/crates/compositor/src/backend/tty/atomic_req.rs +++ b/crates/compositor/src/backend/tty/drmx/atomic_req.rs @@ -6,7 +6,6 @@ use std::{ sync::Arc, }; -use anyhow::Context; use smithay::reexports::{ drm::{ self, @@ -17,11 +16,11 @@ use smithay::reexports::{ pub use smithay::reexports::drm::control::AtomicCommitFlags; -use crate::backend::tty::Card; +use crate::backend::tty::drmx::Card; #[derive(Debug)] pub struct AtomicRequestCache { - card: Arc>, + card: Arc>, cache: HashMap>, } @@ -39,13 +38,28 @@ impl<'a> Drop for AtomicRequest<'a> { } } +#[derive(Debug, thiserror::Error)] +pub enum AtomicReqError { + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error("{0}")] + Other(String), +} + +impl From for AtomicReqError { + fn from(value: String) -> Self { + Self::Other(value) + } +} + impl<'a> AtomicRequest<'a> { pub fn set>( mut self, target: H, prop: P, value: P::Type, - ) -> anyhow::Result { + ) -> Result { let atomic_value = value.to_value(&self.cache.card)?; self.request.add_property( target, @@ -83,7 +97,7 @@ impl<'a> AtomicRequest<'a> { } impl AtomicRequestCache { - pub fn new(card: Arc>) -> Self { + pub fn new(card: Arc>) -> Self { Self { card, cache: Default::default(), @@ -94,12 +108,15 @@ impl AtomicRequestCache { &mut self, target: H, _: P, - ) -> anyhow::Result { + ) -> Result { let prop = if let Some(props) = self.cache.get(&target.into()) { if let Some(&prop) = props.get(P::NAME) { prop } else { - anyhow::bail!("Property does not exist on this resource!"); + return Err(AtomicReqError::Other(format!( + "{} Property does not exist on this resource!", + P::NAME + ))); } } else { let map = self @@ -110,10 +127,12 @@ impl AtomicRequestCache { .map(|(name, info)| (name, info.handle())) .collect::>(); - let prop = map - .get(P::NAME) - .copied() - .context("Property does not exist on this resource")?; + let prop = map.get(P::NAME).copied().ok_or_else(|| { + AtomicReqError::Other(format!( + "{} Property does not exist on this resource!", + P::NAME + )) + })?; self.cache.insert(target.into(), map); @@ -330,7 +349,7 @@ impl AtomicReqType for Option { pub mod props { use std::os::fd::{OwnedFd, RawFd}; - use crate::backend::tty::atomic_req::ToBlob; + use crate::backend::tty::drmx::atomic_req::ToBlob; use super::{Fixed16, Property}; use smithay::reexports::drm; diff --git a/crates/compositor/src/backend/tty/drmx/format.rs b/crates/compositor/src/backend/tty/drmx/format.rs new file mode 100644 index 0000000..11bdee0 --- /dev/null +++ b/crates/compositor/src/backend/tty/drmx/format.rs @@ -0,0 +1,31 @@ +use smithay::reexports::gbm; + +#[derive(Debug, Clone, Copy)] +pub struct Format(pub gbm::Format); + +impl Format { + pub fn fourcc(&self) -> gbm::Format { + self.0 + } + + pub fn skia(&self) -> skia_safe::ColorType { + use gbm::Format as G; + use skia_safe::ColorType as S; + + match self.0 { + G::Xrgb8888 => S::RGB888x, + G::Argb8888 => S::RGBA8888, + G::Xbgr8888 => S::BGRA8888, + G::Abgr8888 => S::BGRA8888, + + // HDR + G::Xbgr2101010 => S::BGR101010x, + G::Abgr2101010 => S::BGRA1010102, + G::Abgr16161616f => S::RGBAF16, + + G::Rgb565 => S::RGB565, + + _ => panic!("unsupported color format"), + } + } +} diff --git a/crates/compositor/src/backend/tty/format_modifier.rs b/crates/compositor/src/backend/tty/drmx/format_modifier.rs similarity index 73% rename from crates/compositor/src/backend/tty/format_modifier.rs rename to crates/compositor/src/backend/tty/drmx/format_modifier.rs index dcf0309..b666af8 100644 --- a/crates/compositor/src/backend/tty/format_modifier.rs +++ b/crates/compositor/src/backend/tty/drmx/format_modifier.rs @@ -1,6 +1,5 @@ -#![allow(non_camel_case_types)] +#![expect(non_camel_case_types)] -use anyhow::Context; use smithay::reexports::drm; #[derive(Debug)] @@ -33,8 +32,22 @@ struct drm_format_modifier_blob_header { modifiers_offset: u32, } +#[derive(Debug, thiserror::Error)] +pub enum DrmFormatParseError { + #[error("expected v1 of `drm_format_modifier_blob`, got {0}")] + InvalidVersion(u32), + #[error("Overflow occurred during pointer arithmetic")] + PointerOverflow, + #[error("valid header not present in the blob provided by drm")] + InvalidHeader, + #[error("formats slice out of bounds")] + FormatsOutOfBounds, + #[error("modifiers slice out of bounds")] + ModifiersOutOfBounds, +} + impl drm_format_modifier_blob { - pub fn read(blob: &[u8]) -> anyhow::Result { + pub fn read(blob: &[u8]) -> Result { // SAFETY: the header's contents are all integer types, so are valid for any bit pattern... let drm_format_modifier_blob_header { version, @@ -44,21 +57,15 @@ impl drm_format_modifier_blob { count_modifiers, modifiers_offset, } = unsafe { - extract_array( - blob, - 1, - 0, - "valid header not present in the blob provided by drm", - )? - .pop() - .unwrap() + extract_array(blob, 1, 0, DrmFormatParseError::InvalidHeader)? + .pop() + .unwrap() }; // Check that this is v1 to not break under future versions - anyhow::ensure!( - version == 1, - "expected v1 of `drm_format_modifier_blob`, got {version}" - ); + if version != 1 { + return Err(DrmFormatParseError::InvalidVersion(version)); + } // SAFETY: u32-s are valid for any bit pattern. let formats = unsafe { @@ -66,7 +73,7 @@ impl drm_format_modifier_blob { blob, count_formats, formats_offset, - "formats slice out of bounds", + DrmFormatParseError::FormatsOutOfBounds, ) }?; @@ -76,7 +83,7 @@ impl drm_format_modifier_blob { blob, count_modifiers, modifiers_offset, - "modifiers slice out of bounds", + DrmFormatParseError::ModifiersOutOfBounds, ) }?; @@ -119,19 +126,19 @@ unsafe fn extract_array( blob: &[u8], count: u32, offset: u32, - msg: &'static str, -) -> Result, anyhow::Error> { + err: DrmFormatParseError, +) -> Result, DrmFormatParseError> { let src = blob .get( (offset as usize) ..((offset as usize).checked_add( size_of::() .checked_mul(count as usize) - .context("overflow in pointer arithmetic")?, + .ok_or(DrmFormatParseError::PointerOverflow)?, )) - .context("overflow in pointer arithmetic")?, + .ok_or(DrmFormatParseError::PointerOverflow)?, ) - .context(msg)?; + .ok_or(err)?; let mut dest = Vec::new(); dest.reserve_exact(count as _); diff --git a/crates/compositor/src/backend/tty/drmx/mod.rs b/crates/compositor/src/backend/tty/drmx/mod.rs new file mode 100644 index 0000000..2436f9c --- /dev/null +++ b/crates/compositor/src/backend/tty/drmx/mod.rs @@ -0,0 +1,185 @@ +use std::{ops::Deref, os::fd::AsFd, sync::Arc}; + +use smithay::{ + backend::drm::DrmNode, + reexports::{ + calloop, + drm::{self, Device, control::Device as _}, + }, +}; + +pub mod atomic_req; +pub mod format; +pub mod format_modifier; + +pub use format::Format; + +#[derive(Debug)] +pub struct Card(std::fs::File, DrmNode); + +impl AsFd for Card { + fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> { + self.0.as_fd() + } +} + +impl drm::Device for Card {} +impl drm::control::Device for Card {} + +const GRAPHICS_CARDS: &str = "/dev/dri/card*"; + +impl Card { + fn new(path: impl AsRef) -> anyhow::Result { + Ok(Self( + std::fs::OpenOptions::new() + .read(true) + .write(true) + .open(path.as_ref())?, + DrmNode::from_path(path)?, + )) + } + + pub fn major(&self) -> u32 { + self.1.major() + } + + pub fn minor(&self) -> u32 { + self.1.minor() + } + + pub fn find_primary() -> anyhow::Result { + let overridden = match std::env::var("NADVA_DRM_DEVICE") { + Ok(path) => Some(std::path::PathBuf::from(path)), + Err(std::env::VarError::NotPresent) => None, + Err(std::env::VarError::NotUnicode(_)) => { + return Err(anyhow::anyhow!( + "Non-unicode string passed into `NADVA_DRM_DEVICE`" + )); + } + }; + + let is_overridden = overridden.is_some(); + + let iter = match overridden { + Some(overridden) => { + Box::new(std::iter::once(Ok(overridden))) as Box> + } + None => Box::new(glob::glob(GRAPHICS_CARDS).unwrap()), + }; + + let mut cards = iter + .filter_map(|res| res.map_err(anyhow::Error::from).and_then(Card::new).ok()) + .collect::>(); + + cards.retain(|card| { + match card.resource_handles() { + Ok(resource_handles) if !resource_handles.crtcs().is_empty() + // Try to set Atomic and UniversalPlanes (we need this for atomic modesetting, and separate cursor, overlay planes) + && let Ok(()) = card.set_client_capability(drm::ClientCapability::Atomic, true) + && let Ok(()) = card.set_client_capability(drm::ClientCapability::UniversalPlanes, true) + // Rule out GPUs without any displays connected to them for now. + && resource_handles.connectors().iter().any(|&con| card.get_connector(con, true).is_ok_and(|con| con.state() == drm::control::connector::State::Connected)) => true, + _ => false, + } + }); + + match cards.len() { + 0 if is_overridden => { + return Err(anyhow::anyhow!( + "device specified by NADVA_DRM_DEVICE is not usable" + )); + } + 0 => return Err(anyhow::anyhow!("No suitable graphics devices!")), + 1 => return Ok(cards.pop().unwrap()), + _ => (), + } + + // Tie-breaking by using the boot VGA device. + + let Some((i, _)) = cards.iter().enumerate().find(|(_, card)| { + let (major, minor) = (card.1.major(), card.1.minor()); + std::fs::read_to_string(format!("/sys/dev/char/{major}:{minor}/device/boot_vga")) + .is_ok_and(|s| s.trim() == "1") + }) else { + // Or just return the first device... + return Ok(cards.swap_remove(0)); + }; + + Ok(cards.swap_remove(i)) + } +} + +pub struct DrmEventNotifier { + token: Option, + card: Arc, +} + +impl> DrmEventNotifier { + pub fn new(card: Arc) -> Self { + Self { token: None, card } + } +} + +impl> calloop::EventSource for DrmEventNotifier { + type Event = drm::control::Event; + type Metadata = (); + type Ret = (); + type Error = std::io::Error; + + fn process_events( + &mut self, + _: calloop::Readiness, + token: calloop::Token, + mut callback: F, + ) -> Result + where + F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, + { + if Some(token) != self.token { + return Ok(calloop::PostAction::Continue); + } + + log::trace!("Pooling for DRM events..."); + let events = self.card.receive_events()?; + events.for_each(|event| callback(event, &mut ())); + + Ok(calloop::PostAction::Continue) + } + + fn register( + &mut self, + poll: &mut calloop::Poll, + factory: &mut calloop::TokenFactory, + ) -> calloop::Result<()> { + self.token = Some(factory.token()); + + // Safety: the FD cannot be closed without removing the DrmDeviceNotifier from the event loop + unsafe { + poll.register( + self.card.as_fd(), + calloop::Interest::READ, + calloop::Mode::Level, + self.token.unwrap(), + ) + } + } + + fn reregister( + &mut self, + poll: &mut calloop::Poll, + factory: &mut calloop::TokenFactory, + ) -> calloop::Result<()> { + self.token = Some(factory.token()); + poll.reregister( + self.card.as_fd(), + calloop::Interest::READ, + calloop::Mode::Level, + self.token.unwrap(), + ) + } + + fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> { + self.token = None; + poll.unregister(self.card.as_fd()) + } +} diff --git a/crates/compositor/src/backend/tty/mod.rs b/crates/compositor/src/backend/tty/mod.rs index a4a3f39..d5b2551 100644 --- a/crates/compositor/src/backend/tty/mod.rs +++ b/crates/compositor/src/backend/tty/mod.rs @@ -1,185 +1,347 @@ -pub mod atomic_req; -pub mod format_modifier; -pub mod skia; +use std::{ + collections::{HashSet, VecDeque}, + os::fd::{FromRawFd as _, OwnedFd, RawFd}, + sync::Arc, + task::Poll, +}; + +use smithay::reexports::{ + drm::{self, control::Device as _}, + gbm, +}; + +use crate::backend::tty::utils::Guard; + +pub mod drmx; pub mod utils; pub mod vulkan; -use std::{ops::Deref, os::fd::AsFd, sync::Arc}; +pub type Card = gbm::Device; -use smithay::{ - backend::drm::DrmNode, - reexports::{ - calloop, - drm::{self, Device, control::Device as _}, - }, -}; +pub trait Backend { + type Error: std::error::Error; -#[derive(Debug)] -pub struct Card(std::fs::File, DrmNode); + fn new(card: &Card) -> Result + where + Self: Sized; -impl AsFd for Card { - fn as_fd(&self) -> std::os::unix::prelude::BorrowedFd<'_> { - self.0.as_fd() - } + fn modifiers(&self, format: drmx::Format) -> HashSet; + + type Buffer: BackendBuffer; + fn new_buffer( + &self, + drm: &DrmState, + bo: &gbm::BufferObject<()>, + ) -> Result; + + fn render(&mut self, buffer: &mut Self::Buffer, frame: usize) -> Result, Self::Error>; } -impl drm::Device for Card {} -impl drm::control::Device for Card {} +pub trait BackendBuffer { + type Backend: Backend; -const GRAPHICS_CARDS: &str = "/dev/dri/card*"; + /// Import a sync file from DRM, which signals when scanout completes (and the backend can re-use this buffer). + fn import_sync(&mut self, fd: OwnedFd) -> Result<(), ::Error>; -impl Card { - fn new(path: impl AsRef) -> anyhow::Result { - Ok(Self( - std::fs::OpenOptions::new() - .read(true) - .write(true) - .open(path.as_ref())?, - DrmNode::from_path(path)?, - )) - } + /// Export a sync file for DRM, which is signalled when the rendering completes (and DRM can scanout from this buffer) + fn export_sync(&mut self) -> Result, ::Error>; +} - pub fn major(&self) -> u32 { - self.1.major() - } +const LEN: usize = 3; + +#[derive(thiserror::Error)] +pub enum ScanoutError { + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error(transparent)] + DrmFormat(#[from] drmx::format_modifier::DrmFormatParseError), + + #[error("cannot get supported formats")] + CannotGetDrmFormats, - pub fn minor(&self) -> u32 { - self.1.minor() + #[error(transparent)] + Backend(B::Error), + + #[error(transparent)] + Modeset(#[from] ModesetError), +} + +impl std::fmt::Debug for ScanoutError +where + B::Error: std::fmt::Debug, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Self::Io(arg0) => f.debug_tuple("Io").field(arg0).finish(), + Self::DrmFormat(arg0) => f.debug_tuple("DrmFormat").field(arg0).finish(), + Self::CannotGetDrmFormats => write!(f, "CannotGetDrmFormats"), + Self::Backend(arg0) => f.debug_tuple("Backend").field(arg0).finish(), + Self::Modeset(arg0) => f.debug_tuple("Modeset").field(arg0).finish(), + } } +} + +#[derive(Debug, thiserror::Error)] +pub enum ModesetError { + #[error(transparent)] + Io(#[from] std::io::Error), + #[error(transparent)] + AtomicReq(#[from] drmx::atomic_req::AtomicReqError), +} + +pub struct BufferedOutput { + pub frame: usize, + queue: VecDeque, + pub drm: DrmState, + pub backend: B, + pub buffers: [ScanoutBuffer; LEN], +} + +impl BufferedOutput { + pub fn new( + card: Arc, + crtc: drm::control::crtc::Handle, + plane: drm::control::plane::Handle, + mode: drm::control::Mode, + connector: drm::control::connector::Handle, + format: drmx::Format, + ) -> Result> { + let backend = B::new(&card).map_err(ScanoutError::Backend)?; + + // Format modifiers... + let modifiers = { + // DRM + let drm_modifiers = + drm_modifiers::(&card, format.fourcc(), &card.get_properties(plane)?)?; + + // Provided by backend... + let vulkan_modifiers = backend.modifiers(format); - pub fn find_primary() -> anyhow::Result { - let overridden = match std::env::var("NADVA_DRM_DEVICE") { - Ok(path) => Some(std::path::PathBuf::from(path)), - Err(std::env::VarError::NotPresent) => None, - Err(std::env::VarError::NotUnicode(_)) => { - return Err(anyhow::anyhow!( - "Non-unicode string passed into `NADVA_DRM_DEVICE`" - )); + // Intersect the DRM- and Vulkan-provided format modifiers + let usable_modifiers = vulkan_modifiers + .intersection(&drm_modifiers) + .copied() + .collect::>(); + + if usable_modifiers.is_empty() { + log::warn!( + "no common modifier between DRM plane and Vulkan; falling back to linear" + ); + vec![u64::from(gbm::Modifier::Linear)] + } else { + usable_modifiers } + .into_iter() + .map(gbm::Modifier::from) + .collect::>() + }; + + let drm = DrmState { + previous_state: card.get_crtc(crtc)?, + cache: drmx::atomic_req::AtomicRequestCache::new(card.clone()), + card, + crtc, + plane, + mode, + connector, + format, + modifiers, }; - let is_overridden = overridden.is_some(); + let [b1, b2, b3] = core::array::from_fn(|_| ScanoutBuffer::::new(&drm, &backend)); + let buffers = [b1?, b2?, b3?]; - let iter = match overridden { - Some(overridden) => { - Box::new(std::iter::once(Ok(overridden))) as Box> - } - None => Box::new(glob::glob(GRAPHICS_CARDS).unwrap()), + let mut output = Self { + drm, + backend, + buffers, + frame: 0, + queue: VecDeque::with_capacity(3), }; - let mut cards = iter - .filter_map(|res| res.map_err(anyhow::Error::from).and_then(Card::new).ok()) - .collect::>(); - - cards.retain(|card| { - match card.resource_handles() { - Ok(resource_handles) if !resource_handles.crtcs().is_empty() - // Try to set Atomic and UniversalPlanes (we need this for atomic modesetting, and separate cursor, overlay planes) - && let Ok(()) = card.set_client_capability(drm::ClientCapability::Atomic, true) - && let Ok(()) = card.set_client_capability(drm::ClientCapability::UniversalPlanes, true) - // Rule out GPUs without any displays connected to them for now. - && resource_handles.connectors().iter().any(|&con| card.get_connector(con, true).is_ok_and(|con| con.state() == drm::control::connector::State::Connected)) => true, - _ => false, - } - }); + output.modeset()?; - match cards.len() { - 0 if is_overridden => { - return Err(anyhow::anyhow!( - "device specified by NADVA_DRM_DEVICE is not usable" - )); - } - 0 => return Err(anyhow::anyhow!("No suitable graphics devices!")), - 1 => return Ok(cards.pop().unwrap()), - _ => (), + Ok(output) + } + + pub fn modeset(&mut self) -> Result<(), ModesetError> { + use drmx::atomic_req::*; + + let fb = self.buffers[self.frame].framebuffer; + let drm = &mut self.drm; + let (w, h) = drm.mode.size(); + + // This blocks to prevent a race condition with the cleanup of the `MODE_ID` blob. + // It's only done once anyway... + drm.cache + .request() + .set(drm.connector, CRTC_ID, Some(drm.crtc))? + .set(drm.crtc, ACTIVE, true)? + .set(drm.crtc, MODE_ID, Some(&drm.mode))? + .set(drm.plane, CRTC_ID, Some(drm.crtc))? + .set(drm.plane, FB_ID, Some(fb))? + .set(drm.plane, SRC_X, Fixed16::ZERO)? + .set(drm.plane, SRC_Y, Fixed16::ZERO)? + .set(drm.plane, SRC_W, Fixed16::integer(w))? + .set(drm.plane, SRC_H, Fixed16::integer(h))? + .set(drm.plane, CRTC_X, 0)? + .set(drm.plane, CRTC_Y, 0)? + .set(drm.plane, CRTC_W, w as u32)? + .set(drm.plane, CRTC_H, h as u32)? + .commit(AtomicCommitFlags::ALLOW_MODESET)?; + + Ok(()) + } + + pub fn render(&mut self) -> Result, B::Error> { + let frame_i = self.frame % 3; + let buffer = &mut self.buffers[frame_i]; + + let poll = self.backend.render(&mut buffer.backend, self.frame)?; + if let Poll::Ready(()) = poll { + self.frame += 1; + self.queue.push_back(ScanoutRequest { frame_i }); } - // Tie-breaking by using the boot VGA device. + Ok(poll) + } + + pub fn flip(&mut self) -> Result, ScanoutError> { + use drmx::atomic_req::*; - let Some((i, _)) = cards.iter().enumerate().find(|(_, card)| { - let (major, minor) = (card.1.major(), card.1.minor()); - std::fs::read_to_string(format!("/sys/dev/char/{major}:{minor}/device/boot_vga")) - .is_ok_and(|s| s.trim() == "1") - }) else { - // Or just return the first device... - return Ok(cards.swap_remove(0)); + let Some(ScanoutRequest { frame_i }) = self.queue.pop_front() else { + return Ok(Poll::Pending); }; - Ok(cards.swap_remove(i)) + let buffer = &mut self.buffers[frame_i]; + let drm = &mut self.drm; + let mut out_fd: RawFd = -1; + + drm.cache + .request() + .set(drm.plane, FB_ID, Some(buffer.framebuffer)) + .map_err(ModesetError::from)? + .set( + drm.plane, + IN_FENCE_FD, + buffer + .backend + .export_sync() + .map_err(ScanoutError::Backend)?, + ) + .map_err(ModesetError::from)? + .set(drm.crtc, OUT_FENCE_PTR, &raw mut out_fd) + .map_err(ModesetError::from)? + .commit(AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK)?; + + if out_fd == -1 { + panic!("OUT_FENCE_PTR is invalid!"); + } + + let out_fd = unsafe { OwnedFd::from_raw_fd(out_fd) }; + buffer + .backend + .import_sync(out_fd) + .map_err(ScanoutError::Backend)?; + + Ok(Poll::Ready(())) } } -pub struct DrmEventNotifier { - token: Option, - card: Arc, +pub struct ScanoutRequest { + pub frame_i: usize, +} + +pub struct DrmState { + pub card: Arc, + pub cache: drmx::atomic_req::AtomicRequestCache, + pub crtc: drm::control::crtc::Handle, + pub plane: drm::control::plane::Handle, + pub mode: drm::control::Mode, + pub connector: drm::control::connector::Handle, + pub format: drmx::Format, + pub modifiers: Vec, + pub previous_state: drm::control::crtc::Info, } -impl> DrmEventNotifier { - pub fn new(card: Arc) -> Self { - Self { token: None, card } +impl Drop for DrmState { + fn drop(&mut self) { + // Reset the state of the CRTC + let prev = &self.previous_state; + let _ = self.card.set_crtc( + self.crtc, + prev.framebuffer(), + prev.position(), + core::slice::from_ref(&self.connector), + prev.mode(), + ); } } -impl> calloop::EventSource for DrmEventNotifier { - type Event = drm::control::Event; - type Metadata = (); - type Ret = (); - type Error = std::io::Error; - - fn process_events( - &mut self, - _: calloop::Readiness, - token: calloop::Token, - mut callback: F, - ) -> Result - where - F: FnMut(Self::Event, &mut Self::Metadata) -> Self::Ret, - { - if Some(token) != self.token { - return Ok(calloop::PostAction::Continue); - } +pub struct ScanoutBuffer { + card: Arc, + framebuffer: drm::control::framebuffer::Handle, - log::trace!("Pooling for DRM events..."); - let events = self.card.receive_events()?; - events.for_each(|event| callback(event, &mut ())); + #[allow(unused)] // Here for lifetime purpose... + buffer_object: gbm::BufferObject<()>, - Ok(calloop::PostAction::Continue) - } + pub backend: B::Buffer, +} - fn register( - &mut self, - poll: &mut calloop::Poll, - factory: &mut calloop::TokenFactory, - ) -> calloop::Result<()> { - self.token = Some(factory.token()); - - // Safety: the FD cannot be closed without removing the DrmDeviceNotifier from the event loop - unsafe { - poll.register( - self.card.as_fd(), - calloop::Interest::READ, - calloop::Mode::Level, - self.token.unwrap(), - ) - } +impl Drop for ScanoutBuffer { + fn drop(&mut self) { + let _ = self.card.destroy_framebuffer(self.framebuffer); } +} - fn reregister( - &mut self, - poll: &mut calloop::Poll, - factory: &mut calloop::TokenFactory, - ) -> calloop::Result<()> { - self.token = Some(factory.token()); - poll.reregister( - self.card.as_fd(), - calloop::Interest::READ, - calloop::Mode::Level, - self.token.unwrap(), - ) - } +impl ScanoutBuffer { + pub fn new(drm: &DrmState, backend: &B) -> Result> { + let (width, height) = drm.mode.size(); - fn unregister(&mut self, poll: &mut calloop::Poll) -> calloop::Result<()> { - self.token = None; - poll.unregister(self.card.as_fd()) + let buffer_object = drm.card.create_buffer_object_with_modifiers2::<()>( + width as _, + height as _, + drm.format.fourcc(), + drm.modifiers.iter().copied(), + gbm::BufferObjectFlags::SCANOUT | gbm::BufferObjectFlags::RENDERING, + )?; + + let framebuffer = Guard::new( + drm.card + .add_planar_framebuffer(&buffer_object, drm::control::FbCmd2Flags::MODIFIERS)?, + |fb| { + let _ = drm.card.destroy_framebuffer(fb); + }, + ); + + Ok(Self { + backend: backend + .new_buffer(drm, &buffer_object) + .map_err(ScanoutError::Backend)?, + framebuffer: framebuffer.finish(), + buffer_object, + card: drm.card.clone(), + }) } } + +/// Get the format modifiers supported for this plane... +fn drm_modifiers( + card: &Card, + fourcc: gbm::Format, + plane_props: &drm::control::PropertyValueSet, +) -> Result, ScanoutError> { + let in_formats = plane_props + .iter() + .find_map(|(k, v)| (card.get_property(*k).ok()?.name() == c"IN_FORMATS").then_some(*v)) + .and_then(|blob| card.get_property_blob(blob).ok()) + .ok_or(ScanoutError::CannotGetDrmFormats)?; + let in_formats = drmx::format_modifier::drm_format_modifier_blob::read(&in_formats)?; + Ok(HashSet::::from_iter( + in_formats + .modifiers_for(fourcc) + .into_iter() + .filter(|&m| m != u64::from(gbm::Modifier::Invalid)), + )) +} diff --git a/crates/compositor/src/backend/tty/skia.rs b/crates/compositor/src/backend/tty/skia.rs deleted file mode 100644 index 429170b..0000000 --- a/crates/compositor/src/backend/tty/skia.rs +++ /dev/null @@ -1,46 +0,0 @@ -use std::sync::Arc; - -use anyhow::Context; -use skia_safe; -use smithay::reexports::ash::{self, vk::Handle}; - -impl super::vulkan::Device { - pub fn skia_context(self: Arc) -> anyhow::Result { - let device = self; - let instance = device.instance.handle().as_raw() as _; - let physical_device = device.physical.as_raw() as _; - let queue = (device.queue.as_raw() as _, device.queue_family as usize); - let raw_device = device.handle().as_raw() as _; - - let get_proc = move |of: skia_safe::gpu::vk::GetProcOf| unsafe { - let instance = &device.instance; - let entry = &instance.entry; - match of { - skia_safe::gpu::vk::GetProcOf::Instance(inst, name) => { - entry.get_instance_proc_addr(ash::vk::Instance::from_raw(inst as _), name) - } - skia_safe::gpu::vk::GetProcOf::Device(dev, name) => { - instance.get_device_proc_addr(ash::vk::Device::from_raw(dev as _), name) - } - } - .map(|f| f as _) - .unwrap_or(std::ptr::null()) - }; - - // SAFETY: the Vulkan handles will outlive this backend context - let ctx = unsafe { - skia_safe::gpu::vk::BackendContext::new_builder( - instance, - physical_device, - raw_device, - queue, - &get_proc, - None, - ) - .build() - }; - - skia_safe::gpu::direct_contexts::make_vulkan(&ctx, None) - .context("could not create skia vulkan direct context") - } -} diff --git a/crates/compositor/src/backend/tty/utils.rs b/crates/compositor/src/backend/tty/utils.rs index 6429e4d..d714171 100644 --- a/crates/compositor/src/backend/tty/utils.rs +++ b/crates/compositor/src/backend/tty/utils.rs @@ -1,7 +1,5 @@ use std::ops::Deref; -use smithay::reexports::{ash::vk, gbm}; - pub struct Guard(Option<(T, F)>); impl Guard { @@ -29,54 +27,3 @@ impl Drop for Guard { } } } - -#[derive(Debug, Clone, Copy)] -pub struct Format(pub gbm::Format); - -impl Format { - pub fn vk(&self) -> vk::Format { - use gbm::Format as G; - use vk::Format as F; - match self.0 { - G::Xrgb8888 => F::B8G8R8A8_UNORM, - G::Argb8888 => F::B8G8R8A8_UNORM, - G::Xbgr8888 => F::R8G8B8A8_UNORM, - G::Abgr8888 => F::R8G8B8A8_UNORM, - - // HDR - G::Xbgr2101010 => F::A2B10G10R10_UNORM_PACK32, - G::Abgr2101010 => F::A2B10G10R10_UNORM_PACK32, - - G::Rgb565 => F::R5G6B5_UNORM_PACK16, - - G::Abgr16161616f => F::R16G16B16A16_SFLOAT, - - _ => panic!("unsupported color format"), - } - } - - pub fn fourcc(&self) -> gbm::Format { - self.0 - } - - pub fn skia(&self) -> skia_safe::ColorType { - use gbm::Format as G; - use skia_safe::ColorType as S; - - match self.0 { - G::Xrgb8888 => S::RGB888x, - G::Argb8888 => S::RGBA8888, - G::Xbgr8888 => S::BGRA8888, - G::Abgr8888 => S::BGRA8888, - - // HDR - G::Xbgr2101010 => S::BGR101010x, - G::Abgr2101010 => S::BGRA1010102, - G::Abgr16161616f => S::RGBAF16, - - G::Rgb565 => S::RGB565, - - _ => panic!("unsupported color format"), - } - } -} diff --git a/crates/compositor/src/backend/tty/vulkan/mod.rs b/crates/compositor/src/backend/tty/vulkan/mod.rs new file mode 100644 index 0000000..09a9528 --- /dev/null +++ b/crates/compositor/src/backend/tty/vulkan/mod.rs @@ -0,0 +1,435 @@ +use std::{ + collections::HashSet, + os::{fd::AsRawFd, unix::prelude::OwnedFd}, + sync::Arc, + task::Poll, +}; + +use smithay::reexports::{ + ash::{self, vk}, + drm::buffer::Buffer, + gbm, +}; +use thiserror::Error; + +use crate::{ + backend::tty::{ + Backend, BackendBuffer, Card, DrmState, drmx, + utils::Guard, + vulkan::utils::{CommandPool, Device, ExportableSemaphore, ImportableFence, Instance}, + }, + colors::COLORS, +}; + +pub mod utils; + +pub struct Vulkan { + device: Arc, + pool: Arc, +} + +impl Drop for Vulkan { + fn drop(&mut self) { + // Ensure device has finished all of the queues before trying to deallocate/destroy anything. + unsafe { + let _ = self.device.device_wait_idle(); + } + } +} + +pub struct VulkanBuffer { + device: Arc, + memory: vk::DeviceMemory, + image: vk::Image, + image_view: vk::ImageView, + fence: ImportableFence, + semaphore: ExportableSemaphore, + pool: Arc, + cmd: vk::CommandBuffer, + cmd_fence: vk::Fence, + size: (u32, u32), +} + +#[derive(Debug, Error)] +pub enum VulkanError { + #[error(transparent)] + Loading(#[from] ash::LoadingError), + + #[error(transparent)] + Vulkan(#[from] ash::vk::Result), + + #[error(transparent)] + Io(#[from] std::io::Error), + + #[error("no supported DMA memory type can be imported into Vulkan")] + UnsupportedDmaMemory, + + #[error(transparent)] + Fd(#[from] gbm::InvalidFdError), + + #[error("{0}")] + Other(String), +} + +impl From for VulkanError { + fn from(value: String) -> Self { + Self::Other(value) + } +} + +impl Backend for Vulkan { + type Error = VulkanError; + + fn new(card: &Card) -> Result { + let instance = Instance::load()?; + let device = Arc::new(instance.device_for(card)?); + + Ok(Self { + pool: Arc::new(CommandPool::new(device.clone())?), + device, + }) + } + + fn modifiers(&self, format: drmx::Format) -> HashSet { + let device = &self.device; + + // Get the number of available format modifiers. + let format_modifier_len = { + let mut modifiers = vk::DrmFormatModifierPropertiesListEXT::default(); + let mut fmt_props2 = vk::FormatProperties2::default().push_next(&mut modifiers); + unsafe { + device.instance.get_physical_device_format_properties2( + device.physical, + format.into(), + &mut fmt_props2, + ) + }; + + modifiers.drm_format_modifier_count as usize + }; + // Then, load all the format modifiers into a list. + let modifier_properties = { + let mut mod_props = + vec![vk::DrmFormatModifierPropertiesEXT::default(); format_modifier_len]; + let mut modifier_list = vk::DrmFormatModifierPropertiesListEXT::default() + .drm_format_modifier_properties(&mut mod_props); + let mut fmt_props2 = vk::FormatProperties2::default().push_next(&mut modifier_list); + unsafe { + device.instance.get_physical_device_format_properties2( + device.physical, + format.into(), + &mut fmt_props2, + ) + }; + + mod_props + }; + // Only select the modifiers for formats with COLOR + modifier_properties + .into_iter() + .filter(|a| { + a.drm_format_modifier_tiling_features + .contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT) + }) + .map(|a| a.drm_format_modifier) + .collect::>() + } + + type Buffer = VulkanBuffer; + fn new_buffer( + &self, + drm: &DrmState, + bo: &gbm::BufferObject<()>, + ) -> Result { + let cmd = { + let [cmd] = unsafe { + self.device + .allocate_command_buffers( + &vk::CommandBufferAllocateInfo::default() + .command_buffer_count(1) + .command_pool(**self.pool) + .level(vk::CommandBufferLevel::PRIMARY), + )? + .try_into() + .expect("one allocated command buffer") + }; + + Guard::new(cmd, |cmd| { + unsafe { self.device.free_command_buffers(**self.pool, &[cmd]) }; + }) + }; + + // Create vk::Image + let image = { + let plane_layouts = (0..bo.plane_count()) + .map(|i| i as i32) + .map(|i| { + vk::SubresourceLayout::default() + .offset(bo.offset(i) as _) + .row_pitch(bo.stride_for_plane(i) as _) + }) + .collect::>(); + + let mut mod_info = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default() + .drm_format_modifier(bo.modifier().into()) + .plane_layouts(&plane_layouts); + + let mut ext_info = vk::ExternalMemoryImageCreateInfo::default() + .handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT); + + let (width, height) = bo.size(); + let info = vk::ImageCreateInfo::default() + .image_type(vk::ImageType::TYPE_2D) + .format(drm.format.into()) + .extent(vk::Extent3D { + width, + height, + depth: 1, + }) + .mip_levels(1) + .array_layers(1) + .samples(vk::SampleCountFlags::TYPE_1) + .tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT) + .usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) + .sharing_mode(vk::SharingMode::EXCLUSIVE) + .initial_layout(vk::ImageLayout::UNDEFINED) + .push_next(&mut ext_info) + .push_next(&mut mod_info); + + let img = unsafe { self.device.create_image(&info, None)? }; + + Guard::new(img, |img| unsafe { self.device.destroy_image(img, None) }) + }; + + // Use the external memory via the fd provided by GBM + let (imported_fd, memory) = { + // (A) Create a DMA file descriptor (owned by Vulkan) + let fd = bo.fd()?; + let fd_props = { + let mut props = vk::MemoryFdPropertiesKHR::default(); + let ext_mem_device = ash::khr::external_memory_fd::Device::new( + &self.device.instance, + self.device.as_ref(), + ); + unsafe { + ext_mem_device.get_memory_fd_properties( + vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, + fd.as_raw_fd(), + &mut props, + )?; + }; + + props + }; + + let reqs = unsafe { self.device.get_image_memory_requirements(*image) }; + + // Get first mem_type compatible with Image and fd. + let compatible_mem_types = reqs.memory_type_bits & fd_props.memory_type_bits; + if compatible_mem_types == 0 { + return Err(VulkanError::UnsupportedDmaMemory); + } + let mem_type_index = compatible_mem_types.trailing_zeros(); + + // (A) Vulkan will take ownership over our fd. + let mut import = vk::ImportMemoryFdInfoKHR::default() + .handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) + .fd(fd.as_raw_fd()); + + let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(*image); + + let info = vk::MemoryAllocateInfo::default() + .allocation_size(reqs.size) + .memory_type_index(mem_type_index) + .push_next(&mut import) + .push_next(&mut dedicated); + + let memory = unsafe { self.device.allocate_memory(&info, None)? }; + + ( + fd, + Guard::new(memory, |memory| unsafe { + self.device.free_memory(memory, None) + }), + ) + }; + + unsafe { self.device.bind_image_memory(*image, *memory, 0)? }; + + // (A) Okay, now everything is good, we can forget the file descriptor here, since Vulkan will close it. + std::mem::forget(imported_fd); + + let image_view = { + let info = vk::ImageViewCreateInfo::default() + .components(vk::ComponentMapping::default()) + .format(drm.format.into()) + .image(*image) + .view_type(vk::ImageViewType::TYPE_2D) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .base_array_layer(0) + .layer_count(1) + .base_mip_level(0) + .level_count(1), + ); + + let image_view = unsafe { self.device.create_image_view(&info, None)? }; + + Guard::new(image_view, |view| unsafe { + self.device.destroy_image_view(view, None) + }) + }; + + let cmd_fence = unsafe { + Guard::new( + self.device.create_fence( + &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), + None, + )?, + |fence| self.device.destroy_fence(fence, None), + ) + }; + + Ok(VulkanBuffer { + device: self.device.clone(), + memory: memory.finish(), + image: image.finish(), + image_view: image_view.finish(), + semaphore: ExportableSemaphore::new(self.device.clone())?, + fence: ImportableFence::new(self.device.clone())?, + pool: self.pool.clone(), + cmd: cmd.finish(), + cmd_fence: cmd_fence.finish(), + size: { + let (w, h) = drm.mode.size(); + (w as u32, h as u32) + }, + }) + } + + fn render( + &mut self, + buffer: &mut Self::Buffer, + frame: usize, + ) -> Result, Self::Error> { + let fence_signal = unsafe { + self.device.get_fence_status(*buffer.fence)? + && self.device.get_fence_status(buffer.cmd_fence)? + }; + + if !fence_signal { + return Ok(Poll::Pending); + } + + unsafe { + self.device + .reset_fences(&[*buffer.fence, buffer.cmd_fence])?; + self.device + .reset_command_buffer(buffer.cmd, vk::CommandBufferResetFlags::default())?; + } + + { + unsafe { + self.device.begin_command_buffer( + buffer.cmd, + &vk::CommandBufferBeginInfo::default() + .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + )? + } + + let barrier = vk::ImageMemoryBarrier2::default() + .src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE) + .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + .old_layout(vk::ImageLayout::UNDEFINED) + .new_layout(vk::ImageLayout::GENERAL) + .image(buffer.image) + .subresource_range( + vk::ImageSubresourceRange::default() + .aspect_mask(vk::ImageAspectFlags::COLOR) + .level_count(1) + .layer_count(1), + ); + + // UNDEFINED -> GENERAL layout + unsafe { + self.device.cmd_pipeline_barrier2( + buffer.cmd, + &vk::DependencyInfo::default() + .image_memory_barriers(core::slice::from_ref(&barrier)), + ) + }; + + // Fill with some color. + { + let color = COLORS[frame % COLORS.len()]; + let attachment = vk::RenderingAttachmentInfo::default() + .image_layout(vk::ImageLayout::GENERAL) + .image_view(buffer.image_view) + // RED + .load_op(vk::AttachmentLoadOp::CLEAR) + .store_op(vk::AttachmentStoreOp::STORE) + .clear_value(vk::ClearValue { + color: vk::ClearColorValue { float32: color }, + }); + + let (width, height) = buffer.size; + let render = vk::RenderingInfo::default() + .render_area(vk::Rect2D { + offset: vk::Offset2D { x: 0, y: 0 }, + extent: vk::Extent2D { + width: width as _, + height: height as _, + }, + }) + .layer_count(1) + .color_attachments(core::slice::from_ref(&attachment)); + + unsafe { + self.device.cmd_begin_rendering(buffer.cmd, &render); + } + unsafe { self.device.cmd_end_rendering(buffer.cmd) }; + } + + unsafe { self.device.end_command_buffer(buffer.cmd)? } + } + + unsafe { + self.device.queue_submit( + self.device.queue, + &[vk::SubmitInfo::default() + .command_buffers(&[buffer.cmd]) + .signal_semaphores(buffer.semaphore.as_slice())], + buffer.cmd_fence, + )?; + } + + Ok(Poll::Ready(())) + } +} + +impl Drop for VulkanBuffer { + fn drop(&mut self) { + unsafe { + self.device.destroy_fence(self.cmd_fence, None); + self.device.free_command_buffers(**self.pool, &[self.cmd]); + self.device.destroy_image_view(self.image_view, None); + self.device.destroy_image(self.image, None); + self.device.free_memory(self.memory, None); + } + } +} + +impl BackendBuffer for VulkanBuffer { + type Backend = Vulkan; + + fn import_sync(&mut self, fd: OwnedFd) -> Result<(), ::Error> { + self.fence.import(fd)?; + Ok(()) + } + + fn export_sync(&mut self) -> Result, ::Error> { + self.semaphore.sync_fd().map_err(VulkanError::Vulkan) + } +} diff --git a/crates/compositor/src/backend/tty/vulkan.rs b/crates/compositor/src/backend/tty/vulkan/utils.rs similarity index 87% rename from crates/compositor/src/backend/tty/vulkan.rs rename to crates/compositor/src/backend/tty/vulkan/utils.rs index 0b8c902..7fc3be6 100644 --- a/crates/compositor/src/backend/tty/vulkan.rs +++ b/crates/compositor/src/backend/tty/vulkan/utils.rs @@ -2,6 +2,8 @@ use std::{collections::HashSet, ffi::CStr, ops::Deref, os::fd::{AsRawFd, FromRaw use smithay::reexports::ash::{self, vk::{self, Handle}}; +use crate::backend::tty::{Card, drmx, vulkan::VulkanError}; + #[cfg(debug_assertions)] const REQUIRED_INSTANCE_EXTENSIONS: &[&CStr] = &[ash::ext::debug_utils::NAME]; #[cfg(debug_assertions)] @@ -98,8 +100,9 @@ impl Drop for DebugUtils { } } + impl Instance { - pub fn load() -> anyhow::Result { + pub fn load() -> Result { // SAFETY: We will not use functions on this instance after destroying it. let entry = unsafe { ash::Entry::load()? }; @@ -123,11 +126,12 @@ impl Instance { log::trace!("Vulkan Version: {version:?}"); if raw_version < MINIMUM_VULKAN_VERSION { - return Err(anyhow::anyhow!( - "your driver's Vulkan version ({}.{}.{}) is below the 1.1.x minimum", + return Err(VulkanError::Other( + format!("Your driver's Vulkan version ({0}.{1}.{2}) is below the 1.1.x minimum.", version.0, version.1, version.2 + ) )); } @@ -140,8 +144,8 @@ impl Instance { REQUIRED_INSTANCE_LAYERS, ) .map_err(|_| { - anyhow::anyhow!( - "the VK_LAYER_KHRONOS_validation layer is required for debug builds" + VulkanError::Other( + "Missing layer: VK_LAYER_KHRONOS_validation".to_string() ) })? }; @@ -155,7 +159,7 @@ impl Instance { REQUIRED_INSTANCE_EXTENSIONS, ) .map_err(|_| { - anyhow::anyhow!("your graphics device should support VK_EXT_debug_utils") + VulkanError::Other("Missing layer: VK_EXT_debug_utils".to_string()) })? }; @@ -211,7 +215,7 @@ impl Instance { }) } - pub fn device_for(self, card: &super::Card) -> anyhow::Result { + pub fn device_for(self, card: &Card) -> Result { let instance = self; let devices = unsafe { instance.enumerate_physical_devices()? }; @@ -243,14 +247,14 @@ impl Instance { found.then_some((api_version, device)) }) - .ok_or(anyhow::anyhow!("your graphics device does not support Vulkan, or you are using a multi-GPU setup which is not supported"))?; + .ok_or_else(|| VulkanError::Other("your graphics device does not support Vulkan, or you are using a multi-GPU setup which is not supported".to_string()))?; if api_version < PREFERRED_VULKAN_VERSION { - return Err(anyhow::anyhow!( + log::warn!( "graphics device only supports Vulkan {}.{}, but 1.3 is required", vk::api_version_major(api_version), vk::api_version_minor(api_version), - )); + ); } let queue_families = @@ -260,8 +264,8 @@ impl Instance { .iter() .position(|queue| queue.queue_flags.contains(vk::QueueFlags::GRAPHICS)) else { - return Err(anyhow::anyhow!( - "Cannot find GRAPHICS queue family on the (physical) graphics device." + return Err(VulkanError::Other( + "Cannot find GRAPHICS queue family on the (physical) graphics device.".to_string() )); }; @@ -272,8 +276,8 @@ impl Instance { ) { Ok(exts) => exts, Err(missing) => { - return Err(anyhow::anyhow!( - "your graphics device is missing the following Vulkan device extensions: {missing:?}" + return Err(VulkanError::Other( + format!("your graphics device is missing the following Vulkan device extensions: {missing:?}") )); } }; @@ -291,8 +295,8 @@ impl Instance { || check13.synchronization2 != vk::TRUE || check13.dynamic_rendering != vk::TRUE { - return Err(anyhow::anyhow!( - "device lacks required Vulkan 1.2 and/or 1.3 features" + return Err(VulkanError::Other( + "device lacks required Vulkan 1.2 and/or 1.3 features: timeline_semaphore, synchronization2, dynamic_rendering".to_string() )); } } @@ -508,17 +512,35 @@ impl ImportableFence { } } -pub struct CommandPool(Arc, vk::CommandPool); +pub struct CommandPool { + device: Arc, + pool: vk::CommandPool, +} impl CommandPool { - pub fn new(device: Arc, command_pool: vk::CommandPool) -> Self { - Self(device, command_pool) + pub fn new(device: Arc) -> ash::prelude::VkResult { + let info = vk::CommandPoolCreateInfo::default() + .queue_family_index(device.queue_family) + .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); + Ok(CommandPool { + pool: unsafe { device.create_command_pool(&info, None) }?, + device, + }) } } impl Drop for CommandPool { fn drop(&mut self) { - unsafe {self.0.destroy_command_pool(self.1, None); } + unsafe { + self.device.destroy_command_pool(self.pool, None); + } + } +} +impl std::ops::Deref for CommandPool { + type Target = vk::CommandPool; + + fn deref(&self) -> &Self::Target { + &self.pool } } @@ -573,3 +595,26 @@ unsafe extern "system" fn callback( vk::FALSE } + +impl From for vk::Format { + fn from(value: drmx::Format) -> Self { + use smithay::reexports::gbm::Format as G; + use vk::Format as F; + match value.0 { + G::Xrgb8888 => F::B8G8R8A8_UNORM, + G::Argb8888 => F::B8G8R8A8_UNORM, + G::Xbgr8888 => F::R8G8B8A8_UNORM, + G::Abgr8888 => F::R8G8B8A8_UNORM, + + // HDR + G::Xbgr2101010 => F::A2B10G10R10_UNORM_PACK32, + G::Abgr2101010 => F::A2B10G10R10_UNORM_PACK32, + + G::Rgb565 => F::R5G6B5_UNORM_PACK16, + + G::Abgr16161616f => F::R16G16B16A16_SFLOAT, + + _ => panic!("unsupported color format"), + } + } +} \ No newline at end of file diff --git a/crates/compositor/src/main.rs b/crates/compositor/src/main.rs index d8dd0b3..56c46c4 100644 --- a/crates/compositor/src/main.rs +++ b/crates/compositor/src/main.rs @@ -2,42 +2,32 @@ pub mod backend; pub mod colors; pub mod config; -use std::{ - collections::{HashSet, VecDeque}, - os::fd::{AsFd, AsRawFd, FromRawFd, OwnedFd, RawFd}, - sync::Arc, - task::Poll, - time::Duration, -}; +use std::{sync::Arc, task::Poll, time::Duration}; // use skia_safe::prelude::NativeAccess; -use anyhow::Context as _; use smithay::reexports::{ - ash::{self, vk}, calloop, - drm::{self, Device, buffer::Buffer, control::Device as _}, + drm::{self, Device, control::Device as _}, gbm, }; -use crate::{ - backend::tty::{self, utils::Guard}, - colors::COLORS, +use crate::backend::tty::{ + self, BufferedOutput, + drmx::{self, DrmEventNotifier}, + vulkan::Vulkan, }; fn main() -> anyhow::Result<()> { pretty_env_logger::init(); - let card = tty::Card::find_primary()?; + let card = tty::drmx::Card::find_primary()?; log::trace!("DRM Driver: {:?}", card.get_driver()); let resources = card.resource_handles()?; let card = Arc::new(gbm::Device::new(card)?); - let instance = tty::vulkan::Instance::load()?; - let device = Arc::new(instance.device_for(&card)?); - // Pick a suitable CRTC and mode for the active connector. let connector = resources .connectors() @@ -90,7 +80,7 @@ fn main() -> anyhow::Result<()> { .filter(|p| resources.filter_crtcs(p.possible_crtcs()).contains(&crtc)) .collect::>(); - let (plane, plane_props) = planes + let (plane, _plane_props) = planes .iter() .filter_map(|p| card.get_properties(p.handle()).ok().map(|info| (p, info))) .filter_map(|(p, info)| { @@ -119,54 +109,17 @@ fn main() -> anyhow::Result<()> { .find_map(|(p, ty, props)| (ty == drm::control::PlaneType::Primary).then_some((p, props))) .ok_or(anyhow::anyhow!("cannot find suitable primary plane"))?; - let format = tty::utils::Format(gbm::Format::Xrgb8888); - - // Format modifiers... - let usable_modifiers = { - // DRM - let drm_modifiers = drm_modifiers(&card, format.fourcc(), &plane_props)?; - - // Vulkan - let vulkan_modifiers = vulkan_modifiers(&device, format.vk()); - - // Intersect the DRM- and Vulkan-provided format modifiers - let usable_modifiers = vulkan_modifiers - .intersection(&drm_modifiers) - .copied() - .collect::>(); - - if usable_modifiers.is_empty() { - log::warn!("no common modifier between DRM plane and Vulkan; falling back to linear"); - vec![u64::from(gbm::Modifier::Linear)] - } else { - usable_modifiers - } - }; - - let usable_modifiers = usable_modifiers - .iter() - .copied() - .map(gbm::Modifier::from) - .collect::>(); + let format = drmx::Format(gbm::Format::Xrgb8888); // let mut skia = device.clone().skia_context()?; - let mut output = BufferedOutput::new( - card.clone(), - device, - crtc, - plane, - mode, - connector.handle(), - format, - &usable_modifiers, - )?; - + let mut output = + BufferedOutput::::new(card.clone(), crtc, plane, mode, connector.handle(), format)?; let mut event_loop = calloop::EventLoop::try_new()?; event_loop.handle().insert_source( - tty::DrmEventNotifier::new(card.clone()), - |event, _, output: &mut BufferedOutput| { + DrmEventNotifier::new(card.clone()), + |event, _, output: &mut BufferedOutput| { if let drm::control::Event::PageFlip(page_flip_event) = event { log::trace!( "Flip event! {:?} @ {:?}", @@ -174,9 +127,7 @@ fn main() -> anyhow::Result<()> { page_flip_event.duration ); - if let Some(pending_req) = output.queue.pop_front() - && let Err(err) = output.flip(pending_req) - { + if let Err(err) = output.flip() { println!("Error during page flip: {err:?}"); } } @@ -196,17 +147,20 @@ fn main() -> anyhow::Result<()> { ) .map_err(|a| a.error)?; - let Poll::Ready(req) = output.try_render()? else { + let Poll::Ready(()) = output.render()? else { panic!("First buffer is still being scanned! Should not be the case!") }; - output.flip(req)?; + + let Poll::Ready(()) = output.flip()? else { + panic!("Should not have a problem with queuing an initial flip.") + }; let now = std::time::Instant::now(); event_loop.run( Some(Duration::from_millis(5)), &mut output, - |output| match output.try_render().expect("No error within render loop") { - Poll::Ready(req) => output.queue.push_back(req), + |output| match output.render().expect("No error within render loop") { + Poll::Ready(()) => (), Poll::Pending => (), }, )?; @@ -216,738 +170,3 @@ fn main() -> anyhow::Result<()> { Ok(()) } - -pub struct ScanoutRequest { - pub frame_i: usize, -} - -pub struct BufferedOutput { - // DRM - card: Arc>, - cache: tty::atomic_req::AtomicRequestCache, - connector: drm::control::connector::Handle, - crtc: drm::control::crtc::Handle, - plane: drm::control::plane::Handle, - mode: drm::control::Mode, - - previous_state: drm::control::crtc::Info, - - // Vulkan - frame: usize, - buffers: [ScanoutBuffer; 3], - pool: tty::vulkan::CommandPool, - device: Arc, - queue: VecDeque, -} - -impl Drop for BufferedOutput { - fn drop(&mut self) { - // Reset the state of the CRTC - { - let prev = &self.previous_state; - let _ = self.card.set_crtc( - self.crtc, - prev.framebuffer(), - prev.position(), - core::slice::from_ref(&self.connector), - prev.mode(), - ); - } - - unsafe { - let _ = self.device.device_wait_idle(); - } - } -} - -impl BufferedOutput { - pub fn new( - card: Arc>, - device: Arc, - crtc: drm::control::crtc::Handle, - plane: drm::control::plane::Handle, - mode: drm::control::Mode, - connector: drm::control::connector::Handle, - format: tty::utils::Format, - modifiers: &[gbm::Modifier], - ) -> anyhow::Result { - // Render =:render_sem:=> Scanout - // Create a Vulkan semaphore that we can export to DRM via a SYNC_FD. - - let command_pool = { - let info = vk::CommandPoolCreateInfo::default() - .queue_family_index(device.queue_family) - .flags(vk::CommandPoolCreateFlags::RESET_COMMAND_BUFFER); - - unsafe { - Guard::new(device.create_command_pool(&info, None)?, |pool| { - device.destroy_command_pool(pool, None) - }) - } - }; - - let (cmd, cmd_frames) = { - let info = vk::CommandBufferAllocateInfo::default() - .command_buffer_count(1) - .command_pool(*command_pool) - .level(vk::CommandBufferLevel::PRIMARY); - - let cmd = unsafe { - let [cmd] = device.allocate_command_buffers(&info)?.try_into().unwrap(); - - Guard::new(cmd, |cmd| { - device.free_command_buffers(*command_pool, &[cmd]) - }) - }; - - let info = vk::CommandBufferAllocateInfo::default() - .command_buffer_count(3) - .command_pool(*command_pool) - .level(vk::CommandBufferLevel::PRIMARY); - - let cmd_frames = unsafe { - let cmds @ [_, _, _] = device.allocate_command_buffers(&info)?.try_into().unwrap(); - - Guard::new(cmds, |cmds| { - device.free_command_buffers(*command_pool, cmds.as_slice()) - }) - }; - - (cmd, cmd_frames) - }; - - let buffers = { - let [cmd1, cmd2, cmd3] = cmd_frames.finish(); - let create_scan = |cmd| { - ScanoutBuffer::new( - device.clone(), - card.clone(), - format, - mode, - modifiers, - *command_pool, - cmd, // &mut skia, - ) - }; - - [create_scan(cmd1)?, create_scan(cmd2)?, create_scan(cmd3)?] - }; - - anyhow::ensure!( - buffers[0].bo.modifier() == buffers[1].bo.modifier(), - "Both buffers should have the same format modifiers!" - ); - - // Clear the first buffer to avoid displaying garbage. - { - let clear_fence = unsafe { - Guard::new( - device.create_fence(&vk::FenceCreateInfo::default(), None)?, - |fence| { - let _ = device.wait_for_fences( - core::slice::from_ref(&fence), - true, - 1_000_000_000, - ); - device.destroy_fence(fence, None); - }, - ) - }; - - unsafe { - device.begin_command_buffer( - *cmd, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - )? - } - - let barrier = vk::ImageMemoryBarrier2::default() - .src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE) - .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .old_layout(vk::ImageLayout::UNDEFINED) - .new_layout(vk::ImageLayout::GENERAL) - .image(buffers[2].image) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .level_count(1) - .layer_count(1), - ); - - // UNDEFINED -> GENERAL layout - unsafe { - device.cmd_pipeline_barrier2( - *cmd, - &vk::DependencyInfo::default() - .image_memory_barriers(core::slice::from_ref(&barrier)), - ) - }; - - // Fill with BLACK. - { - let attachment = vk::RenderingAttachmentInfo::default() - .image_layout(vk::ImageLayout::GENERAL) - .image_view(buffers[2].image_view) - // RED - .load_op(vk::AttachmentLoadOp::CLEAR) - .store_op(vk::AttachmentStoreOp::STORE) - .clear_value(vk::ClearValue { - color: vk::ClearColorValue { - float32: [0.0, 0.0, 0.0, 1.0], - }, - }); - - let (width, height) = mode.size(); - let render = vk::RenderingInfo::default() - .render_area(vk::Rect2D { - offset: vk::Offset2D { x: 0, y: 0 }, - extent: vk::Extent2D { - width: width as _, - height: height as _, - }, - }) - .layer_count(1) - .color_attachments(core::slice::from_ref(&attachment)); - - unsafe { - device.cmd_begin_rendering(*cmd, &render); - } - unsafe { device.cmd_end_rendering(*cmd) }; - } - - unsafe { device.end_command_buffer(*cmd)? } - - unsafe { - device.queue_submit( - device.queue, - &[vk::SubmitInfo::default().command_buffers(core::slice::from_ref(&cmd))], - *clear_fence, - )? - }; - } - - let previous_state = card.get_crtc(crtc)?; - - drop(cmd); - - let mut output = Self { - cache: tty::atomic_req::AtomicRequestCache::new(card.clone()), - frame: 0, - connector, - crtc, - plane, - mode, - previous_state, - buffers, - card, - pool: tty::vulkan::CommandPool::new(device.clone(), command_pool.finish()), - device, - queue: VecDeque::with_capacity(3), - }; - - output.modeset()?; - - Ok(output) - } - - pub fn modeset(&mut self) -> anyhow::Result<()> { - use tty::atomic_req::*; - - let fb = self.buffers[self.frame].fb; - let (w, h) = self.mode.size(); - - // This blocks to prevent a race condition with the cleanup of the `MODE_ID` blob. - // It's only done once anyway... - self.cache - .request() - .set(self.connector, CRTC_ID, Some(self.crtc))? - .set(self.crtc, ACTIVE, true)? - .set(self.crtc, MODE_ID, Some(&self.mode))? - .set(self.plane, CRTC_ID, Some(self.crtc))? - .set(self.plane, FB_ID, Some(fb))? - .set(self.plane, SRC_X, Fixed16::ZERO)? - .set(self.plane, SRC_Y, Fixed16::ZERO)? - .set(self.plane, SRC_W, Fixed16::integer(w))? - .set(self.plane, SRC_H, Fixed16::integer(h))? - .set(self.plane, CRTC_X, 0)? - .set(self.plane, CRTC_Y, 0)? - .set(self.plane, CRTC_W, w as u32)? - .set(self.plane, CRTC_H, h as u32)? - .commit(AtomicCommitFlags::ALLOW_MODESET)?; - - Ok(()) - } - - fn try_render(&mut self) -> anyhow::Result> { - let (frame_i, buffer, color) = { - let frame_i = self.frame % 3; - let color = COLORS[self.frame % COLORS.len()]; - (frame_i, &mut self.buffers[frame_i], color) - }; - - let fence_signal = unsafe { - self.device.get_fence_status(*buffer.fence)? - && self.device.get_fence_status(buffer.cmd_fence)? - }; - - if !fence_signal { - return Ok(Poll::Pending); - } - - log::trace!("Frame #{} in buffer #{}", self.frame, frame_i); - self.frame += 1; - - unsafe { - self.device - .reset_fences(&[*buffer.fence, buffer.cmd_fence])?; - self.device - .reset_command_buffer(buffer.cmd, vk::CommandBufferResetFlags::default())?; - } - - { - unsafe { - self.device.begin_command_buffer( - buffer.cmd, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - )? - } - - let barrier = vk::ImageMemoryBarrier2::default() - .src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE) - .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .old_layout(vk::ImageLayout::UNDEFINED) - .new_layout(vk::ImageLayout::GENERAL) - .image(buffer.image) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .level_count(1) - .layer_count(1), - ); - - // UNDEFINED -> GENERAL layout - unsafe { - self.device.cmd_pipeline_barrier2( - buffer.cmd, - &vk::DependencyInfo::default() - .image_memory_barriers(core::slice::from_ref(&barrier)), - ) - }; - - // Fill with some color. - { - let attachment = vk::RenderingAttachmentInfo::default() - .image_layout(vk::ImageLayout::GENERAL) - .image_view(buffer.image_view) - // RED - .load_op(vk::AttachmentLoadOp::CLEAR) - .store_op(vk::AttachmentStoreOp::STORE) - .clear_value(vk::ClearValue { - color: vk::ClearColorValue { float32: color }, - }); - - let (width, height) = self.mode.size(); - let render = vk::RenderingInfo::default() - .render_area(vk::Rect2D { - offset: vk::Offset2D { x: 0, y: 0 }, - extent: vk::Extent2D { - width: width as _, - height: height as _, - }, - }) - .layer_count(1) - .color_attachments(core::slice::from_ref(&attachment)); - - unsafe { - self.device.cmd_begin_rendering(buffer.cmd, &render); - } - unsafe { self.device.cmd_end_rendering(buffer.cmd) }; - } - - unsafe { self.device.end_command_buffer(buffer.cmd)? } - } - - unsafe { - self.device.queue_submit( - self.device.queue, - &[vk::SubmitInfo::default() - .command_buffers(&[buffer.cmd]) - .signal_semaphores(buffer.semaphore.as_slice())], - buffer.cmd_fence, - )?; - } - - // let surface = &mut self.buffers[back].surface; - // let canvas = surface.canvas(); - // let paint = skia_safe::Paint::new(skia_safe::Color4f::from(skia_safe::Color::BLACK), None); - // canvas - // .clear(skia_safe::Color::WHITE) // - // .draw_text_align( - // self.frame.to_string(), - // ( 0.0, 100.0), - // &skia_safe::Font::default(), - // &paint, - // skia_safe::utils::text_utils::Align::Left, - // ); - - // { - // let semaphores = self.render_semaphore.skia(); - // let flush_info = { - // let mut info = skia_safe::gpu::FlushInfo::default(); - // unsafe { info.set_signal_semaphores(semaphores) }; - // info - // }; - - // let mut ctx = surface.direct_context().unwrap(); - // let submitted_semaphores = ctx.flush_surface_with_access( - // surface, - // skia_safe::surface::BackendSurfaceAccess::NoAccess, - // &flush_info, - // ); - // assert_eq!( - // submitted_semaphores, - // skia_safe::gpu::ganesh::SemaphoresSubmitted::Yes - // ); - - // let can_wait_on_semaphore = ctx.submit(Some(skia_safe::gpu::SyncCpu::No)); - // assert!(can_wait_on_semaphore); - // } - - Ok(Poll::Ready(ScanoutRequest { frame_i })) - } - - fn flip(&mut self, req: ScanoutRequest) -> anyhow::Result<()> { - use tty::atomic_req::*; - - let ScanoutRequest { frame_i } = req; - let buffer = &mut self.buffers[frame_i]; - let mut out_fd: RawFd = -1; - - self.cache - .request() - .set(self.plane, FB_ID, Some(buffer.fb))? - .set(self.plane, IN_FENCE_FD, buffer.semaphore.sync_fd()?)? - .set(self.crtc, OUT_FENCE_PTR, &raw mut out_fd)? - .commit(AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK) - .context(format!("Submitting page flip for page #{}", self.frame))?; - - if out_fd == -1 { - panic!("OUT_FENCE_PTR is invalid!"); - } - - let out_fd = unsafe { OwnedFd::from_raw_fd(out_fd) }; - buffer.fence.import(out_fd)?; - - Ok(()) - } - - // fn wait_for_flip(&self) -> std::io::Result<()> { - // loop { - // let mut events = match self.card.receive_events() { - // Ok(events) => events, - // Err(err) if err.kind() == std::io::ErrorKind::Interrupted => continue, - // Err(err) => return Err(err), - // }; - - // if events.any(|ev| matches!(ev, drm::control::Event::PageFlip(_))) { - // return Ok(()); - // } - // } - // } -} - -pub struct ScanoutBuffer { - device: Arc, - card: Arc>, - memory: vk::DeviceMemory, - image: vk::Image, - image_view: vk::ImageView, - fb: drm::control::framebuffer::Handle, - bo: gbm::BufferObject<()>, - fence: tty::vulkan::ImportableFence, - semaphore: tty::vulkan::ExportableSemaphore, - pool: vk::CommandPool, - cmd: vk::CommandBuffer, // surface: skia_safe::Surface, - cmd_fence: vk::Fence, -} - -impl Drop for ScanoutBuffer { - fn drop(&mut self) { - let _ = self.card.destroy_framebuffer(self.fb); - unsafe { - self.device.destroy_fence(self.cmd_fence, None); - self.device.free_command_buffers(self.pool, &[self.cmd]); - self.device.destroy_image_view(self.image_view, None); - self.device.destroy_image(self.image, None); - self.device.free_memory(self.memory, None); - } - } -} - -impl ScanoutBuffer { - pub fn new( - device: Arc, - card: Arc>, - format: tty::utils::Format, - mode: drm::control::Mode, - usable_modifiers: &[gbm::Modifier], - pool: vk::CommandPool, - cmd: vk::CommandBuffer, // skia_context: &mut skia_safe::gpu::DirectContext, - ) -> anyhow::Result { - // Create a BufferObject with - let (width, height) = mode.size(); - - let cmd = Guard::new(cmd, |cmd| { - unsafe { device.free_command_buffers(pool, &[cmd]) }; - }); - - let bo = card.create_buffer_object_with_modifiers2::<()>( - width as _, - height as _, - format.fourcc(), - usable_modifiers.iter().copied(), - gbm::BufferObjectFlags::SCANOUT | gbm::BufferObjectFlags::RENDERING, - )?; - - // Create vk::Image - let image = { - let plane_layouts = (0..bo.plane_count()) - .map(|i| i as i32) - .map(|i| { - vk::SubresourceLayout::default() - .offset(bo.offset(i) as _) - .row_pitch(bo.stride_for_plane(i) as _) - }) - .collect::>(); - - let mut mod_info = vk::ImageDrmFormatModifierExplicitCreateInfoEXT::default() - .drm_format_modifier(bo.modifier().into()) - .plane_layouts(&plane_layouts); - - let mut ext_info = vk::ExternalMemoryImageCreateInfo::default() - .handle_types(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT); - - let (width, height) = bo.size(); - let info = vk::ImageCreateInfo::default() - .image_type(vk::ImageType::TYPE_2D) - .format(format.vk()) - .extent(vk::Extent3D { - width, - height, - depth: 1, - }) - .mip_levels(1) - .array_layers(1) - .samples(vk::SampleCountFlags::TYPE_1) - .tiling(vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT) - .usage(vk::ImageUsageFlags::COLOR_ATTACHMENT) - .sharing_mode(vk::SharingMode::EXCLUSIVE) - .initial_layout(vk::ImageLayout::UNDEFINED) - .push_next(&mut ext_info) - .push_next(&mut mod_info); - - let img = unsafe { device.create_image(&info, None)? }; - - Guard::new(img, |img| unsafe { device.destroy_image(img, None) }) - }; - - // Use the external memory via the fd provided by GBM - let (imported_fd, memory) = { - // (A) Create a DMA file descriptor (owned by Vulkan) - let fd = bo.fd()?; - let fd_props = { - let mut props = vk::MemoryFdPropertiesKHR::default(); - let ext_mem_device = - ash::khr::external_memory_fd::Device::new(&device.instance, device.as_ref()); - unsafe { - ext_mem_device.get_memory_fd_properties( - vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT, - fd.as_raw_fd(), - &mut props, - )?; - }; - - props - }; - - let reqs = unsafe { device.get_image_memory_requirements(*image) }; - - // Get first mem_type compatible with Image and fd. - let compatible_mem_types = reqs.memory_type_bits & fd_props.memory_type_bits; - if compatible_mem_types == 0 { - anyhow::bail!("no supported DMA memory type can be imported into Vulkan") - } - let mem_type_index = compatible_mem_types.trailing_zeros(); - - // (A) Vulkan will take ownership over our fd. - let mut import = vk::ImportMemoryFdInfoKHR::default() - .handle_type(vk::ExternalMemoryHandleTypeFlags::DMA_BUF_EXT) - .fd(fd.as_raw_fd()); - - let mut dedicated = vk::MemoryDedicatedAllocateInfo::default().image(*image); - - let info = vk::MemoryAllocateInfo::default() - .allocation_size(reqs.size) - .memory_type_index(mem_type_index) - .push_next(&mut import) - .push_next(&mut dedicated); - - let memory = unsafe { device.allocate_memory(&info, None)? }; - - ( - fd, - Guard::new(memory, |memory| unsafe { device.free_memory(memory, None) }), - ) - }; - - unsafe { device.bind_image_memory(*image, *memory, 0)? }; - - // (A) Okay, now everything is good, we can forget the file descriptor here, since Vulkan will close it. - std::mem::forget(imported_fd); - - let image_view = { - let info = vk::ImageViewCreateInfo::default() - .components(vk::ComponentMapping::default()) - .format(format.vk()) - .image(*image) - .view_type(vk::ImageViewType::TYPE_2D) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .base_array_layer(0) - .layer_count(1) - .base_mip_level(0) - .level_count(1), - ); - - let image_view = unsafe { device.create_image_view(&info, None)? }; - - Guard::new(image_view, |view| unsafe { - device.destroy_image_view(view, None) - }) - }; - - let framebuffer = card.add_planar_framebuffer(&bo, drm::control::FbCmd2Flags::MODIFIERS)?; - - let cmd_fence = unsafe { - Guard::new( - device.create_fence( - &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), - None, - )?, - |fence| device.destroy_fence(fence, None), - ) - }; - // let surface = { - // let image_info = unsafe { - // skia_safe::gpu::vk::ImageInfo::new( - // image.as_raw() as _, - // Default::default(), - // skia_safe::gpu::vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT, - // skia_safe::gpu::vk::ImageLayout::UNDEFINED, - // std::mem::transmute::(format.vk()), - // 1, - // device.queue_family, - // None, - // None, - // None, - // ) - // }; - - // let render_target = skia_safe::gpu::backend_render_targets::make_vk( - // (width as i32, height as i32), - // &image_info, - // ); - - // skia_safe::gpu::surfaces::wrap_backend_render_target( - // skia_context, - // &render_target, - // skia_safe::gpu::SurfaceOrigin::TopLeft, - // format.skia(), - // None, - // None, - // ) - // .context("wrap surface")? - // }; - - Ok(ScanoutBuffer { - fence: tty::vulkan::ImportableFence::new(device.clone())?, - semaphore: tty::vulkan::ExportableSemaphore::new(device.clone())?, - memory: memory.finish(), - image: image.finish(), - image_view: image_view.finish(), - fb: framebuffer, - bo, - card, - pool, - cmd: cmd.finish(), - cmd_fence: cmd_fence.finish(), - device, - }) - } -} -fn drm_modifiers( - card: &gbm::Device, - fourcc: gbm::Format, - plane_props: &drm::control::PropertyValueSet, -) -> Result, anyhow::Error> { - let in_formats = plane_props - .iter() - .find_map(|(k, v)| (card.get_property(*k).ok()?.name() == c"IN_FORMATS").then_some(*v)) - .and_then(|blob| card.get_property_blob(blob).ok()) - .ok_or(anyhow::anyhow!("cannot get supported formats"))?; - let in_formats = tty::format_modifier::drm_format_modifier_blob::read(&in_formats)?; - Ok(HashSet::::from_iter( - in_formats - .modifiers_for(fourcc) - .into_iter() - .filter(|&m| m != u64::from(gbm::Modifier::Invalid)), - )) -} - -fn vulkan_modifiers(device: &tty::vulkan::Device, format: vk::Format) -> HashSet { - // Get the number of available format modifiers. - let format_modifier_len = { - let mut modifiers = vk::DrmFormatModifierPropertiesListEXT::default(); - let mut fmt_props2 = vk::FormatProperties2::default().push_next(&mut modifiers); - unsafe { - device.instance.get_physical_device_format_properties2( - device.physical, - format, - &mut fmt_props2, - ) - }; - - modifiers.drm_format_modifier_count as usize - }; - // Then, load all the format modifiers into a list. - let modifier_properties = { - let mut mod_props = - vec![vk::DrmFormatModifierPropertiesEXT::default(); format_modifier_len]; - let mut modifier_list = vk::DrmFormatModifierPropertiesListEXT::default() - .drm_format_modifier_properties(&mut mod_props); - let mut fmt_props2 = vk::FormatProperties2::default().push_next(&mut modifier_list); - unsafe { - device.instance.get_physical_device_format_properties2( - device.physical, - format, - &mut fmt_props2, - ) - }; - - mod_props - }; - // Only select the modifiers for formats with COLOR - modifier_properties - .into_iter() - .filter(|a| { - a.drm_format_modifier_tiling_features - .contains(vk::FormatFeatureFlags::COLOR_ATTACHMENT) - }) - .map(|a| a.drm_format_modifier) - .collect::>() -} From ab8421cbe893d5212795d1191add6ffd8f7914ed Mon Sep 17 00:00:00 2001 From: Sammy99jsp Date: Sat, 1 Aug 2026 17:37:31 +0100 Subject: [PATCH 5/8] Implement CPU backend + Skia. --- Cargo.lock | 1 + crates/compositor/Cargo.toml | 1 + crates/compositor/src/backend/tty/cpu/mod.rs | 209 ++++++++++++++++++ crates/compositor/src/backend/tty/mod.rs | 89 +++++--- .../compositor/src/backend/tty/vulkan/mod.rs | 22 +- .../src/backend/tty/vulkan/utils.rs | 2 + crates/compositor/src/main.rs | 25 ++- 7 files changed, 301 insertions(+), 48 deletions(-) create mode 100644 crates/compositor/src/backend/tty/cpu/mod.rs diff --git a/Cargo.lock b/Cargo.lock index 56751d9..1b7ae1b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -105,6 +105,7 @@ name = "avdanos-compositor" version = "0.0.1" dependencies = [ "anyhow", + "drm-ffi", "glob", "log", "pretty_env_logger", diff --git a/crates/compositor/Cargo.toml b/crates/compositor/Cargo.toml index 17a5d3e..0829efd 100644 --- a/crates/compositor/Cargo.toml +++ b/crates/compositor/Cargo.toml @@ -6,6 +6,7 @@ edition.workspace = true [dependencies] anyhow = "1.0.103" +drm-ffi = "0.9.1" glob = "0.3.3" log = "0.4.33" pretty_env_logger = "0.5.0" diff --git a/crates/compositor/src/backend/tty/cpu/mod.rs b/crates/compositor/src/backend/tty/cpu/mod.rs new file mode 100644 index 0000000..b68227f --- /dev/null +++ b/crates/compositor/src/backend/tty/cpu/mod.rs @@ -0,0 +1,209 @@ +use std::{ + collections::HashSet, + os::{ + fd::{AsFd, AsRawFd}, + unix::prelude::OwnedFd, + }, + sync::Arc, + task::Poll, +}; + +use smithay::reexports::{ + drm::{self, control::Device}, + gbm, rustix, +}; + +use crate::backend::tty::{Backend, BackendBuffer, drmx::Format}; + +use super::Card; + +pub struct Cpu { + card: Arc, +} + +pub struct CpuBuffer { + bo: gbm::BufferObject<()>, + format: Format, + fence: Option, +} + +const TIMEOUT_NSEC: i64 = 500_000; // 0.5ms + +impl Backend for Cpu { + const NAME: &str = "CPU"; + const BUFFER_FLAGS: gbm::BufferObjectFlags = const { + gbm::BufferObjectFlags::SCANOUT + .union(gbm::BufferObjectFlags::LINEAR) + .union(gbm::BufferObjectFlags::WRITE) + }; + type Error = std::io::Error; + + fn new(card: &Arc) -> Result + where + Self: Sized, + { + Ok(Cpu { card: card.clone() }) + } + + fn modifiers( + &self, + _: super::drmx::Format, + _: &HashSet, + ) -> std::collections::HashSet { + HashSet::new() + } + + type Buffer = CpuBuffer; + fn new_buffer( + &self, + drm: &super::DrmState, + bo: gbm::BufferObject<()>, + ) -> Result { + Ok(CpuBuffer { + bo, + fence: None, + format: drm.format, + }) + } + + fn render(&mut self, buffer: &mut Self::Buffer, frame: usize) -> Result, Self::Error> { + // Wait on the imported sync file. + if let Some(sync) = &buffer.fence { + const TIMEOUT: i32 = rustix::io::Errno::TIME.raw_os_error(); + match self + .card + .syncobj_wait(core::slice::from_ref(sync), TIMEOUT_NSEC, true, true) + { + Ok(_) => (), + Err(err) if let Some(TIMEOUT) = err.raw_os_error() => return Ok(Poll::Pending), + Err(err) => return Err(err), + } + } + + let (width, height) = (buffer.bo.width(), buffer.bo.height()); + let format = buffer.format; + + buffer.bo.map_mut(0, 0, width, height, |pxs| { + let info = skia_safe::ImageInfo::new( + (width as _, height as _), + format.skia(), + skia_safe::AlphaType::Premul, + None, + ); + + let stride = pxs.stride(); + let mut surface = + skia_safe::surfaces::wrap_pixels(&info, pxs.buffer_mut(), stride as usize, None) + .expect("Valid skia surface"); + + let canvas = surface.canvas(); + + canvas.clear(skia_safe::Color4f { + r: 1.0, + g: 0.0, + b: 1.0, + a: 1.0, + }); + + let f = skia_safe::FontMgr::new(); + let mut a = f.match_family("Arvo"); + let fface = a + .match_style(skia_safe::FontStyle::bold()) + .expect("Find font"); + let font = &skia_safe::Font::new(fface, 128.0); + let paint = skia_safe::Paint::new( + skia_safe::Color4f { + r: 0.0, + g: 0.0, + b: 0.0, + a: 1.0, + }, + None, + ); + canvas.draw_text_align( + format!("{frame}"), + (100.0, 100.0), + font, + &paint, + skia_safe::utils::text_utils::Align::Left, + ); + + Poll::Ready(()) + }) + } +} + +impl BackendBuffer for CpuBuffer { + type Backend = Cpu; + + fn import_sync( + &mut self, + cpu: &mut Cpu, + fd: OwnedFd, + ) -> Result<(), ::Error> { + self.fence.replace(import_sync_file(cpu.card.clone(), fd)?); + Ok(()) + } + + fn export_sync( + &mut self, + _: &mut Cpu, + ) -> Result, ::Error> { + Ok(None) + } +} + +pub struct ImportedSyncFile { + card: Arc, + sync: drm::control::syncobj::Handle, + + #[expect(unused)] // Closed when dropped + fd: OwnedFd, +} + +impl std::ops::Deref for ImportedSyncFile { + type Target = drm::control::syncobj::Handle; + + fn deref(&self) -> &Self::Target { + &self.sync + } +} + +impl Drop for ImportedSyncFile { + fn drop(&mut self) { + let _ = self.card.destroy_syncobj(self.sync); + } +} + +// Stolen shamelessly from Smithay's workaround whilst drm-rs sorts this out: +// https://github.com/caedenhq/smithay/blob/642e53e9eb1260c223c1cc821d52df4bb65a7da8/src/wayland/drm_syncobj/sync_point.rs#L201 +fn import_sync_file(card: Arc, fd: OwnedFd) -> std::io::Result { + use rustix::ioctl::{Updater, ioctl, opcode::read_write}; + + let sync = card.create_syncobj(false)?; + + const DRM_IOCTL_SYNCOBJ_FD_TO_HANDLE: rustix::ioctl::Opcode = + read_write::(drm_ffi::DRM_IOCTL_BASE, 0xC2); + + let mut args = drm_ffi::drm_syncobj_handle { + handle: sync.into(), + flags: drm_ffi::DRM_SYNCOBJ_FD_TO_HANDLE_FLAGS_IMPORT_SYNC_FILE, + fd: fd.as_raw_fd(), + pad: 0, + point: 0, + }; + + let result = unsafe { + ioctl( + card.as_fd(), + Updater::::new(&mut args), + ) + }; + + if let Err(err) = result { + let _ = card.destroy_syncobj(sync); + return Err(err.into()); + } + + Ok(ImportedSyncFile { card, sync, fd }) +} diff --git a/crates/compositor/src/backend/tty/mod.rs b/crates/compositor/src/backend/tty/mod.rs index d5b2551..14c8ca6 100644 --- a/crates/compositor/src/backend/tty/mod.rs +++ b/crates/compositor/src/backend/tty/mod.rs @@ -12,6 +12,7 @@ use smithay::reexports::{ use crate::backend::tty::utils::Guard; +pub mod cpu; pub mod drmx; pub mod utils; pub mod vulkan; @@ -19,19 +20,23 @@ pub mod vulkan; pub type Card = gbm::Device; pub trait Backend { + const NAME: &str; + const BUFFER_FLAGS: gbm::BufferObjectFlags = + const { gbm::BufferObjectFlags::SCANOUT.union(gbm::BufferObjectFlags::RENDERING) }; + type Error: std::error::Error; - fn new(card: &Card) -> Result + fn new(card: &Arc) -> Result where Self: Sized; - fn modifiers(&self, format: drmx::Format) -> HashSet; + fn modifiers(&self, format: drmx::Format, drm_modifiers: &HashSet) -> HashSet; type Buffer: BackendBuffer; fn new_buffer( &self, drm: &DrmState, - bo: &gbm::BufferObject<()>, + bo: gbm::BufferObject<()>, ) -> Result; fn render(&mut self, buffer: &mut Self::Buffer, frame: usize) -> Result, Self::Error>; @@ -41,10 +46,17 @@ pub trait BackendBuffer { type Backend: Backend; /// Import a sync file from DRM, which signals when scanout completes (and the backend can re-use this buffer). - fn import_sync(&mut self, fd: OwnedFd) -> Result<(), ::Error>; + fn import_sync( + &mut self, + backend: &mut Self::Backend, + fd: OwnedFd, + ) -> Result<(), ::Error>; /// Export a sync file for DRM, which is signalled when the rendering completes (and DRM can scanout from this buffer) - fn export_sync(&mut self) -> Result, ::Error>; + fn export_sync( + &mut self, + backend: &mut Self::Backend, + ) -> Result, ::Error>; } const LEN: usize = 3; @@ -116,25 +128,15 @@ impl BufferedOutput { drm_modifiers::(&card, format.fourcc(), &card.get_properties(plane)?)?; // Provided by backend... - let vulkan_modifiers = backend.modifiers(format); + let backend_modifiers = backend.modifiers(format, &drm_modifiers); // Intersect the DRM- and Vulkan-provided format modifiers - let usable_modifiers = vulkan_modifiers + + backend_modifiers .intersection(&drm_modifiers) .copied() - .collect::>(); - - if usable_modifiers.is_empty() { - log::warn!( - "no common modifier between DRM plane and Vulkan; falling back to linear" - ); - vec![u64::from(gbm::Modifier::Linear)] - } else { - usable_modifiers - } - .into_iter() - .map(gbm::Modifier::from) - .collect::>() + .map(gbm::Modifier::from) + .collect::>() }; let drm = DrmState { @@ -198,7 +200,9 @@ impl BufferedOutput { let frame_i = self.frame % 3; let buffer = &mut self.buffers[frame_i]; + log::trace!("Backend render start."); let poll = self.backend.render(&mut buffer.backend, self.frame)?; + log::trace!("Backend render end."); if let Poll::Ready(()) = poll { self.frame += 1; self.queue.push_back(ScanoutRequest { frame_i }); @@ -218,6 +222,7 @@ impl BufferedOutput { let drm = &mut self.drm; let mut out_fd: RawFd = -1; + log::trace!("Modesetting request!"); drm.cache .request() .set(drm.plane, FB_ID, Some(buffer.framebuffer)) @@ -227,7 +232,7 @@ impl BufferedOutput { IN_FENCE_FD, buffer .backend - .export_sync() + .export_sync(&mut self.backend) .map_err(ScanoutError::Backend)?, ) .map_err(ModesetError::from)? @@ -240,9 +245,10 @@ impl BufferedOutput { } let out_fd = unsafe { OwnedFd::from_raw_fd(out_fd) }; + log::trace!("Backend import syncfile"); buffer .backend - .import_sync(out_fd) + .import_sync(&mut self.backend, out_fd) .map_err(ScanoutError::Backend)?; Ok(Poll::Ready(())) @@ -283,9 +289,6 @@ pub struct ScanoutBuffer { card: Arc, framebuffer: drm::control::framebuffer::Handle, - #[allow(unused)] // Here for lifetime purpose... - buffer_object: gbm::BufferObject<()>, - pub backend: B::Buffer, } @@ -299,14 +302,32 @@ impl ScanoutBuffer { pub fn new(drm: &DrmState, backend: &B) -> Result> { let (width, height) = drm.mode.size(); - let buffer_object = drm.card.create_buffer_object_with_modifiers2::<()>( - width as _, - height as _, - drm.format.fourcc(), - drm.modifiers.iter().copied(), - gbm::BufferObjectFlags::SCANOUT | gbm::BufferObjectFlags::RENDERING, - )?; + log::trace!("Making buffer object"); + + let buffer_object: gbm::BufferObject<()> = if drm.modifiers.is_empty() { + // Linear Fallback. + log::warn!( + "no common modifier between DRM plane and {}; falling back to linear", + B::NAME + ); + + drm.card.create_buffer_object( + width as _, + height as _, + drm.format.fourcc(), + B::BUFFER_FLAGS, + )? + } else { + drm.card.create_buffer_object_with_modifiers2::<()>( + width as _, + height as _, + drm.format.fourcc(), + drm.modifiers.iter().copied(), + B::BUFFER_FLAGS, + )? + }; + log::trace!("Making framebuffer"); let framebuffer = Guard::new( drm.card .add_planar_framebuffer(&buffer_object, drm::control::FbCmd2Flags::MODIFIERS)?, @@ -317,10 +338,10 @@ impl ScanoutBuffer { Ok(Self { backend: backend - .new_buffer(drm, &buffer_object) + .new_buffer(drm, buffer_object) .map_err(ScanoutError::Backend)?, framebuffer: framebuffer.finish(), - buffer_object, + card: drm.card.clone(), }) } diff --git a/crates/compositor/src/backend/tty/vulkan/mod.rs b/crates/compositor/src/backend/tty/vulkan/mod.rs index 09a9528..6279c39 100644 --- a/crates/compositor/src/backend/tty/vulkan/mod.rs +++ b/crates/compositor/src/backend/tty/vulkan/mod.rs @@ -48,6 +48,9 @@ pub struct VulkanBuffer { cmd: vk::CommandBuffer, cmd_fence: vk::Fence, size: (u32, u32), + + #[expect(unused)] + bo: gbm::BufferObject<()>, } #[derive(Debug, Error)] @@ -78,9 +81,10 @@ impl From for VulkanError { } impl Backend for Vulkan { + const NAME: &str = "Vulkan"; type Error = VulkanError; - fn new(card: &Card) -> Result { + fn new(card: &Arc) -> Result { let instance = Instance::load()?; let device = Arc::new(instance.device_for(card)?); @@ -90,7 +94,7 @@ impl Backend for Vulkan { }) } - fn modifiers(&self, format: drmx::Format) -> HashSet { + fn modifiers(&self, format: drmx::Format, _: &HashSet) -> HashSet { let device = &self.device; // Get the number of available format modifiers. @@ -139,7 +143,7 @@ impl Backend for Vulkan { fn new_buffer( &self, drm: &DrmState, - bo: &gbm::BufferObject<()>, + bo: gbm::BufferObject<()>, ) -> Result { let cmd = { let [cmd] = unsafe { @@ -305,6 +309,7 @@ impl Backend for Vulkan { let (w, h) = drm.mode.size(); (w as u32, h as u32) }, + bo, }) } @@ -424,12 +429,19 @@ impl Drop for VulkanBuffer { impl BackendBuffer for VulkanBuffer { type Backend = Vulkan; - fn import_sync(&mut self, fd: OwnedFd) -> Result<(), ::Error> { + fn import_sync( + &mut self, + _: &mut Vulkan, + fd: OwnedFd, + ) -> Result<(), ::Error> { self.fence.import(fd)?; Ok(()) } - fn export_sync(&mut self) -> Result, ::Error> { + fn export_sync( + &mut self, + _: &mut Vulkan, + ) -> Result, ::Error> { self.semaphore.sync_fd().map_err(VulkanError::Vulkan) } } diff --git a/crates/compositor/src/backend/tty/vulkan/utils.rs b/crates/compositor/src/backend/tty/vulkan/utils.rs index 7fc3be6..1d6aac6 100644 --- a/crates/compositor/src/backend/tty/vulkan/utils.rs +++ b/crates/compositor/src/backend/tty/vulkan/utils.rs @@ -86,11 +86,13 @@ impl Drop for RawInstance { } } +#[cfg(debug_assertions)] struct DebugUtils { instance: ash::ext::debug_utils::Instance, messenger: ash::vk::DebugUtilsMessengerEXT, } +#[cfg(debug_assertions)] impl Drop for DebugUtils { fn drop(&mut self) { unsafe { diff --git a/crates/compositor/src/main.rs b/crates/compositor/src/main.rs index 56c46c4..5f3bff6 100644 --- a/crates/compositor/src/main.rs +++ b/crates/compositor/src/main.rs @@ -6,16 +6,19 @@ use std::{sync::Arc, task::Poll, time::Duration}; // use skia_safe::prelude::NativeAccess; -use smithay::reexports::{ - calloop, - drm::{self, Device, control::Device as _}, - gbm, +use smithay::{ + reexports::{ + calloop, + drm::{self, Device, control::Device as _}, + gbm, + }, + utils::DevPath, }; use crate::backend::tty::{ self, BufferedOutput, + cpu::Cpu, drmx::{self, DrmEventNotifier}, - vulkan::Vulkan, }; fn main() -> anyhow::Result<()> { @@ -28,6 +31,8 @@ fn main() -> anyhow::Result<()> { let card = Arc::new(gbm::Device::new(card)?); + log::trace!("Using card {:?}", card.dev_path()); + // Pick a suitable CRTC and mode for the active connector. let connector = resources .connectors() @@ -114,12 +119,12 @@ fn main() -> anyhow::Result<()> { // let mut skia = device.clone().skia_context()?; let mut output = - BufferedOutput::::new(card.clone(), crtc, plane, mode, connector.handle(), format)?; + BufferedOutput::new(card.clone(), crtc, plane, mode, connector.handle(), format)?; let mut event_loop = calloop::EventLoop::try_new()?; event_loop.handle().insert_source( DrmEventNotifier::new(card.clone()), - |event, _, output: &mut BufferedOutput| { + |event, _, output: &mut BufferedOutput| { if let drm::control::Event::PageFlip(page_flip_event) = event { log::trace!( "Flip event! {:?} @ {:?}", @@ -128,7 +133,7 @@ fn main() -> anyhow::Result<()> { ); if let Err(err) = output.flip() { - println!("Error during page flip: {err:?}"); + log::error!("Error during page flip: {err:?}"); } } }, @@ -151,10 +156,12 @@ fn main() -> anyhow::Result<()> { panic!("First buffer is still being scanned! Should not be the case!") }; + log::trace!("Before Flip page!"); let Poll::Ready(()) = output.flip()? else { panic!("Should not have a problem with queuing an initial flip.") }; - + + log::trace!("After Flip page!"); let now = std::time::Instant::now(); event_loop.run( Some(Duration::from_millis(5)), From 0416d1536f3477b25ef52d9131b17453dcefcdc0 Mon Sep 17 00:00:00 2001 From: Sammy99jsp Date: Sun, 2 Aug 2026 18:05:23 +0100 Subject: [PATCH 6/8] Add Skia support for Vulkan. --- crates/compositor/src/backend/tty/cpu/mod.rs | 45 +-- .../compositor/src/backend/tty/drmx/format.rs | 8 +- crates/compositor/src/backend/tty/mod.rs | 53 ++- .../compositor/src/backend/tty/vulkan/mod.rs | 346 +++++++++++------- .../src/backend/tty/vulkan/utils.rs | 6 +- crates/compositor/src/main.rs | 63 +++- 6 files changed, 325 insertions(+), 196 deletions(-) diff --git a/crates/compositor/src/backend/tty/cpu/mod.rs b/crates/compositor/src/backend/tty/cpu/mod.rs index b68227f..16967fb 100644 --- a/crates/compositor/src/backend/tty/cpu/mod.rs +++ b/crates/compositor/src/backend/tty/cpu/mod.rs @@ -66,7 +66,15 @@ impl Backend for Cpu { }) } - fn render(&mut self, buffer: &mut Self::Buffer, frame: usize) -> Result, Self::Error> { + fn render( + &mut self, + buffer: &mut Self::Buffer, + frame: usize, + mut callback: F, + ) -> Result, Self::Error> + where + F: for<'a> FnMut(&'a skia_safe::Canvas), + { // Wait on the imported sync file. if let Some(sync) = &buffer.fence { const TIMEOUT: i32 = rustix::io::Errno::TIME.raw_os_error(); @@ -96,37 +104,10 @@ impl Backend for Cpu { skia_safe::surfaces::wrap_pixels(&info, pxs.buffer_mut(), stride as usize, None) .expect("Valid skia surface"); - let canvas = surface.canvas(); - - canvas.clear(skia_safe::Color4f { - r: 1.0, - g: 0.0, - b: 1.0, - a: 1.0, - }); - - let f = skia_safe::FontMgr::new(); - let mut a = f.match_family("Arvo"); - let fface = a - .match_style(skia_safe::FontStyle::bold()) - .expect("Find font"); - let font = &skia_safe::Font::new(fface, 128.0); - let paint = skia_safe::Paint::new( - skia_safe::Color4f { - r: 0.0, - g: 0.0, - b: 0.0, - a: 1.0, - }, - None, - ); - canvas.draw_text_align( - format!("{frame}"), - (100.0, 100.0), - font, - &paint, - skia_safe::utils::text_utils::Align::Left, - ); + { + let canvas = surface.canvas(); + callback(canvas); + } Poll::Ready(()) }) diff --git a/crates/compositor/src/backend/tty/drmx/format.rs b/crates/compositor/src/backend/tty/drmx/format.rs index 11bdee0..4902694 100644 --- a/crates/compositor/src/backend/tty/drmx/format.rs +++ b/crates/compositor/src/backend/tty/drmx/format.rs @@ -13,10 +13,10 @@ impl Format { use skia_safe::ColorType as S; match self.0 { - G::Xrgb8888 => S::RGB888x, - G::Argb8888 => S::RGBA8888, - G::Xbgr8888 => S::BGRA8888, - G::Abgr8888 => S::BGRA8888, + G::Xrgb8888 => S::BGRA8888, + G::Argb8888 => S::BGRA8888, + G::Xbgr8888 => S::RGB888x, + G::Abgr8888 => S::RGBA8888, // HDR G::Xbgr2101010 => S::BGR101010x, diff --git a/crates/compositor/src/backend/tty/mod.rs b/crates/compositor/src/backend/tty/mod.rs index 14c8ca6..7e8d1c6 100644 --- a/crates/compositor/src/backend/tty/mod.rs +++ b/crates/compositor/src/backend/tty/mod.rs @@ -7,7 +7,7 @@ use std::{ use smithay::reexports::{ drm::{self, control::Device as _}, - gbm, + gbm, rustix, }; use crate::backend::tty::utils::Guard; @@ -39,7 +39,14 @@ pub trait Backend { bo: gbm::BufferObject<()>, ) -> Result; - fn render(&mut self, buffer: &mut Self::Buffer, frame: usize) -> Result, Self::Error>; + fn render( + &mut self, + buffer: &mut Self::Buffer, + frame: usize, + callback: F, + ) -> Result, Self::Error> + where + F: for<'a> FnMut(&'a skia_safe::Canvas); } pub trait BackendBuffer { @@ -104,6 +111,7 @@ pub enum ModesetError { pub struct BufferedOutput { pub frame: usize, + pub on_screen: Option, queue: VecDeque, pub drm: DrmState, pub backend: B, @@ -159,6 +167,7 @@ impl BufferedOutput { backend, buffers, frame: 0, + on_screen: None, queue: VecDeque::with_capacity(3), }; @@ -196,12 +205,17 @@ impl BufferedOutput { Ok(()) } - pub fn render(&mut self) -> Result, B::Error> { + pub fn render(&mut self, render_fn: F) -> Result, B::Error> + where + F: for<'a> FnMut(&'a skia_safe::Canvas), + { let frame_i = self.frame % 3; let buffer = &mut self.buffers[frame_i]; log::trace!("Backend render start."); - let poll = self.backend.render(&mut buffer.backend, self.frame)?; + let poll = self + .backend + .render(&mut buffer.backend, self.frame, render_fn)?; log::trace!("Backend render end."); if let Poll::Ready(()) = poll { self.frame += 1; @@ -214,7 +228,8 @@ impl BufferedOutput { pub fn flip(&mut self) -> Result, ScanoutError> { use drmx::atomic_req::*; - let Some(ScanoutRequest { frame_i }) = self.queue.pop_front() else { + let Some(ScanoutRequest { frame_i }) = self.queue.front().copied() else { + log::trace!("Waiting for rendered frames!"); return Ok(Poll::Pending); }; @@ -223,7 +238,8 @@ impl BufferedOutput { let mut out_fd: RawFd = -1; log::trace!("Modesetting request!"); - drm.cache + let res = drm + .cache .request() .set(drm.plane, FB_ID, Some(buffer.framebuffer)) .map_err(ModesetError::from)? @@ -238,7 +254,17 @@ impl BufferedOutput { .map_err(ModesetError::from)? .set(drm.crtc, OUT_FENCE_PTR, &raw mut out_fd) .map_err(ModesetError::from)? - .commit(AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK)?; + .commit(AtomicCommitFlags::PAGE_FLIP_EVENT | AtomicCommitFlags::NONBLOCK); + + match res { + Ok(()) => { + let _ = self.queue.pop_front(); + } + Err(err) if err.raw_os_error() == Some(rustix::io::Errno::BUSY.raw_os_error()) => { + return Ok(Poll::Pending); + } + Err(err) => return Err(ScanoutError::Io(err)), + } if out_fd == -1 { panic!("OUT_FENCE_PTR is invalid!"); @@ -246,15 +272,20 @@ impl BufferedOutput { let out_fd = unsafe { OwnedFd::from_raw_fd(out_fd) }; log::trace!("Backend import syncfile"); - buffer - .backend - .import_sync(&mut self.backend, out_fd) - .map_err(ScanoutError::Backend)?; + + // The out-fence releases the buffer that was on screen *before* this flip. + if let Some(prev) = self.on_screen.replace(frame_i) { + self.buffers[prev] + .backend + .import_sync(&mut self.backend, out_fd) + .map_err(ScanoutError::Backend)?; + } Ok(Poll::Ready(())) } } +#[derive(Debug, Clone, Copy)] pub struct ScanoutRequest { pub frame_i: usize, } diff --git a/crates/compositor/src/backend/tty/vulkan/mod.rs b/crates/compositor/src/backend/tty/vulkan/mod.rs index 6279c39..07ae1ba 100644 --- a/crates/compositor/src/backend/tty/vulkan/mod.rs +++ b/crates/compositor/src/backend/tty/vulkan/mod.rs @@ -6,25 +6,27 @@ use std::{ }; use smithay::reexports::{ - ash::{self, vk}, + ash::{ + self, + vk::{self, Handle}, + }, drm::buffer::Buffer, gbm, }; use thiserror::Error; -use crate::{ - backend::tty::{ - Backend, BackendBuffer, Card, DrmState, drmx, - utils::Guard, - vulkan::utils::{CommandPool, Device, ExportableSemaphore, ImportableFence, Instance}, - }, - colors::COLORS, +use crate::backend::tty::{ + Backend, BackendBuffer, Card, DrmState, drmx, + utils::Guard, + vulkan::utils::{CommandPool, Device, ExportableSemaphore, ImportableFence, Instance}, }; pub mod utils; pub struct Vulkan { device: Arc, + + #[allow(unused)] pool: Arc, } @@ -36,21 +38,28 @@ impl Drop for Vulkan { } } } +pub struct SkiaSurface { + surface: skia_safe::Surface, + + #[expect(unused)] // Needs to be dropped after the SkSurface + target: skia_safe::gpu::BackendRenderTarget, + ctx: skia_safe::gpu::DirectContext, +} pub struct VulkanBuffer { - device: Arc, - memory: vk::DeviceMemory, - image: vk::Image, + skia: SkiaSurface, image_view: vk::ImageView, + image: vk::Image, + + memory: vk::DeviceMemory, + fence: ImportableFence, semaphore: ExportableSemaphore, - pool: Arc, - cmd: vk::CommandBuffer, - cmd_fence: vk::Fence, - size: (u32, u32), #[expect(unused)] bo: gbm::BufferObject<()>, + + device: Arc, } #[derive(Debug, Error)] @@ -145,24 +154,6 @@ impl Backend for Vulkan { drm: &DrmState, bo: gbm::BufferObject<()>, ) -> Result { - let cmd = { - let [cmd] = unsafe { - self.device - .allocate_command_buffers( - &vk::CommandBufferAllocateInfo::default() - .command_buffer_count(1) - .command_pool(**self.pool) - .level(vk::CommandBufferLevel::PRIMARY), - )? - .try_into() - .expect("one allocated command buffer") - }; - - Guard::new(cmd, |cmd| { - unsafe { self.device.free_command_buffers(**self.pool, &[cmd]) }; - }) - }; - // Create vk::Image let image = { let plane_layouts = (0..bo.plane_count()) @@ -206,7 +197,7 @@ impl Backend for Vulkan { }; // Use the external memory via the fd provided by GBM - let (imported_fd, memory) = { + let (imported_fd, memory, alloc_size) = { // (A) Create a DMA file descriptor (owned by Vulkan) let fd = bo.fd()?; let fd_props = { @@ -255,6 +246,7 @@ impl Backend for Vulkan { Guard::new(memory, |memory| unsafe { self.device.free_memory(memory, None) }), + reqs.size, ) }; @@ -285,15 +277,10 @@ impl Backend for Vulkan { }) }; - let cmd_fence = unsafe { - Guard::new( - self.device.create_fence( - &vk::FenceCreateInfo::default().flags(vk::FenceCreateFlags::SIGNALED), - None, - )?, - |fence| self.device.destroy_fence(fence, None), - ) - }; + let skia = SkiaSurface::new(self, *image, *memory, alloc_size, drm.format, { + let (w, h) = drm.mode.size(); + (w as u32, h as u32) + })?; Ok(VulkanBuffer { device: self.device.clone(), @@ -302,113 +289,127 @@ impl Backend for Vulkan { image_view: image_view.finish(), semaphore: ExportableSemaphore::new(self.device.clone())?, fence: ImportableFence::new(self.device.clone())?, - pool: self.pool.clone(), - cmd: cmd.finish(), - cmd_fence: cmd_fence.finish(), - size: { - let (w, h) = drm.mode.size(); - (w as u32, h as u32) - }, bo, + skia, }) } - fn render( + fn render( &mut self, buffer: &mut Self::Buffer, frame: usize, - ) -> Result, Self::Error> { - let fence_signal = unsafe { - self.device.get_fence_status(*buffer.fence)? - && self.device.get_fence_status(buffer.cmd_fence)? - }; + mut callback: F, + ) -> Result, Self::Error> + where + F: for<'a> FnMut(&'a skia_safe::Canvas), + { + let fence_signal = unsafe { self.device.get_fence_status(*buffer.fence)? }; if !fence_signal { + log::trace!("Pending!"); return Ok(Poll::Pending); } unsafe { - self.device - .reset_fences(&[*buffer.fence, buffer.cmd_fence])?; - self.device - .reset_command_buffer(buffer.cmd, vk::CommandBufferResetFlags::default())?; + self.device.reset_fences(&[*buffer.fence])?; } { - unsafe { - self.device.begin_command_buffer( - buffer.cmd, - &vk::CommandBufferBeginInfo::default() - .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), - )? - } + let canvas = buffer.skia.surface.canvas(); - let barrier = vk::ImageMemoryBarrier2::default() - .src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE) - .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) - .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) - .old_layout(vk::ImageLayout::UNDEFINED) - .new_layout(vk::ImageLayout::GENERAL) - .image(buffer.image) - .subresource_range( - vk::ImageSubresourceRange::default() - .aspect_mask(vk::ImageAspectFlags::COLOR) - .level_count(1) - .layer_count(1), - ); + callback(canvas); + } - // UNDEFINED -> GENERAL layout - unsafe { - self.device.cmd_pipeline_barrier2( - buffer.cmd, - &vk::DependencyInfo::default() - .image_memory_barriers(core::slice::from_ref(&barrier)), - ) + { + buffer.semaphore = ExportableSemaphore::new(self.device.clone())?; + + let info = unsafe { + let mut semaphore = + skia_safe::gpu::backend_semaphores::make_vk(buffer.semaphore.as_raw() as _); + let mut info = skia_safe::gpu::FlushInfo::default(); + info.set_signal_semaphores(core::slice::from_mut(&mut semaphore)); + info }; - // Fill with some color. - { - let color = COLORS[frame % COLORS.len()]; - let attachment = vk::RenderingAttachmentInfo::default() - .image_layout(vk::ImageLayout::GENERAL) - .image_view(buffer.image_view) - // RED - .load_op(vk::AttachmentLoadOp::CLEAR) - .store_op(vk::AttachmentStoreOp::STORE) - .clear_value(vk::ClearValue { - color: vk::ClearColorValue { float32: color }, - }); - - let (width, height) = buffer.size; - let render = vk::RenderingInfo::default() - .render_area(vk::Rect2D { - offset: vk::Offset2D { x: 0, y: 0 }, - extent: vk::Extent2D { - width: width as _, - height: height as _, - }, - }) - .layer_count(1) - .color_attachments(core::slice::from_ref(&attachment)); - - unsafe { - self.device.cmd_begin_rendering(buffer.cmd, &render); - } - unsafe { self.device.cmd_end_rendering(buffer.cmd) }; - } - - unsafe { self.device.end_command_buffer(buffer.cmd)? } + buffer.skia.ctx.flush(&info); + buffer.skia.ctx.submit(Some(skia_safe::gpu::SyncCpu::Yes)); } - unsafe { - self.device.queue_submit( - self.device.queue, - &[vk::SubmitInfo::default() - .command_buffers(&[buffer.cmd]) - .signal_semaphores(buffer.semaphore.as_slice())], - buffer.cmd_fence, - )?; - } + // { + // unsafe { + // self.device.begin_command_buffer( + // buffer.cmd, + // &vk::CommandBufferBeginInfo::default() + // .flags(vk::CommandBufferUsageFlags::ONE_TIME_SUBMIT), + // )? + // } + + // let barrier = vk::ImageMemoryBarrier2::default() + // .src_stage_mask(vk::PipelineStageFlags2::TOP_OF_PIPE) + // .dst_stage_mask(vk::PipelineStageFlags2::COLOR_ATTACHMENT_OUTPUT) + // .dst_access_mask(vk::AccessFlags2::COLOR_ATTACHMENT_WRITE) + // .old_layout(vk::ImageLayout::UNDEFINED) + // .new_layout(vk::ImageLayout::GENERAL) + // .image(buffer.image) + // .subresource_range( + // vk::ImageSubresourceRange::default() + // .aspect_mask(vk::ImageAspectFlags::COLOR) + // .level_count(1) + // .layer_count(1), + // ); + + // // UNDEFINED -> GENERAL layout + // unsafe { + // self.device.cmd_pipeline_barrier2( + // buffer.cmd, + // &vk::DependencyInfo::default() + // .image_memory_barriers(core::slice::from_ref(&barrier)), + // ) + // }; + + // // Fill with some color. + // { + // let color = COLORS[frame % COLORS.len()]; + // let attachment = vk::RenderingAttachmentInfo::default() + // .image_layout(vk::ImageLayout::GENERAL) + // .image_view(buffer.image_view) + // // RED + // .load_op(vk::AttachmentLoadOp::CLEAR) + // .store_op(vk::AttachmentStoreOp::STORE) + // .clear_value(vk::ClearValue { + // color: vk::ClearColorValue { float32: color }, + // }); + + // let (width, height) = buffer.size; + // let render = vk::RenderingInfo::default() + // .render_area(vk::Rect2D { + // offset: vk::Offset2D { x: 0, y: 0 }, + // extent: vk::Extent2D { + // width: width as _, + // height: height as _, + // }, + // }) + // .layer_count(1) + // .color_attachments(core::slice::from_ref(&attachment)); + + // unsafe { + // self.device.cmd_begin_rendering(buffer.cmd, &render); + // } + // unsafe { self.device.cmd_end_rendering(buffer.cmd) }; + // } + + // unsafe { self.device.end_command_buffer(buffer.cmd)? } + // } + + // unsafe { + // self.device.queue_submit( + // self.device.queue, + // &[vk::SubmitInfo::default() + // .command_buffers(&[buffer.cmd]) + // .signal_semaphores(buffer.semaphore.as_slice())], + // buffer.cmd_fence, + // )?; + // } Ok(Poll::Ready(())) } @@ -417,8 +418,6 @@ impl Backend for Vulkan { impl Drop for VulkanBuffer { fn drop(&mut self) { unsafe { - self.device.destroy_fence(self.cmd_fence, None); - self.device.free_command_buffers(**self.pool, &[self.cmd]); self.device.destroy_image_view(self.image_view, None); self.device.destroy_image(self.image, None); self.device.free_memory(self.memory, None); @@ -445,3 +444,90 @@ impl BackendBuffer for VulkanBuffer { self.semaphore.sync_fd().map_err(VulkanError::Vulkan) } } + +impl SkiaSurface { + pub fn new( + backend: &Vulkan, + image: vk::Image, + memory: vk::DeviceMemory, + alloc_size: u64, + format: drmx::Format, + size: (u32, u32), + ) -> Result { + let get_proc = |of| unsafe { + let fn_ptr = match of { + skia_safe::gpu::vk::GetProcOf::Instance(instance, name) => backend + .device + .instance + .entry + .get_instance_proc_addr(vk::Instance::from_raw(instance as _), name) + .map(|ptr| ptr as *const core::ffi::c_void), + skia_safe::gpu::vk::GetProcOf::Device(device, name) => backend + .device + .instance + .get_device_proc_addr(vk::Device::from_raw(device as _), name) + .map(|ptr| ptr as *const core::ffi::c_void), + }; + + fn_ptr.expect("Find Vulkan function ptr for Skia") + }; + let mut ctx = unsafe { + let ctx = skia_safe::gpu::vk::BackendContext::new_builder( + backend.device.instance.handle().as_raw() as _, + backend.device.physical.as_raw() as _, + backend.device.handle().as_raw() as _, + ( + backend.device.queue.as_raw() as _, + backend.device.queue_family as _, + ), + &get_proc, + Some(skia_safe::gpu::vk::Version::new(1, 3, 0)), + ) + .build(); + skia_safe::gpu::direct_contexts::make_vulkan(&ctx, None) + .ok_or("Cannot initialize direct Skia-Vulkan context".to_string())? + }; + + let target = unsafe { + let mut info = skia_safe::gpu::vk::ImageInfo::new( + image.as_raw() as _, + skia_safe::gpu::vk::Alloc::from_device_memory( + memory.as_raw() as _, + 0, + alloc_size, + skia_safe::gpu::vk::AllocFlag::empty(), + ), + skia_safe::gpu::vk::ImageTiling::DRM_FORMAT_MODIFIER_EXT, + skia_safe::gpu::vk::ImageLayout::UNDEFINED, + std::mem::transmute::(format.into()), + 1, + backend.device.queue_family, + None, + Some(skia_safe::gpu::Protected::No), + None, + ); + + info.image_usage_flags = (vk::ImageUsageFlags::COLOR_ATTACHMENT + | vk::ImageUsageFlags::TRANSFER_SRC + | vk::ImageUsageFlags::TRANSFER_DST) + .as_raw(); + + skia_safe::gpu::backend_render_targets::make_vk((size.0 as _, size.1 as _), &info) + }; + let surface = skia_safe::gpu::surfaces::wrap_backend_render_target( + &mut ctx, + &target, + skia_safe::gpu::SurfaceOrigin::TopLeft, + format.skia(), + None, + None, + ) + .expect("SkSurface from Vulkan stuff"); + + Ok(Self { + surface, + target, + ctx, + }) + } +} diff --git a/crates/compositor/src/backend/tty/vulkan/utils.rs b/crates/compositor/src/backend/tty/vulkan/utils.rs index 1d6aac6..421cf3c 100644 --- a/crates/compositor/src/backend/tty/vulkan/utils.rs +++ b/crates/compositor/src/backend/tty/vulkan/utils.rs @@ -53,7 +53,7 @@ const APP_VERSION: u32 = { vk::make_api_version(0, major, minor, patch) }; -const PREFERRED_VULKAN_VERSION: u32 = vk::make_api_version(0, 1, 3, 0); +pub const MAX_VULKAN_VERSION: u32 = vk::make_api_version(0, 1, 3, 0); const MINIMUM_VULKAN_VERSION: u32 = vk::make_api_version(0, 1, 1, 0); pub struct Instance { @@ -185,7 +185,7 @@ impl Instance { let app_info = vk::ApplicationInfo::default() .application_name(APP_NAME) .application_version(APP_VERSION) - .api_version(PREFERRED_VULKAN_VERSION); + .api_version(MAX_VULKAN_VERSION); let create_info = vk::InstanceCreateInfo::default() .application_info(&app_info) @@ -251,7 +251,7 @@ impl Instance { }) .ok_or_else(|| VulkanError::Other("your graphics device does not support Vulkan, or you are using a multi-GPU setup which is not supported".to_string()))?; - if api_version < PREFERRED_VULKAN_VERSION { + if api_version < MAX_VULKAN_VERSION { log::warn!( "graphics device only supports Vulkan {}.{}, but 1.3 is required", vk::api_version_major(api_version), diff --git a/crates/compositor/src/main.rs b/crates/compositor/src/main.rs index 5f3bff6..99c5a2d 100644 --- a/crates/compositor/src/main.rs +++ b/crates/compositor/src/main.rs @@ -19,6 +19,7 @@ use crate::backend::tty::{ self, BufferedOutput, cpu::Cpu, drmx::{self, DrmEventNotifier}, + vulkan::Vulkan, }; fn main() -> anyhow::Result<()> { @@ -114,17 +115,15 @@ fn main() -> anyhow::Result<()> { .find_map(|(p, ty, props)| (ty == drm::control::PlaneType::Primary).then_some((p, props))) .ok_or(anyhow::anyhow!("cannot find suitable primary plane"))?; - let format = drmx::Format(gbm::Format::Xrgb8888); - - // let mut skia = device.clone().skia_context()?; + let format = drmx::Format(gbm::Format::Argb8888); let mut output = - BufferedOutput::new(card.clone(), crtc, plane, mode, connector.handle(), format)?; + BufferedOutput::::new(card.clone(), crtc, plane, mode, connector.handle(), format)?; let mut event_loop = calloop::EventLoop::try_new()?; event_loop.handle().insert_source( DrmEventNotifier::new(card.clone()), - |event, _, output: &mut BufferedOutput| { + |event, _, output: &mut BufferedOutput<_>| { if let drm::control::Event::PageFlip(page_flip_event) = event { log::trace!( "Flip event! {:?} @ {:?}", @@ -144,7 +143,7 @@ fn main() -> anyhow::Result<()> { event_loop .handle() .insert_source( - calloop::timer::Timer::from_duration(Duration::from_secs(10)), + calloop::timer::Timer::from_duration(Duration::from_secs(2)), move |_, _, _| { signal.stop(); calloop::timer::TimeoutAction::Drop @@ -152,21 +151,53 @@ fn main() -> anyhow::Result<()> { ) .map_err(|a| a.error)?; - let Poll::Ready(()) = output.render()? else { - panic!("First buffer is still being scanned! Should not be the case!") + let mut frame = 0usize; + let mgr = skia_safe::FontMgr::new(); + let mut family = mgr.match_family("Inter"); + let typeface = family + .match_style(skia_safe::FontStyle::normal()) + .expect("Iter"); + let font = skia_safe::Font::from_typeface(typeface, 128.0); + let paint = skia_safe::Paint::new(skia_safe::Color4f::new(0.0, 0.0, 0.0, 1.0), None); + + let mut callback = move |canvas: &skia_safe::Canvas| { + canvas.clear(skia_safe::Color4f { + r: 1.0, + g: 1.0, + b: 1.0, + a: 1.0, + }); + + canvas.draw_text_align( + format!("{frame}"), + (100., 100.), + &font, + &paint, + skia_safe::utils::text_utils::Align::Left, + ); + + frame += 1; }; - log::trace!("Before Flip page!"); - let Poll::Ready(()) = output.flip()? else { - panic!("Should not have a problem with queuing an initial flip.") - }; - - log::trace!("After Flip page!"); + if let Err(err) = output.render(&mut callback) { + log::error!("Error during initial render: {err:?}"); + } + if let Err(err) = output.render(&mut callback) { + log::error!("Error during second render: {err:?}"); + } + if let Err(err) = output.flip() { + log::error!("Error during initial page flip: {err:?}"); + } + + log::trace!("Starting render loop"); let now = std::time::Instant::now(); event_loop.run( - Some(Duration::from_millis(5)), + Some(Duration::from_micros(5_000)), &mut output, - |output| match output.render().expect("No error within render loop") { + |output| match output + .render(&mut callback) + .expect("No error within render loop") + { Poll::Ready(()) => (), Poll::Pending => (), }, From a28e30d8e8f1ff05fd6cc4d01c12e52aa97125cf Mon Sep 17 00:00:00 2001 From: Sammy99jsp Date: Sun, 2 Aug 2026 18:25:51 +0100 Subject: [PATCH 7/8] Add backtrace to errors. --- .../src/backend/tty/drmx/atomic_req.rs | 6 ++- crates/compositor/src/backend/tty/mod.rs | 30 +++++++++++--- .../compositor/src/backend/tty/vulkan/mod.rs | 41 +++++++++++++++---- .../src/backend/tty/vulkan/utils.rs | 19 ++++----- crates/compositor/src/main.rs | 2 + 5 files changed, 72 insertions(+), 26 deletions(-) diff --git a/crates/compositor/src/backend/tty/drmx/atomic_req.rs b/crates/compositor/src/backend/tty/drmx/atomic_req.rs index 3d8ef02..41db91e 100644 --- a/crates/compositor/src/backend/tty/drmx/atomic_req.rs +++ b/crates/compositor/src/backend/tty/drmx/atomic_req.rs @@ -41,7 +41,11 @@ impl<'a> Drop for AtomicRequest<'a> { #[derive(Debug, thiserror::Error)] pub enum AtomicReqError { #[error(transparent)] - Io(#[from] std::io::Error), + Io( + #[from] + #[backtrace] + std::io::Error, + ), #[error("{0}")] Other(String), diff --git a/crates/compositor/src/backend/tty/mod.rs b/crates/compositor/src/backend/tty/mod.rs index 7e8d1c6..7f73099 100644 --- a/crates/compositor/src/backend/tty/mod.rs +++ b/crates/compositor/src/backend/tty/mod.rs @@ -71,10 +71,18 @@ const LEN: usize = 3; #[derive(thiserror::Error)] pub enum ScanoutError { #[error(transparent)] - Io(#[from] std::io::Error), + Io( + #[from] + #[backtrace] + std::io::Error, + ), #[error(transparent)] - DrmFormat(#[from] drmx::format_modifier::DrmFormatParseError), + DrmFormat( + #[from] + #[backtrace] + drmx::format_modifier::DrmFormatParseError, + ), #[error("cannot get supported formats")] CannotGetDrmFormats, @@ -83,7 +91,11 @@ pub enum ScanoutError { Backend(B::Error), #[error(transparent)] - Modeset(#[from] ModesetError), + Modeset( + #[backtrace] + #[from] + ModesetError, + ), } impl std::fmt::Debug for ScanoutError @@ -104,9 +116,17 @@ where #[derive(Debug, thiserror::Error)] pub enum ModesetError { #[error(transparent)] - Io(#[from] std::io::Error), + Io( + #[from] + #[backtrace] + std::io::Error, + ), #[error(transparent)] - AtomicReq(#[from] drmx::atomic_req::AtomicReqError), + AtomicReq( + #[from] + #[backtrace] + drmx::atomic_req::AtomicReqError, + ), } pub struct BufferedOutput { diff --git a/crates/compositor/src/backend/tty/vulkan/mod.rs b/crates/compositor/src/backend/tty/vulkan/mod.rs index 07ae1ba..847b08b 100644 --- a/crates/compositor/src/backend/tty/vulkan/mod.rs +++ b/crates/compositor/src/backend/tty/vulkan/mod.rs @@ -1,4 +1,5 @@ use std::{ + backtrace::Backtrace, collections::HashSet, os::{fd::AsRawFd, unix::prelude::OwnedFd}, sync::Arc, @@ -65,27 +66,49 @@ pub struct VulkanBuffer { #[derive(Debug, Error)] pub enum VulkanError { #[error(transparent)] - Loading(#[from] ash::LoadingError), + Loading( + #[from] + #[backtrace] + ash::LoadingError, + ), #[error(transparent)] - Vulkan(#[from] ash::vk::Result), + Vulkan( + #[from] + #[backtrace] + ash::vk::Result, + ), #[error(transparent)] - Io(#[from] std::io::Error), + Io( + #[backtrace] + #[from] + std::io::Error, + ), #[error("no supported DMA memory type can be imported into Vulkan")] UnsupportedDmaMemory, #[error(transparent)] - Fd(#[from] gbm::InvalidFdError), - - #[error("{0}")] - Other(String), + Fd( + #[from] + #[backtrace] + gbm::InvalidFdError, + ), + + #[error("{reason}")] + Other { + reason: String, + backtrace: Backtrace, + }, } impl From for VulkanError { - fn from(value: String) -> Self { - Self::Other(value) + fn from(reason: String) -> Self { + Self::Other { + reason, + backtrace: Backtrace::capture(), + } } } diff --git a/crates/compositor/src/backend/tty/vulkan/utils.rs b/crates/compositor/src/backend/tty/vulkan/utils.rs index 421cf3c..1b05a6f 100644 --- a/crates/compositor/src/backend/tty/vulkan/utils.rs +++ b/crates/compositor/src/backend/tty/vulkan/utils.rs @@ -128,13 +128,12 @@ impl Instance { log::trace!("Vulkan Version: {version:?}"); if raw_version < MINIMUM_VULKAN_VERSION { - return Err(VulkanError::Other( + return Err( format!("Your driver's Vulkan version ({0}.{1}.{2}) is below the 1.1.x minimum.", version.0, version.1, version.2 - ) - )); + ).into()); } #[cfg(debug_assertions)] @@ -146,9 +145,7 @@ impl Instance { REQUIRED_INSTANCE_LAYERS, ) .map_err(|_| { - VulkanError::Other( - "Missing layer: VK_LAYER_KHRONOS_validation".to_string() - ) + VulkanError::from("Missing layer: VK_LAYER_KHRONOS_validation".to_string()) })? }; let extensions = { @@ -161,7 +158,7 @@ impl Instance { REQUIRED_INSTANCE_EXTENSIONS, ) .map_err(|_| { - VulkanError::Other("Missing layer: VK_EXT_debug_utils".to_string()) + VulkanError::from("Missing layer: VK_EXT_debug_utils".to_string()) })? }; @@ -249,7 +246,7 @@ impl Instance { found.then_some((api_version, device)) }) - .ok_or_else(|| VulkanError::Other("your graphics device does not support Vulkan, or you are using a multi-GPU setup which is not supported".to_string()))?; + .ok_or_else(|| VulkanError::from("your graphics device does not support Vulkan, or you are using a multi-GPU setup which is not supported".to_string()))?; if api_version < MAX_VULKAN_VERSION { log::warn!( @@ -266,7 +263,7 @@ impl Instance { .iter() .position(|queue| queue.queue_flags.contains(vk::QueueFlags::GRAPHICS)) else { - return Err(VulkanError::Other( + return Err( VulkanError::from( "Cannot find GRAPHICS queue family on the (physical) graphics device.".to_string() )); }; @@ -278,7 +275,7 @@ impl Instance { ) { Ok(exts) => exts, Err(missing) => { - return Err(VulkanError::Other( + return Err( VulkanError::from( format!("your graphics device is missing the following Vulkan device extensions: {missing:?}") )); } @@ -297,7 +294,7 @@ impl Instance { || check13.synchronization2 != vk::TRUE || check13.dynamic_rendering != vk::TRUE { - return Err(VulkanError::Other( + return Err( VulkanError::from( "device lacks required Vulkan 1.2 and/or 1.3 features: timeline_semaphore, synchronization2, dynamic_rendering".to_string() )); } diff --git a/crates/compositor/src/main.rs b/crates/compositor/src/main.rs index 99c5a2d..8ba60d5 100644 --- a/crates/compositor/src/main.rs +++ b/crates/compositor/src/main.rs @@ -1,3 +1,5 @@ +#![feature(error_generic_member_access)] + pub mod backend; pub mod colors; pub mod config; From 012181e8c18cf7efc713015494ab913fe4f70b9d Mon Sep 17 00:00:00 2001 From: Sammy99jsp Date: Sun, 2 Aug 2026 18:48:14 +0100 Subject: [PATCH 8/8] Make format selection by attempting many formats. --- crates/compositor/src/backend/tty/cpu/mod.rs | 23 +- .../compositor/src/backend/tty/drmx/format.rs | 332 +++++++++++++++++- crates/compositor/src/backend/tty/drmx/mod.rs | 4 +- crates/compositor/src/backend/tty/mod.rs | 120 ++++--- .../compositor/src/backend/tty/vulkan/mod.rs | 22 +- .../src/backend/tty/vulkan/utils.rs | 26 +- crates/compositor/src/main.rs | 31 +- 7 files changed, 463 insertions(+), 95 deletions(-) diff --git a/crates/compositor/src/backend/tty/cpu/mod.rs b/crates/compositor/src/backend/tty/cpu/mod.rs index 16967fb..cd00c43 100644 --- a/crates/compositor/src/backend/tty/cpu/mod.rs +++ b/crates/compositor/src/backend/tty/cpu/mod.rs @@ -13,7 +13,10 @@ use smithay::reexports::{ gbm, rustix, }; -use crate::backend::tty::{Backend, BackendBuffer, drmx::Format}; +use crate::backend::tty::{ + Backend, BackendBuffer, + drmx::{self, Format}, +}; use super::Card; @@ -23,7 +26,6 @@ pub struct Cpu { pub struct CpuBuffer { bo: gbm::BufferObject<()>, - format: Format, fence: Option, } @@ -31,11 +33,8 @@ const TIMEOUT_NSEC: i64 = 500_000; // 0.5ms impl Backend for Cpu { const NAME: &str = "CPU"; - const BUFFER_FLAGS: gbm::BufferObjectFlags = const { - gbm::BufferObjectFlags::SCANOUT - .union(gbm::BufferObjectFlags::LINEAR) - .union(gbm::BufferObjectFlags::WRITE) - }; + const BUFFER_FLAGS: gbm::BufferObjectFlags = + const { gbm::BufferObjectFlags::SCANOUT.union(gbm::BufferObjectFlags::LINEAR) }; type Error = std::io::Error; fn new(card: &Arc) -> Result @@ -59,11 +58,7 @@ impl Backend for Cpu { drm: &super::DrmState, bo: gbm::BufferObject<()>, ) -> Result { - Ok(CpuBuffer { - bo, - fence: None, - format: drm.format, - }) + Ok(CpuBuffer { bo, fence: None }) } fn render( @@ -89,12 +84,12 @@ impl Backend for Cpu { } let (width, height) = (buffer.bo.width(), buffer.bo.height()); - let format = buffer.format; + let format = drmx::Format(buffer.bo.format()); buffer.bo.map_mut(0, 0, width, height, |pxs| { let info = skia_safe::ImageInfo::new( (width as _, height as _), - format.skia(), + format.try_into().unwrap(), skia_safe::AlphaType::Premul, None, ); diff --git a/crates/compositor/src/backend/tty/drmx/format.rs b/crates/compositor/src/backend/tty/drmx/format.rs index 4902694..bbdf0a1 100644 --- a/crates/compositor/src/backend/tty/drmx/format.rs +++ b/crates/compositor/src/backend/tty/drmx/format.rs @@ -8,24 +8,334 @@ impl Format { self.0 } - pub fn skia(&self) -> skia_safe::ColorType { + pub fn iter() -> impl Iterator { + use gbm::Format as G; + + #[rustfmt::skip] + const FORMATS: &[gbm::Format] = &[G::C8,G::R8,G::R16,G::Rg88,G::Gr88,G::Rg1616,G::Gr1616,G::Bgr233,G::Rgb332,G::Xrgb4444,G::Xbgr4444,G::Rgbx4444,G::Bgrx4444,G::Argb4444,G::Abgr4444,G::Rgba4444,G::Bgra4444,G::Xrgb1555,G::Xbgr1555,G::Rgbx5551,G::Bgrx5551,G::Argb1555,G::Abgr1555,G::Rgba5551,G::Bgra5551,G::Rgb565,G::Bgr565,G::Rgb888,G::Bgr888,G::Xrgb8888,G::Xbgr8888,G::Rgbx8888,G::Bgrx8888,G::Argb8888,G::Abgr8888,G::Rgba8888,G::Bgra8888,G::Xrgb2101010,G::Xbgr2101010,G::Rgbx1010102,G::Bgrx1010102,G::Argb2101010,G::Abgr2101010,G::Rgba1010102,G::Bgra1010102,G::Yuyv,G::Yvyu,G::Uyvy,G::Vyuy,G::Ayuv,G::Xrgb8888_a8,G::Nv12,G::Nv21,G::Nv16,G::Nv61,G::Nv24,G::Nv42,G::Yuv410,G::Yvu410,G::Yuv411,G::Yvu411,G::Yuv420,G::Yvu420,G::Yuv422,G::Yvu422,G::Yuv444,G::Yvu444,G::Abgr16161616f,G::Argb16161616f,G::Axbxgxrx106106106106,G::Bgr565_a8,G::Bgr888_a8,G::Bgrx8888_a8,G::Big_endian,G::Nv15,G::P010,G::P012,G::P016,G::P210,G::Q401,G::Q410,G::Rgb565_a8,G::Rgb888_a8,G::Rgbx8888_a8,G::Vuy101010,G::Vuy888,G::X0l0,G::X0l2,G::Xbgr16161616f,G::Xbgr8888_a8,G::Xrgb16161616f,G::Xvyu12_16161616,G::Xvyu16161616,G::Xvyu2101010,G::Xyuv8888,G::Y0l0,G::Y0l2,G::Y210,G::Y212,G::Y216,G::Y410,G::Y412,G::Y416,G::Yuv420_10bit,G::Yuv420_8bit]; + FORMATS.iter().copied().map(Self) + } + + pub fn skia(&self) -> Option { + TryInto::try_into(*self).ok() + } + + pub fn rank(&self) -> usize { + match self.0 { + gbm::Format::Argb2101010 => 0, + gbm::Format::Abgr2101010 => 0, + gbm::Format::Bgra1010102 => 0, + gbm::Format::Rgba1010102 => 0, + + gbm::Format::Bgrx1010102 => 1, + gbm::Format::Rgbx1010102 => 1, + gbm::Format::Xbgr2101010 => 1, + gbm::Format::Xrgb2101010 => 1, + + gbm::Format::Rgba8888 => 2, + gbm::Format::Argb8888 => 2, + gbm::Format::Bgra8888 => 2, + gbm::Format::Bgrx8888 => 2, + gbm::Format::Abgr8888 => 2, + gbm::Format::Rgbx8888 => 2, + gbm::Format::Xbgr8888 => 2, + gbm::Format::Xrgb8888 => 2, + + gbm::Format::Abgr16161616f => 3, + gbm::Format::Argb16161616f => 3, + gbm::Format::Xrgb16161616f => 3, + gbm::Format::Xbgr16161616f => 3, + + gbm::Format::Abgr1555 => usize::MAX, + gbm::Format::Abgr4444 => usize::MAX, + gbm::Format::Argb1555 => usize::MAX, + gbm::Format::Argb4444 => usize::MAX, + gbm::Format::Axbxgxrx106106106106 => usize::MAX, + gbm::Format::Ayuv => usize::MAX, + gbm::Format::Bgr233 => usize::MAX, + gbm::Format::Bgr565 => usize::MAX, + gbm::Format::Bgr565_a8 => usize::MAX, + gbm::Format::Bgr888 => usize::MAX, + gbm::Format::Bgr888_a8 => usize::MAX, + gbm::Format::Bgra4444 => usize::MAX, + gbm::Format::Bgra5551 => usize::MAX, + gbm::Format::Bgrx4444 => usize::MAX, + gbm::Format::Bgrx5551 => usize::MAX, + gbm::Format::Bgrx8888_a8 => usize::MAX, + gbm::Format::Big_endian => usize::MAX, + gbm::Format::C8 => usize::MAX, + gbm::Format::Gr1616 => usize::MAX, + gbm::Format::Gr88 => usize::MAX, + gbm::Format::Nv12 => usize::MAX, + gbm::Format::Nv15 => usize::MAX, + gbm::Format::Nv16 => usize::MAX, + gbm::Format::Nv21 => usize::MAX, + gbm::Format::Nv24 => usize::MAX, + gbm::Format::Nv42 => usize::MAX, + gbm::Format::Nv61 => usize::MAX, + gbm::Format::P010 => usize::MAX, + gbm::Format::P012 => usize::MAX, + gbm::Format::P016 => usize::MAX, + gbm::Format::P210 => usize::MAX, + gbm::Format::Q401 => usize::MAX, + gbm::Format::Q410 => usize::MAX, + gbm::Format::R16 => usize::MAX, + gbm::Format::R8 => usize::MAX, + gbm::Format::Rg1616 => usize::MAX, + gbm::Format::Rg88 => usize::MAX, + gbm::Format::Rgb332 => usize::MAX, + gbm::Format::Rgb565 => usize::MAX, + gbm::Format::Rgb565_a8 => usize::MAX, + gbm::Format::Rgb888 => usize::MAX, + gbm::Format::Rgb888_a8 => usize::MAX, + gbm::Format::Rgba4444 => usize::MAX, + gbm::Format::Rgba5551 => usize::MAX, + gbm::Format::Rgbx4444 => usize::MAX, + gbm::Format::Rgbx5551 => usize::MAX, + gbm::Format::Rgbx8888_a8 => usize::MAX, + + gbm::Format::Uyvy => usize::MAX, + gbm::Format::Vuy101010 => usize::MAX, + gbm::Format::Vuy888 => usize::MAX, + gbm::Format::Vyuy => usize::MAX, + gbm::Format::X0l0 => usize::MAX, + gbm::Format::X0l2 => usize::MAX, + gbm::Format::Xbgr1555 => usize::MAX, + gbm::Format::Xbgr4444 => usize::MAX, + gbm::Format::Xbgr8888_a8 => usize::MAX, + gbm::Format::Xrgb1555 => usize::MAX, + gbm::Format::Xrgb4444 => usize::MAX, + gbm::Format::Xrgb8888_a8 => usize::MAX, + gbm::Format::Xvyu12_16161616 => usize::MAX, + gbm::Format::Xvyu16161616 => usize::MAX, + gbm::Format::Xvyu2101010 => usize::MAX, + gbm::Format::Xyuv8888 => usize::MAX, + gbm::Format::Y0l0 => usize::MAX, + gbm::Format::Y0l2 => usize::MAX, + gbm::Format::Y210 => usize::MAX, + gbm::Format::Y212 => usize::MAX, + gbm::Format::Y216 => usize::MAX, + gbm::Format::Y410 => usize::MAX, + gbm::Format::Y412 => usize::MAX, + gbm::Format::Y416 => usize::MAX, + gbm::Format::Yuv410 => usize::MAX, + gbm::Format::Yuv411 => usize::MAX, + gbm::Format::Yuv420 => usize::MAX, + gbm::Format::Yuv420_10bit => usize::MAX, + gbm::Format::Yuv420_8bit => usize::MAX, + gbm::Format::Yuv422 => usize::MAX, + gbm::Format::Yuv444 => usize::MAX, + gbm::Format::Yuyv => usize::MAX, + gbm::Format::Yvu410 => usize::MAX, + gbm::Format::Yvu411 => usize::MAX, + gbm::Format::Yvu420 => usize::MAX, + gbm::Format::Yvu422 => usize::MAX, + gbm::Format::Yvu444 => usize::MAX, + gbm::Format::Yvyu => usize::MAX, + } + } +} + +impl TryInto for Format { + type Error = (); + fn try_into(self) -> Result { use gbm::Format as G; use skia_safe::ColorType as S; match self.0 { - G::Xrgb8888 => S::BGRA8888, - G::Argb8888 => S::BGRA8888, - G::Xbgr8888 => S::RGB888x, - G::Abgr8888 => S::RGBA8888, + // Color index + G::C8 => Err(()), + + // 8 bpp red + G::R8 => Ok(S::R8UNorm), + + // 16 bpp red + G::R16 => Ok(S::R16UNorm), + + // 16 bpp RG + G::Rg88 => Err(()), + G::Gr88 => Ok(S::R8G8UNorm), + + // 32 bpp RG + G::Rg1616 => Err(()), + G::Gr1616 => Ok(S::R16G16UNorm), + + // 8 bpp RGB + G::Bgr233 => Err(()), + G::Rgb332 => Err(()), - // HDR - G::Xbgr2101010 => S::BGR101010x, - G::Abgr2101010 => S::BGRA1010102, - G::Abgr16161616f => S::RGBAF16, + // 16 bpp RGB + G::Xrgb4444 => Err(()), + G::Xbgr4444 => Err(()), + G::Rgbx4444 => Ok(S::ARGB4444), + G::Bgrx4444 => Err(()), - G::Rgb565 => S::RGB565, + G::Argb4444 => Err(()), + G::Abgr4444 => Err(()), + G::Rgba4444 => Ok(S::ARGB4444), + G::Bgra4444 => Err(()), + + G::Xrgb1555 => Err(()), + G::Xbgr1555 => Err(()), + G::Rgbx5551 => Err(()), + G::Bgrx5551 => Err(()), + + G::Argb1555 => Err(()), + G::Abgr1555 => Err(()), + G::Rgba5551 => Err(()), + G::Bgra5551 => Err(()), + + G::Rgb565 => Ok(S::RGB565), + G::Bgr565 => Err(()), + + // 24 bpp RGB + G::Rgb888 => Err(()), + G::Bgr888 => Err(()), + + /* 32 bpp RGB */ + G::Xrgb8888 => Ok(S::BGRA8888), + G::Xbgr8888 => Ok(S::RGB888x), + G::Rgbx8888 => Err(()), + G::Bgrx8888 => Err(()), + + G::Argb8888 => Ok(S::BGRA8888), + G::Abgr8888 => Ok(S::RGBA8888), + G::Rgba8888 => Err(()), + G::Bgra8888 => Err(()), + + G::Xrgb2101010 => Ok(S::BGR101010x), + G::Xbgr2101010 => Ok(S::RGB101010x), + G::Rgbx1010102 => Err(()), + G::Bgrx1010102 => Err(()), + + G::Argb2101010 => Ok(S::BGRA1010102), + G::Abgr2101010 => Ok(S::RGBA1010102), + G::Rgba1010102 => Err(()), + G::Bgra1010102 => Err(()), + + /* packed YCbCr */ + G::Yuyv => Err(()), + G::Yvyu => Err(()), + G::Uyvy => Err(()), + G::Vyuy => Err(()), + + G::Ayuv => Err(()), + + /* + * 2 plane RGB + A + * index 0 = RGB plane, same format as the corresponding non _A8 format has + * index 1 = A plane, [7:0] A + * index 0 = Y plane, [7:0] Y + * index 1 = Cr:Cb plane, [15:0] Cr:Cb little endian + * or + * index 1 = Cb:Cr plane, [15:0] Cb:Cr little endian + */ + G::Xrgb8888_a8 => Err(()), + G::Nv12 => Err(()), + G::Nv21 => Err(()), + G::Nv16 => Err(()), + G::Nv61 => Err(()), + G::Nv24 => Err(()), + G::Nv42 => Err(()), + + /* + * 3 plane YCbCr + * index 0: Y plane, [7:0] Y + * index 1: Cb plane, [7:0] Cb + * index 2: Cr plane, [7:0] Cr + * or + * index 1: Cr plane, [7:0] Cr + * index 2: Cb plane, [7:0] Cb + */ + G::Yuv410 => Err(()), + G::Yvu410 => Err(()), + G::Yuv411 => Err(()), + G::Yvu411 => Err(()), + G::Yuv420 => Err(()), + G::Yvu420 => Err(()), + G::Yuv422 => Err(()), + G::Yvu422 => Err(()), + G::Yuv444 => Err(()), + G::Yvu444 => Err(()), + + G::Abgr16161616f => Err(()), + G::Argb16161616f => Err(()), + G::Axbxgxrx106106106106 => Err(()), + G::Bgr565_a8 => Err(()), + G::Bgr888_a8 => Err(()), + G::Bgrx8888_a8 => Err(()), + G::Big_endian => Err(()), + G::Nv15 => Err(()), + G::P010 => Err(()), + G::P012 => Err(()), + G::P016 => Err(()), + G::P210 => Err(()), + G::Q401 => Err(()), + G::Q410 => Err(()), + G::Rgb565_a8 => Err(()), + G::Rgb888_a8 => Err(()), + G::Rgbx8888_a8 => Err(()), + G::Vuy101010 => Err(()), + G::Vuy888 => Err(()), + G::X0l0 => Err(()), + G::X0l2 => Err(()), + G::Xbgr16161616f => Err(()), + G::Xbgr8888_a8 => Err(()), + G::Xrgb16161616f => Err(()), + G::Xvyu12_16161616 => Err(()), + G::Xvyu16161616 => Err(()), + G::Xvyu2101010 => Err(()), + G::Xyuv8888 => Err(()), + G::Y0l0 => Err(()), + G::Y0l2 => Err(()), + G::Y210 => Err(()), + G::Y212 => Err(()), + G::Y216 => Err(()), + G::Y410 => Err(()), + G::Y412 => Err(()), + G::Y416 => Err(()), + G::Yuv420_10bit => Err(()), + G::Yuv420_8bit => Err(()), + } + } +} + +impl TryFrom for Format { + type Error = (); + fn try_from(value: skia_safe::ColorType) -> Result { + use gbm::Format as G; + use skia_safe::ColorType as S; - _ => panic!("unsupported color format"), + match value { + S::Unknown => Err(()), + S::Alpha8 => Err(()), + S::RGB565 => Ok(Format(G::Rgb565)), + S::ARGB4444 => Ok(Format(G::Rgba4444)), + S::RGBA8888 => Ok(Format(G::Abgr8888)), + S::RGB888x => Ok(Format(G::Xbgr8888)), + S::BGRA8888 => Ok(Format(G::Argb8888)), + S::RGBA1010102 => Ok(Format(G::Abgr2101010)), + S::BGRA1010102 => Ok(Format(G::Argb2101010)), + S::RGB101010x => Ok(Format(G::Xbgr2101010)), + S::BGR101010x => Ok(Format(G::Xrgb2101010)), + S::BGR101010xXR => Ok(Format(G::Xrgb2101010)), + S::BGRA10101010XR => Err(()), + S::RGBA10x6 => Err(()), + S::Gray8 => Err(()), + S::RGBAF16Norm => Err(()), + S::RGBAF16 => Err(()), + S::RGBF16F16F16x => Err(()), + S::RGBAF32 => Err(()), + S::R8G8UNorm => Ok(Format(G::Gr88)), + S::A16Float => Err(()), + S::R16Float => Err(()), + S::R16G16Float => Err(()), + S::A16UNorm => Err(()), + S::R16UNorm => Ok(Format(G::R16)), + S::R16G16UNorm => Ok(Format(G::Gr1616)), + S::R16G16B16A16UNorm => Err(()), + S::SRGBA8888 => Err(()), + S::R8UNorm => Ok(Format(G::R8)), } } } diff --git a/crates/compositor/src/backend/tty/drmx/mod.rs b/crates/compositor/src/backend/tty/drmx/mod.rs index 2436f9c..a9c10c0 100644 --- a/crates/compositor/src/backend/tty/drmx/mod.rs +++ b/crates/compositor/src/backend/tty/drmx/mod.rs @@ -86,10 +86,10 @@ impl Card { match cards.len() { 0 if is_overridden => { return Err(anyhow::anyhow!( - "device specified by NADVA_DRM_DEVICE is not usable" + "device specified by NADVA_DRM_DEVICE is not usable: either no connected connectors, and/or no (atomic + universal plane capability)" )); } - 0 => return Err(anyhow::anyhow!("No suitable graphics devices!")), + 0 => return Err(anyhow::anyhow!("No suitable graphics devices found! either no connected connectors, and/or no (atomic + universal plane capability)")), 1 => return Ok(cards.pop().unwrap()), _ => (), } diff --git a/crates/compositor/src/backend/tty/mod.rs b/crates/compositor/src/backend/tty/mod.rs index 7f73099..ca497d3 100644 --- a/crates/compositor/src/backend/tty/mod.rs +++ b/crates/compositor/src/backend/tty/mod.rs @@ -5,9 +5,12 @@ use std::{ task::Poll, }; -use smithay::reexports::{ - drm::{self, control::Device as _}, - gbm, rustix, +use smithay::{ + reexports::{ + drm::{self, control::Device as _}, + gbm, rustix, + }, + utils::DevPath, }; use crate::backend::tty::utils::Guard; @@ -87,6 +90,9 @@ pub enum ScanoutError { #[error("cannot get supported formats")] CannotGetDrmFormats, + #[error("could not allocate buffers in the following 'supported' formats: {0:?}")] + NoSuitableFormats(Vec), + #[error(transparent)] Backend(B::Error), @@ -109,6 +115,9 @@ where Self::CannotGetDrmFormats => write!(f, "CannotGetDrmFormats"), Self::Backend(arg0) => f.debug_tuple("Backend").field(arg0).finish(), Self::Modeset(arg0) => f.debug_tuple("Modeset").field(arg0).finish(), + Self::NoSuitableFormats(arg0) => { + f.debug_tuple("NoSuitableFormats").field(arg0).finish() + } } } } @@ -145,42 +154,56 @@ impl BufferedOutput { plane: drm::control::plane::Handle, mode: drm::control::Mode, connector: drm::control::connector::Handle, - format: drmx::Format, + formats: Vec, ) -> Result> { let backend = B::new(&card).map_err(ScanoutError::Backend)?; - // Format modifiers... - let modifiers = { - // DRM - let drm_modifiers = - drm_modifiers::(&card, format.fourcc(), &card.get_properties(plane)?)?; - - // Provided by backend... - let backend_modifiers = backend.modifiers(format, &drm_modifiers); - - // Intersect the DRM- and Vulkan-provided format modifiers - - backend_modifiers - .intersection(&drm_modifiers) - .copied() - .map(gbm::Modifier::from) - .collect::>() - }; - - let drm = DrmState { - previous_state: card.get_crtc(crtc)?, - cache: drmx::atomic_req::AtomicRequestCache::new(card.clone()), - card, - crtc, - plane, - mode, - connector, - format, - modifiers, - }; - - let [b1, b2, b3] = core::array::from_fn(|_| ScanoutBuffer::::new(&drm, &backend)); - let buffers = [b1?, b2?, b3?]; + let (drm, buffers) = formats + .iter() + .copied() + .find_map(|format| { + // Format modifiers... + let modifiers = { + // DRM + let drm_modifiers = drm_modifiers::( + &card, + format.fourcc(), + &card.get_properties(plane).ok()?, + ) + .ok()?; + + // Provided by backend... + let backend_modifiers = backend.modifiers(format, &drm_modifiers); + + // Intersect the DRM- and Vulkan-provided format modifiers + + backend_modifiers + .intersection(&drm_modifiers) + .copied() + .map(gbm::Modifier::from) + .chain([gbm::Modifier::Invalid]) + .collect::>() + }; + + let drm = DrmState { + previous_state: card.get_crtc(crtc).expect("should be able to get the crtc"), + cache: drmx::atomic_req::AtomicRequestCache::new(card.clone()), + card: card.clone(), + crtc, + plane, + mode, + connector, + modifiers, + }; + let buffers = [ + ScanoutBuffer::::new(&drm, format, &backend).ok()?, + ScanoutBuffer::::new(&drm, format, &backend).ok()?, + ScanoutBuffer::::new(&drm, format, &backend).ok()?, + ]; + + Some((drm, buffers)) + }) + .ok_or_else(|| ScanoutError::NoSuitableFormats(formats))?; let mut output = Self { drm, @@ -317,7 +340,6 @@ pub struct DrmState { pub plane: drm::control::plane::Handle, pub mode: drm::control::Mode, pub connector: drm::control::connector::Handle, - pub format: drmx::Format, pub modifiers: Vec, pub previous_state: drm::control::crtc::Info, } @@ -350,38 +372,46 @@ impl Drop for ScanoutBuffer { } impl ScanoutBuffer { - pub fn new(drm: &DrmState, backend: &B) -> Result> { + pub fn new(drm: &DrmState, format: drmx::Format, backend: &B) -> Result> { let (width, height) = drm.mode.size(); - log::trace!("Making buffer object"); + log::trace!( + "Making buffer object with available modifiers: {:?}", + drm.modifiers + ); - let buffer_object: gbm::BufferObject<()> = if drm.modifiers.is_empty() { + let buffer_object: gbm::BufferObject<()> = if drm.modifiers.len() == 1 { // Linear Fallback. log::warn!( - "no common modifier between DRM plane and {}; falling back to linear", + "no common modifier between DRM plane and {}; continuing without modifiers.", B::NAME ); drm.card.create_buffer_object( width as _, height as _, - drm.format.fourcc(), + format.fourcc(), B::BUFFER_FLAGS, )? } else { drm.card.create_buffer_object_with_modifiers2::<()>( width as _, height as _, - drm.format.fourcc(), + format.fourcc(), drm.modifiers.iter().copied(), B::BUFFER_FLAGS, )? }; + let flags = if buffer_object.modifier() == gbm::Modifier::Invalid { + drm::control::FbCmd2Flags::empty() + } else { + drm::control::FbCmd2Flags::MODIFIERS + }; + log::trace!("Making framebuffer"); let framebuffer = Guard::new( - drm.card - .add_planar_framebuffer(&buffer_object, drm::control::FbCmd2Flags::MODIFIERS)?, + drm.card.add_planar_framebuffer(&buffer_object, flags)?, |fb| { let _ = drm.card.destroy_framebuffer(fb); }, diff --git a/crates/compositor/src/backend/tty/vulkan/mod.rs b/crates/compositor/src/backend/tty/vulkan/mod.rs index 847b08b..54ced4c 100644 --- a/crates/compositor/src/backend/tty/vulkan/mod.rs +++ b/crates/compositor/src/backend/tty/vulkan/mod.rs @@ -177,6 +177,7 @@ impl Backend for Vulkan { drm: &DrmState, bo: gbm::BufferObject<()>, ) -> Result { + let format: vk::Format = drmx::Format(bo.format()).into(); // Create vk::Image let image = { let plane_layouts = (0..bo.plane_count()) @@ -198,7 +199,7 @@ impl Backend for Vulkan { let (width, height) = bo.size(); let info = vk::ImageCreateInfo::default() .image_type(vk::ImageType::TYPE_2D) - .format(drm.format.into()) + .format(format) .extent(vk::Extent3D { width, height, @@ -281,7 +282,7 @@ impl Backend for Vulkan { let image_view = { let info = vk::ImageViewCreateInfo::default() .components(vk::ComponentMapping::default()) - .format(drm.format.into()) + .format(format) .image(*image) .view_type(vk::ImageViewType::TYPE_2D) .subresource_range( @@ -300,10 +301,17 @@ impl Backend for Vulkan { }) }; - let skia = SkiaSurface::new(self, *image, *memory, alloc_size, drm.format, { - let (w, h) = drm.mode.size(); - (w as u32, h as u32) - })?; + let skia = SkiaSurface::new( + self, + *image, + *memory, + alloc_size, + drmx::Format(bo.format()), + { + let (w, h) = drm.mode.size(); + (w as u32, h as u32) + }, + )?; Ok(VulkanBuffer { device: self.device.clone(), @@ -541,7 +549,7 @@ impl SkiaSurface { &mut ctx, &target, skia_safe::gpu::SurfaceOrigin::TopLeft, - format.skia(), + format.try_into().unwrap(), None, None, ) diff --git a/crates/compositor/src/backend/tty/vulkan/utils.rs b/crates/compositor/src/backend/tty/vulkan/utils.rs index 1b05a6f..4f9a536 100644 --- a/crates/compositor/src/backend/tty/vulkan/utils.rs +++ b/crates/compositor/src/backend/tty/vulkan/utils.rs @@ -598,21 +598,23 @@ unsafe extern "system" fn callback( impl From for vk::Format { fn from(value: drmx::Format) -> Self { use smithay::reexports::gbm::Format as G; - use vk::Format as F; + use vk::Format as V; match value.0 { - G::Xrgb8888 => F::B8G8R8A8_UNORM, - G::Argb8888 => F::B8G8R8A8_UNORM, - G::Xbgr8888 => F::R8G8B8A8_UNORM, - G::Abgr8888 => F::R8G8B8A8_UNORM, + G::Xrgb8888 => V::B8G8R8A8_UNORM, + G::Argb8888 => V::B8G8R8A8_UNORM, + G::Xbgr8888 => V::R8G8B8A8_UNORM, + G::Abgr8888 => V::R8G8B8A8_UNORM, // HDR - G::Xbgr2101010 => F::A2B10G10R10_UNORM_PACK32, - G::Abgr2101010 => F::A2B10G10R10_UNORM_PACK32, - - G::Rgb565 => F::R5G6B5_UNORM_PACK16, - - G::Abgr16161616f => F::R16G16B16A16_SFLOAT, - + G::Xbgr2101010 => V::A2B10G10R10_UNORM_PACK32, + G::Abgr2101010 => V::A2B10G10R10_UNORM_PACK32, + G::Abgr16161616f => V::R16G16B16A16_SFLOAT, + + G::Xrgb2101010 => V::A2R10G10B10_UNORM_PACK32, + G::Argb2101010 => V::A2R10G10B10_UNORM_PACK32, + G::Argb16161616f => V::R16G16B16A16_SFLOAT, + + G::Rgb565 => V::R5G6B5_UNORM_PACK16, _ => panic!("unsupported color format"), } } diff --git a/crates/compositor/src/main.rs b/crates/compositor/src/main.rs index 8ba60d5..f70838e 100644 --- a/crates/compositor/src/main.rs +++ b/crates/compositor/src/main.rs @@ -10,6 +10,7 @@ use std::{sync::Arc, task::Poll, time::Duration}; use smithay::{ reexports::{ + ash::vk, calloop, drm::{self, Device, control::Device as _}, gbm, @@ -18,7 +19,7 @@ use smithay::{ }; use crate::backend::tty::{ - self, BufferedOutput, + self, Backend, BufferedOutput, cpu::Cpu, drmx::{self, DrmEventNotifier}, vulkan::Vulkan, @@ -117,10 +118,32 @@ fn main() -> anyhow::Result<()> { .find_map(|(p, ty, props)| (ty == drm::control::PlaneType::Primary).then_some((p, props))) .ok_or(anyhow::anyhow!("cannot find suitable primary plane"))?; - let format = drmx::Format(gbm::Format::Argb8888); + // gbm::Format:: + let mut compatible_formats = drmx::Format::iter() + .filter(|format| format.skia().is_some()) + .filter(|&format| card.is_format_supported(format.fourcc(), Vulkan::BUFFER_FLAGS)) + .collect::>(); - let mut output = - BufferedOutput::::new(card.clone(), crtc, plane, mode, connector.handle(), format)?; + compatible_formats.sort_unstable_by_key(|a| a.rank()); + + log::trace!("Compatible Formats: {compatible_formats:?}"); + let format = drmx::Format(gbm::Format::Abgr2101010); + + log::trace!( + "Chose format : DRM {:?}; VK {:?}; SKIA: {:?}", + format.fourcc(), + vk::Format::from(format), + format.skia() + ); + + let mut output = BufferedOutput::::new( + card.clone(), + crtc, + plane, + mode, + connector.handle(), + compatible_formats, + )?; let mut event_loop = calloop::EventLoop::try_new()?; event_loop.handle().insert_source(