-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcontent.json
1 lines (1 loc) · 612 KB
/
content.json
1
{"meta":{"title":"小竹's blog","subtitle":"永远不要停止思考","description":"小竹的博客","author":"小竹","url":"https://blog.justforlxz.com","root":"/"},"pages":[{"title":"Comment Policy guidelines 评论政策","date":"2018-07-09T01:59:09.000Z","updated":"2024-04-15T05:09:55.101Z","comments":true,"path":"guidelines.html","permalink":"https://blog.justforlxz.com/guidelines.html","excerpt":"","text":"很简单,平和交流。"},{"title":"archives","date":"2024-12-31T07:36:27.000Z","updated":"2024-12-31T07:36:34.300Z","comments":true,"path":"archives/index.html","permalink":"https://blog.justforlxz.com/archives/index.html","excerpt":"archives","text":""},{"title":"Untitled","date":"2024-04-15T05:09:55.102Z","updated":"2024-04-15T05:09:55.102Z","comments":true,"path":"test.html","permalink":"https://blog.justforlxz.com/test.html","excerpt":"Untitled","text":"Page Title"},{"title":"圣人忘情,最下不及于情,情之所钟,正在我辈。","date":"2017-08-18T02:07:58.000Z","updated":"2024-12-31T07:37:52.812Z","comments":true,"path":"about/index.html","permalink":"https://blog.justforlxz.com/about/index.html","excerpt":"圣人忘情,最下不及于情,情之所钟,正在我辈。","text":"冒险经历 2016年9月 – 至今 武汉统信科技(原武汉深之度科技有限公司),负责开发deepin linux的桌面环境和相关软件。 已习得技能 Qt/C++ Go TypeScript Rust Python3 业余爱好 topbar 在DDE桌面上实现类似于苹果的顶栏,支持系统托盘 视频壁纸 在DDE桌面上实现置低窗口播放视频 已归档 konachan爬虫 满足个人壁纸爱好的爬虫,使用Go编写 我在哪里 Mail [email protected] Telegram https://t.me/justforlxz GPG Fingerprint fingerprint A182 F28F A78F 7060 1453 137B CF82 E295 9732 1B63 Download Public Key"},{"title":"我的装备","date":"2024-12-31T09:55:28.000Z","updated":"2024-12-31T09:56:48.697Z","comments":true,"path":"equipment/index.html","permalink":"https://blog.justforlxz.com/equipment/index.html","excerpt":"我的装备","text":"–"},{"title":"简历","date":"2020-02-20T07:03:54.000Z","updated":"2024-04-15T05:09:55.102Z","comments":true,"path":"private/cv.html","permalink":"https://blog.justforlxz.com/private/cv.html","excerpt":"简历","text":"个人信息 张丁元 / 男 / 1996 工作年限: 6年 技术博客: https://blog.justforlxz.com Github: https://github.com/justforlxz/ 联系方式 手机: 17607195053 邮箱: justforlxz@gmail.com 工作经历统信科技有限公司 (原 武汉深之度科技有限公司) (2016/09 —— 至今)武汉地区技术委员会 (2020/07 – 至今)职位描述: 为了提高研发中心技术能力,保持研发中心的技术创造性,保证技术决策的合理性,满足产品发展趋势,提供前瞻性的解决方案。 工作性质: 参与制定研发中心的年度技术规划 参加相关项目的关键技术论证,技术评审 对关键的项目,产品提供提供技术调研、评审,提供技术咨询服务和改进意见 对公司内部技术人才培养提供建议 对技术专利提供规划,对技术资源进行沉淀 武汉地区开发部桌面组技术小组 (2020/05 – 至今)职位描述: 负责部门整体技术的架构,新技术领域的探索,技术性知识输出,为部门各个方面提供技术后盾。 工作性质: 制定开发框架,优化项目代码,编写调研报告等。 deepin-recovery (2020/01 – 2020/05)项目简介: 提供Linux Deepin发行版分区级别的备份还原方案。 项目描述: 项目包含恢复出厂设置、手动备份、手动还原等功能。 工作性质: 项目负责人,负责架构设计和功能开发,项目功能横跨三个不同的项目,通过约定配置文件充当接口约束,并通过封装Task类来解析任务数据,简化封装流程。现在带领3人团队开发和维护备份还原项目,涵盖dde-control-center、live-filesystem项目。 deepin-installer (2018/08 – 2020/05)项目简介: 提供Linux Deepin发行版的安装功能。 项目描述: 项目包含用户信息、时区、组件化列表、分区、语言选择等功能。 工作性质: 项目负责人,期间维护项目代码和新功能开发。对项目进行过多次重构,使用智能指针解决内存泄漏问题、使用继承的方式合并不同类型的分区代码、使用接口的方式实现了新的页面框架,简化页面调度和疏通页面流程,并优化了OEM定制时配置文件的合并方案。现在带领5人团队开发LVM分区方案、无人值守安装和字符界面安装等。 dde-session-ui (2017/03 – 2018/07)地址: https://github.com/linuxdeepin/dde-session-ui 项目简介: DDE桌面环境的用户会话相关的界面,例如锁屏,登录界面等。 项目描述: 项目包含用户锁屏及用户登录程序,还有和会话提醒相关的窗管选择器、内存不足对话框、用户OSD显示等程序。 工作性质: 项目负责人,负责维护代码,期间对项目进行了两次重构,第一次架构重构,对项目代码进行解耦,消除了大量重复代码,扩展了可维护性。第二次重构是拆分无关二进制,并使用CMake构建系统,统一公司内部的编译系统。 dde-dock (2017/05 – 2019/05)地址: https://github.com/linuxdeepin/dde-dock 项目简介: DDE桌面环境的任务栏,为桌面环境提供当前执行程序的切换及托盘功能。 项目描述: 项目包含启动器图标、应用管理、托盘管理和插件系统功能。 工作性质: 项目负责人,负责维护和优化,通过调整框架加载顺序和重写插件调度实现插件的快速加载,不会造成主界面的卡顿。通过特殊的操作方式优化启动动画,使程序启动时画面更加友好。 dde-launcher (2017/05 – 2019/05)地址: https://github.com/linuxdeepin/dde-launcher 项目简介: DDE桌面环境的应用启动器,为桌面环境提供应用列表。 项目描述: 项目包含全屏模式、mini模式、分类管理功能。 工作性质: 项目负责人,负责维护和优化项目,通过增加缓存的方式来提升程序启动和运行速度,在国产处理器平台上进行过内存占用优化。 dde-control-center (2016/09 – 2019/6)地址: https://github.com/linuxdeepin/dde-control-center 项目简介: DDE桌面环境的控制中心,负责管理系统及个人用户的设置。 项目描述: 项目包含账户管理、显示管理、键盘及鼠标管理、用户个性化等功能。 工作性质: 负责项目的架构,开发与维护。进行组员代码 review,性能分析,架构调整等方面的工作 开源项目及作品deepin-topbar地址: https://github.com/justforlxz/deepin-topbar 项目描述: 使用Qt和C++在dde桌面实现类似于MacOS的顶栏,扩展了系统操作,可以用来访问网络,控制系统音量。通过调用X11的接口实现对屏幕显示区域的划分,通过设置窗口属性实现阴影和本地在桌面的层叠关系。 项目采用模块化开发,每个功能都是独立的,通过定义接口类来实现框架和模块之间的访问与控制。 deepin-dreamscene地址 https://github.com/justforlxz/deepin-dreamscene 项目描述 使用Qt和C++在dde桌面实现了windows下Wallpaper Engine软件的动态视频壁纸软件。通过调用X11的接口将程序置于最底层,并调用mpv库实现视频播放。 技能清单以下是我熟练使用的技能: 操作系统: Linux 编程语言: C++/Qt、TypeScript 开发工具: QtCreator/Visual Studio Code/NeoVim 构建系统: CMake/QMake 管理系统: Git 自我总结我这几年主攻方向是C++/Qt,项目中使用标准库和Qt提供的智能指针来避免内存泄漏,使用迭代器避免出现内存异常,使用设计模式来完善项目架构,通过QFuture和std::thread避免阻塞UI线程。在公司负责多个项目团队的技术架构及技术指导,并对团队的技术栈进行定期培训,对代码进行审核和优化。现在就任武汉开发部桌面组技术小组。 致谢感谢您花时间阅读我的简历, 期待能有机会和您共事."},{"title":"links","date":"2024-12-31T07:24:24.000Z","updated":"2024-12-31T07:24:46.637Z","comments":true,"path":"links/index.html","permalink":"https://blog.justforlxz.com/links/index.html","excerpt":"links","text":""}],"posts":[{"title":"使用 distrobox 和 nix 加速 UOS 开发","slug":"use-docker-and-distrobx-and-nix-to-develop-uos","date":"2025-01-02T08:18:00.000Z","updated":"2025-01-02T08:32:45.387Z","comments":true,"path":"2025/01/02/use-docker-and-distrobx-and-nix-to-develop-uos/","permalink":"https://blog.justforlxz.com/2025/01/02/use-docker-and-distrobx-and-nix-to-develop-uos/","excerpt":"","text":"UOS 这仓库有多鬼畜,就不用我多说了,开发的时候每次都要重装一个不同版本的系统也挺麻烦,比如 1060、1063、1070、1070u1。。。 所以我就想到,我可以用构建用的 rootfs 加上仓库,整出来一个基础环境,然后通过 distrobox 来启动,这样随时都可以快速准备一个环境。 平时我用的开发工具就 neovim 和 vscode 居多,所以再配合上 nix 把 neovim 和一些 UOS 缺少的工具都装上,想想都是美滋滋。 说干就干! 构建 Docker 镜像由于 UOS 有多个不同架构的支持,肯定不能绑死到一个平台上,我使用 docker buildx 来构建多架构镜像。rootfs 我就不公开了,直接上 Dockerfile。 FROM --platform=$TARGETPLATFORM scratchARG TARGETARCHARG UOS_VERSIONADD buster-${TARGETARCH}-1050update4.tgz /RUN echo "deb [trusted=yes] http://pools.uniontech.com/desktop-professional ${UOS_VERSION:-eagle/1070} main contrib non-free" > /etc/apt/sources.listRUN apt-get update && apt-get install -y \\ apt-utils \\ apt-transport-https \\ ca-certificates \\ curl \\ gnupg \\ lsb-release \\ && apt-get dist-upgrade -y \\ && apt-get clean \\ && rm -rf /var/lib/apt/lists/*CMD [ "bash" ] 这里为了方便我构建,我将仓库的名称使用变量控制了。 docker buildx build --platform=linux/amd64 -t linuxdeepin/1063:base --build-arg UOS_VERSION=eagle/1063 . --load 只需要控制 label 和 仓库名称,就能快速的创建出一个可用的 docker 镜像。使用 --load 参数构建完以后直接导入镜像,可以在 docker images 中看到。 创建 Distrobox 环境现在有个可用的 docker 镜像,就可以使用 distrobox 创建环境了。 distrobox create --image linuxdeepin/1063:base --name 1063 --volume /nix:/nix:rw --additional-flags "--pids-limit -1" 安装 nix 的过程请参考上一篇文章,这里是把 nix 挂载到环境里了。 运行以后等待一会儿,就看到创建成功了。 Creating '1063' using image linuxdeepin/1063:base [ OK ]Distrobox '1063' successfully created.To enter, run:distrobox enter 1063Successfully copied 2.05kB to /tmp/1063.os-release 提醒我们用 distrobox enter 1063 进入环境。 运行运行的话没啥问题,只是需要设置一下用户密码,不知道是不是 distrobox 改变策略了,以前应该是首次进入的时候就提醒设置密码,现在必须手动先运行一下 passwd,不然 sudo 之类的命令是不能用的。 ❯ distrobox enter 1063Starting container... [ OK ]Installing basic packages... [ OK ]Setting up devpts mounts... [ OK ]Setting up read-only mounts... [ OK ]Setting up read-write mounts... [ OK ]Setting up host's sockets integration... [ OK ]Integrating host's themes, icons, fonts... [ OK ]Setting up distrobox profile... [ OK ]Setting up sudo... [ OK ]Setting up user groups... [ OK ]Setting up user's group list... [ OK ]Adding user... [ OK ]Ensuring user's access... [ OK ]Container Setup Complete!","categories":[{"name":"技术分享","slug":"技术分享","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"}],"tags":[{"name":"uos","slug":"uos","permalink":"https://blog.justforlxz.com/tags/uos/"}]},{"title":"使用 VK-GL-CTS 对 wayland 执行一致性测试","slug":"vk-gl-cts-test-wayland","date":"2024-12-27T04:00:00.000Z","updated":"2024-12-31T08:44:41.609Z","comments":true,"path":"2024/12/27/vk-gl-cts-test-wayland/","permalink":"https://blog.justforlxz.com/2024/12/27/vk-gl-cts-test-wayland/","excerpt":"","text":"😭 华为那边使用 deqp-vk 作为测试项,结果 kwin_wayland 有个测试没通过,沟通了一星期才知道是公开的测试套件,但是在 uos 1070 上构建遇到了点问题,我把坑记录一下。 uos 1070 里的 gcc 太旧了,需要安装 clang-13,然后更新软链。还需要安装 libc++abi-13,不能碰任何 gcc 的东西。 sudo apt install libwayland-dev wayland-protocols cmake ninja-build clang-13 libc++1-13 libc++-13-dev libc++abi1-13 libc++abi-13-dev sudo update-alternatives --install /usr/bin/clang clang /usr/bin/clang-13 100sudo update-alternatives --install /usr/bin/clang++ clang++ /usr/bin/clang++-13 100 拉取代码 https://github.com/KhronosGroup/VK-GL-CTS/ git clone https://github.com/KhronosGroup/VK-GL-CTS/ 拉完代码以后,还需要执行脚本把依赖项都拉去了。 python3 external/fetch_sources.py 同时还需要在系统里安装 python3-lxml。 更新完所有依赖后,我只需要 deqp-vk 这一个 target,而 khronosGroup 给 VK-GL-CTS 提供了 SELECTED_BUILD_TARGETS 变量。 cmake -B build -GNinja \\ -DSELECTED_BUILD_TARGETS=deqp-vk \\ -DCMAKE_BUILD_TYPE=Debug \\ -DCMAKE_CXX_STANDARD=17 \\ -DCMAKE_CXX_STANDARD_REQUIRED=ON \\ -DCMAKE_CXX_EXTENSIONS=OFF \\ -DCMAKE_CXX_FLAGS="-stdlib=libc++" \\ -DCMAKE_EXE_LINKER_FLAGS="-lstdc++fs" \\ -DCMAKE_SHARED_LINKER_FLAGS="-lstdc++fs" \\ -DCMAKE_C_COMPILER=clang \\ -DCMAKE_CXX_COMPILER=clang++ 就可以完成构建系统的准备。之后就可以正常构建了,大概需要编译 1331 个单元, cmake --build build --target deqp-vk 等构建完成后,使用华为提供的测试命令执行测试。 ./build/external/vulkancts/modules/vulkan/deqp-vk --deqp-case=dEQP-VK.wsi.wayland.swapchain.render.10swapchains 运行前最好使用 vulkaninfo 测试一下 vulkan 信息。如果没有成功运行,安装一下 mesa-vulkan-drivers。 sudo apt install mesa-vulkan-drivers 会看到通过了 case。 ❯ ./build/external/vulkancts/modules/vulkan/deqp-vk --deqp-case=dEQP-VK.wsi.wayland.swapchain.render.10swapchainsWriting test log into TestResults.qpadEQP Core vulkan-cts-1.4.1.0-93-g7655439d7333848a59d3114ee6227a26259b2b4b (0x7655439d) starting.. target implementation = 'Default'B%<-------- Process name: deqp-vk. Logging ended at: Fri Dec 27 15:52:34 2024Test case 'dEQP-VK.wsi.wayland.swapchain.render.10swapchains'..?%<-------- Process name: deqp-vk. Logging ended at: Fri Dec 27 15:52:37 2024 Pass (Rendering tests succeeded)<-------- Process name: deqp-vk. Logging ended at: Fri Dec 27 15:52:37 2024DONE!Test run totals: Passed: 1/1 (100.0%) Failed: 0/1 (0.0%) Not supported: 0/1 (0.0%) Warnings: 0/1 (0.0%) Waived: 0/1 (0.0%)","categories":[{"name":"技术分享","slug":"技术分享","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"}],"tags":[{"name":"UOS, Vulkan, 图形学, 开发","slug":"UOS-Vulkan-图形学-开发","permalink":"https://blog.justforlxz.com/tags/UOS-Vulkan-%E5%9B%BE%E5%BD%A2%E5%AD%A6-%E5%BC%80%E5%8F%91/"}]},{"title":"Nix home-manager 使用笔记","slug":"nix-home-manager","date":"2024-12-25T04:00:00.000Z","updated":"2024-12-31T08:44:38.277Z","comments":true,"path":"2024/12/25/nix-home-manager/","permalink":"https://blog.justforlxz.com/2024/12/25/nix-home-manager/","excerpt":"每次都忘了要运行啥,记下来","text":"😵💫 每次都忘了要运行啥 安装 nixsh <(curl -L https://nixos.org/nix/install) --no-daemon 配置创建文件 ~/.config/nix/nix.conf,写入以下内容: experimental-features = nix-command flakes 创建目录 ~/.config/home-manager/,需要两个文件,我的配置里使用 flake 作为辅助,所以先创建 flake.nix 文件,写入以下内容: { description = "Home Manager configuration for Me QwQ"; inputs = { nixpkgs.url = "github:nixos/nixpkgs/nixos-unstable"; home-manager = { url = "github:nix-community/home-manager"; inputs.nixpkgs.follows = "nixpkgs"; }; flake-utils.url = "github:numtide/flake-utils"; neovim-nightly-overlay = { url = "github:nix-community/neovim-nightly-overlay"; inputs.nixpkgs.follows = "nixpkgs"; }; }; outputs = { self, flake-utils, nixpkgs, home-manager, neovim-nightly-overlay, ... }@inputs: flake-utils.lib.eachDefaultSystemPassThrough (system: let pkgs = nixpkgs.legacyPackages.${system}; in { homeConfigurations.lxz = home-manager.lib.homeManagerConfiguration { inherit pkgs; # Specify your home configuration modules here, for example, # the path to your home.nix. modules = [ ./home.nix ]; # Optionally use extraSpecialArgs # to pass through arguments to home.nix extraSpecialArgs = { inherit inputs; }; }; } );} nix 的语法就不在这里介绍了,还需要创建一个 home.nix,写入以下内容: { inputs, config, pkgs, ... }:{ # Home Manager needs a bit of information about you and the # paths it should manage. home.username = "lxz"; home.homeDirectory = "/home/lxz"; home.packages = with pkgs; [ git-lfs neofetch htop ripgrep lazygit gh go cargo lemonade nix-index nix-update # font noto-fonts noto-fonts-cjk noto-fonts-emoji liberation_ttf fira-code fira-code-symbols fira-code-nerdfont sarasa-gothic (nerdfonts.override { fonts = [ "FiraCode" "DroidSansMono" ]; }) nodejs # neovim nightly neovim # neovim-nightly ]; nixpkgs.overlays = [ inputs.neovim-nightly-overlay.overlays.default ]; fonts.fontconfig.enable = true; # Allow fontconfig to discover fonts in home.packages programs.fzf = { enable = true; enableBashIntegration = true; enableZshIntegration = true; }; services.gpg-agent = { enable = false; defaultCacheTtl = 1800; enableSshSupport = true; }; home.shellAliases = { "..." = "cd ../.."; }; home.stateVersion = "24.05"; # Let Home Manager install and manage itself. programs.home-manager.enable = true;} 这个文件的配置就是安装和配置软件了,可以看到我定义了一些路径和软件包,以及字体的 override 等,可以按需修改。 安装和更新nix flake update 更新下仓库 nix-shell -p home-manager 安装一个 home-manager。 home-manager switch --flake . 使用当前配置安装 nix 软件包,其中包括了 home-manager。","categories":[{"name":"技术分享","slug":"技术分享","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"}],"tags":[{"name":"dotfile","slug":"dotfile","permalink":"https://blog.justforlxz.com/tags/dotfile/"}]},{"title":"异世相遇,尽享美味","slug":"have-a-nice-day","date":"2024-11-23T04:00:00.000Z","updated":"2024-12-31T08:44:54.341Z","comments":true,"path":"2024/11/23/have-a-nice-day/","permalink":"https://blog.justforlxz.com/2024/11/23/have-a-nice-day/","excerpt":"","text":"今天周五,下班去接老婆下班回家,快到家的时候突然有点嘴馋,想吃烧烤。 我:以前去的那家韩国烤肉店花了多少钱? 老婆:三百多。 我:三百多太贵了,不去!(拍大腿) 我:三百块都够买多少肉了。 老婆:那在家自己做?买点五花,买点泡菜,生菜,家里还有牛肉。 我:可以,自己做下也费不了多少功夫,还能吃到饱。 老婆:用电饼铛直接烤。 我:电饼铛火力不够吧。 老婆:够,不行还有小烤盘,专门烤肉的。 我:好,现在过去买肉。 最终花了不到一百块钱,两个人吃到撑,还没吃完。 😀 五花肉 23元 楼下超市 牛肉 10元 永旺夜间打折 生菜 2元 楼下菜市场送的 锅圈 40元 只能自己花钱了 😭 想想以前四五百去外边吃,还吃不饱。","categories":[{"name":"心情随笔","slug":"心情随笔","permalink":"https://blog.justforlxz.com/categories/%E5%BF%83%E6%83%85%E9%9A%8F%E7%AC%94/"}],"tags":[{"name":"健康, 思考","slug":"健康-思考","permalink":"https://blog.justforlxz.com/tags/%E5%81%A5%E5%BA%B7-%E6%80%9D%E8%80%83/"}]},{"title":"Treeland Part.1 如何实现一个基于 wlroots 的合成器","slug":"treeland-part1-how-to-implement-a-wlroots-based-compositor","date":"2024-11-09T04:00:00.000Z","updated":"2024-12-31T08:44:45.357Z","comments":true,"path":"2024/11/09/treeland-part1-how-to-implement-a-wlroots-based-compositor/","permalink":"https://blog.justforlxz.com/2024/11/09/treeland-part1-how-to-implement-a-wlroots-based-compositor/","excerpt":"","text":"😀 Treeland 是一个基于 wlroots 和 Qt 的 Wayland 合成器 并且支持多用户共用合成器。 名词概念QtQt(/ˈkjuːt/,发音同“cute”)是一个跨平台的C++应用程序开发框架。广泛用于开发GUI程序,这种情况下又被称为部件工具箱。也可用于开发非GUI程序,例如控制台工具和服务器。 wlroots用于构建 Wayland 合成器的模块化工具集,简化了约 60,000 行代码的开发工作。 提供抽象底层显示和输入的后端,支持 KMS/DRM、libinput、Wayland、X11 等,可动态创建和销毁。 实现多种 Wayland 接口,支持协议扩展,促进合成器标准化。 提供通用合成器组件,如物理空间输出管理。 集成 Xwayland 抽象,简化 X11 窗口管理。 提供渲染器抽象,支持简单和自定义渲染需求。 seat由分配给特定工作场景的所有硬件设备组成。它至少包含一个图形设备,通常还有键盘和鼠标。此外,它可能包括摄像头、声卡等设备。座位由座位名称标识,这是一个短字符串(不超过64个字符),以”seat”四个字符开头,后跟至少一个a-zA-Z0-9范围内的字符,或”_”和”-“。这种命名方式适合用于文件名。座位名称可能是稳定的,也可能不稳定,如果座位再次可用,其名称可以重复使用。 RHIRHI 是 Renderer Hardware Interface(渲染硬件接口)的缩写,是一套对硬件的抽象,在上层只需要设置参数,底层具体使用的是 OpenGL、Vulkan、DX12 还是 Metal 哪套接口,我们是不必关心的。 Qt6 提供了 QRHI,为 Qt 程序提供了底层的硬件抽象,这样上层的 QtQuick 组件在执行 GPU 渲染时,就可以自动调用对应的驱动接口。 QPAQt 平台抽象(QPA)是 Qt 中的核心平台抽象层。 QPA 的 API 可通过类前缀”QPlatform*”识别,用于实现 Qt GUI 中的高级类。例如,QPlatformWindow 用于窗口系统集成,而 QPlatformTheme 和 QStyleHint 则用于深层次的平台主题和集成。 基本工作流程Treeland 使用 QQuickWindow 作为渲染的根,这样在 Treeland 里开发时,就如同开发一个普通 Qt 程序一样,先创建一个 Window,在 Window 内创建 Qt 控件,使用 QEvent 处理各种事件。 那么 Treeland 是如何实现这件事的呢? QQuickWindow 的私有类提供了自定义 QQuickGraphicsDevice 对象的接口,而 QQuickGraphicsDevice 可以使用 fromOpenGLContext 和 fromPhyicalDevice 创建新的对象,那么 Treeland 只需要继承 QQuickWindow,并从 wlroots 获取 OpenGL context 和 phyical device,就可以将 Qt QuickWindow 的渲染,嫁接到 wlroots 上。 通过将 wlroots 的渲染上下文与 Qt 的渲染上下文进行结合,可以将 wlroots 渲染的图形结果嵌入到 Qt 应用程序的渲染流程中,可以直接使用 wlroots 提供的图形资源和设备对象,如物理设备(phdev)、逻辑设备(dev)和队列族(queue_family),以减少不必要的上下文切换和资源拷贝。这样,Qt 就可以利用 wlroots 提供的渲染能力,同时能够继续使用 Qt 的渲染框架和 API。 之后在 Qt QPA 中将屏幕信息,以及输入信息转换成 Qt 内部对象,从而利用 Qt 自身的事件循环等机制继续处理。 Qt QPAQPA 为 Qt 提供了跨平台的接口抽象能力,我们可以提供自己的 QPA 插件来为 Qt 程序提供新的能力,例如将 wlroots 的输入事件转换成 Qt 内部事件。 输入事件处理 Treeland 处理底层事件与上层事件的流程 bool WOutputRenderWindow::event(QEvent *event){ Q_D(WOutputRenderWindow); if (event->type() == doRenderEventType) { QCoreApplication::removePostedEvents(this, doRenderEventType); d_func()->doRender(); return true; } if (QW::RenderWindow::beforeDisposeEventFilter(this, event)) { event->accept(); QW::RenderWindow::afterDisposeEventFilter(this, event); return true; } bool isAccepted = QQuickWindow::event(event); if (QW::RenderWindow::afterDisposeEventFilter(this, event)) return true; return isAccepted;} 在 WOutputRenderWindow 的事件处理中,会额外调用下 seat 的事件过滤器,确保合成器可以拦截掉一部分事件,例如将一部分按键拦截下来,不发送给客户端。 bool QWlrootsRenderWindow::beforeDisposeEventFilter(QEvent *event){ if (event->isInputEvent()) { auto ie = static_cast<QInputEvent*>(event); auto device = WInputDevice::from(ie->device()); Q_ASSERT(device); Q_ASSERT(device->seat()); lastActiveCursor = device->seat()->cursor(); return device->seat()->filterEventBeforeDisposeStage(window(), ie); } return false;} 这段代码展示了转换输入设备的功能,判断输入设备的类型,创建对应的 QInputDevice 对象。 QPointer<QInputDevice> QWlrootsIntegration::addInputDevice(WInputDevice *device, const QString &seatName){ QPointer<QInputDevice> qtdev; auto qwDevice = device->handle(); const QString name = QString::fromUtf8(qwDevice->handle()->name); qint64 systemId = reinterpret_cast<qint64>(device); switch (qwDevice->handle()->type) { case WLR_INPUT_DEVICE_KEYBOARD: { qtdev = new QInputDevice(name, systemId, QInputDevice::DeviceType::Keyboard, seatName); break; } case WLR_INPUT_DEVICE_POINTER: { qtdev = new QPointingDevice(name, systemId, QInputDevice::DeviceType::TouchPad, QPointingDevice::PointerType::Generic, QInputDevice::Capability::Position | QInputDevice::Capability::Hover | QInputDevice::Capability::Scroll | QInputDevice::Capability::MouseEmulation, 10, 32, seatName, QPointingDeviceUniqueId()); break; } case WLR_INPUT_DEVICE_TOUCH: { qtdev = new QPointingDevice(name, systemId, QInputDevice::DeviceType::TouchScreen, QPointingDevice::PointerType::Finger, QInputDevice::Capability::Position | QInputDevice::Capability::Area | QInputDevice::Capability::MouseEmulation, 10, 32, seatName, QPointingDeviceUniqueId()); break; } case WLR_INPUT_DEVICE_TABLET_TOOL: { qtdev = new QPointingDevice(name, systemId, QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Pen, QInputDevice::Capability::XTilt | QInputDevice::Capability::YTilt | QInputDevice::Capability::Pressure, 1, 32, seatName, QPointingDeviceUniqueId()); break; } case WLR_INPUT_DEVICE_TABLET_PAD: { auto pad = wlr_tablet_pad_from_input_device(qwDevice->handle()); qtdev = new QPointingDevice(name, systemId, QInputDevice::DeviceType::TouchPad, QPointingDevice::PointerType::Pen, QInputDevice::Capability::Position | QInputDevice::Capability::Hover | QInputDevice::Capability::Pressure, 1, pad->button_count, seatName, QPointingDeviceUniqueId()); break; } case WLR_INPUT_DEVICE_SWITCH: { qtdev = new QInputDevice(name, systemId, QInputDevice::DeviceType::Keyboard, seatName); break; } } if (qtdev) { device->setQtDevice(qtdev); QWindowSystemInterface::registerInputDevice(qtdev); if (qtdev->type() == QInputDevice::DeviceType::Mouse || qtdev->type() == QInputDevice::DeviceType::TouchPad) { auto primaryQtDevice = QPointingDevice::primaryPointingDevice(); if (!WInputDevice::from(primaryQtDevice)) { // Ensure the primary pointing device is the WInputDevice auto pd = const_cast<QPointingDevice*>(primaryQtDevice); pd->setParent(nullptr); delete pd; } Q_ASSERT(WInputDevice::from(QPointingDevice::primaryPointingDevice())); } else if (qtdev->type() == QInputDevice::DeviceType::Keyboard) { auto primaryQtDevice = QInputDevice::primaryKeyboard(); if (!WInputDevice::from(primaryQtDevice)) { // Ensure the primary keyboard device is the WInputDevice auto pd = const_cast<QInputDevice*>(primaryQtDevice); pd->setParent(nullptr); delete pd; } Q_ASSERT(WInputDevice::from(QInputDevice::primaryKeyboard())); } } return qtdev;} 客户端事件在 Treeland 还有一种事件需要处理,当用户点击一个窗口,合成器需要告知客户端哪个坐标点击了。或者使用键盘进行输入时,需要告知客户端输入的内容。 首先,Treeland 会标记一个窗口成为激活窗口,设置给 seat,这样 wlroots 就知道哪个窗口此时拥有焦点。 之后当键盘发生输入事件时,Treeland 没有过滤掉按键事件,或者是放行某些按键,这些剩余的输入事件就会在 wseat 的 sendEvent 中,发送给激活的客户端。 // for keyboard eventinline bool doNotifyKey(WInputDevice *device, uint32_t keycode, uint32_t state, uint32_t timestamp) { if (!keyboardFocusSurface()) return false; q_func()->setKeyboard(device); /* Send modifiers to the client. */ this->handle()->keyboard_notify_key(timestamp, keycode, state); return true;} 屏幕信息在 QPA 中还对 WOutput 进行了封装 QWlrootsScreen。 QWlrootsScreen *QWlrootsIntegration::addScreen(WOutput *output){ m_screens << new QWlrootsScreen(output); if (isMaster()) { QWindowSystemInterface::handleScreenAdded(m_screens.last()); if (m_placeholderScreen) { QWindowSystemInterface::handleScreenRemoved(m_placeholderScreen.release()); } } else { Q_UNUSED(new QScreen(m_screens.last())) } m_screens.last()->initialize(); output->setScreen(m_screens.last()); return m_screens.last();} QWlrootsScreen 继承自 QPlatformScreen,做的事情是将部分参数进行转换,例如physicalSize、devicePixelRatio、DPI等,之后通过 QWindowSystemInterface::handleScreenAdded 将创建好的 QWlrootsScreen 添加进 Qt 内。 Qt RHI摘抄一段来自 waylib 中初始化 Qt RHI 的代码 bool WOutputRenderWindowPrivate::initRCWithRhi(){ W_Q(WOutputRenderWindow); QQuickRenderControlPrivate *rcd = QQuickRenderControlPrivate::get(rc()); QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();// sanity check for Vulkan#ifdef ENABLE_VULKAN_RENDER if (rhiSupport->rhiBackend() == QRhi::Vulkan) { vkInstance.reset(new QVulkanInstance()); auto phdev = wlr_vk_renderer_get_physical_device(m_renderer->handle()); auto dev = wlr_vk_renderer_get_device(m_renderer->handle()); auto queue_family = wlr_vk_renderer_get_queue_family(m_renderer->handle());#if QT_VERSION > QT_VERSION_CHECK(6, 6, 0) auto instance = wlr_vk_renderer_get_instance(m_renderer->handle()); vkInstance->setVkInstance(instance);#endif // vkInstance->setExtensions(fromCStyleList(vkRendererAttribs.extension_count, vkRendererAttribs.extensions)); // vkInstance->setLayers(fromCStyleList(vkRendererAttribs.layer_count, vkRendererAttribs.layers)); vkInstance->setApiVersion({1, 1, 0}); vkInstance->create(); q->setVulkanInstance(vkInstance.data()); auto gd = QQuickGraphicsDevice::fromDeviceObjects(phdev, dev, queue_family); q->setGraphicsDevice(gd); } else#endif if (rhiSupport->rhiBackend() == QRhi::OpenGLES2) { Q_ASSERT(wlr_renderer_is_gles2(m_renderer->handle())); auto egl = wlr_gles2_renderer_get_egl(m_renderer->handle()); auto display = wlr_egl_get_display(egl); auto context = wlr_egl_get_context(egl); this->glContext = new QW::OpenGLContext(display, context, rc()); bool ok = this->glContext->create(); if (!ok) return false; q->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(this->glContext)); } else { return false; } QOffscreenSurface *offscreenSurface = new QW::OffscreenSurface(nullptr, q); offscreenSurface->create(); QSGRhiSupport::RhiCreateResult result = rhiSupport->createRhi(q, offscreenSurface); if (!result.rhi) { qWarning("WOutput::initRhi: Failed to initialize QRhi"); return false; } rcd->rhi = result.rhi; // Ensure the QQuickRenderControl don't reinit the RHI rcd->ownRhi = true; if (!rc()->initialize()) return false; rcd->ownRhi = result.own; Q_ASSERT(rcd->rhi == result.rhi); Q_ASSERT(!swapchain); return true;} 先获取 QSGRhiSupport 及相关控制对象。 判断 RHI backend 的类型,需要适配 vulkan、gles等。 从 wlroots 获取物理设备等参数,使用 QQuickGraphicsDevice::fromDeviceObjects 创建 Qt 的 QQuickGraphicsDevice。 render window的私有类是继承自 QQuickWindowPrivate,只需要将获取到的 QQuickGraphicsDevice 设置给 QQuickWindowPrivate::setGraphicsDevice 即可。 之后创建一个离屏渲染表面,用于 RHI 的初始化。 Qt Viewport在 Qt 中,想要查看或者渲染一个组件,需要使用 Viewport 组件,俗称照相机。 视口(Viewport)是一个可观察的多边形区域,只有 Viewport 范围内的画面才能显示到屏幕上。 wlroots 中的 Viewport 是一个与 Wayland 显示协议相关的概念,主要用于定义渲染输出在屏幕上的显示区域。它允许在渲染时对显示内容进行缩放、裁剪或平移,以适应不同的分辨率和显示需求。 Treeland 使用 WOutputViewport 提供 Viewport 功能,使用 wlroots 的 wlr_output 中的屏幕信息,对画面进行矩阵变换,这里会涉及到屏幕的缩放、DPI等参数。 QMatrix4x4 WOutputViewport::renderMatrix() const{ QMatrix4x4 renderMatrix; if (auto customTransform = viewportTransform()) { customTransform->applyTo(&renderMatrix); } else if (parentItem() && !ignoreViewport() && input() != this) { auto d = QQuickItemPrivate::get(const_cast<WOutputViewport*>(this)); auto viewportMatrix = d->itemNode()->matrix().inverted(); if (auto inputItem = input()) { QMatrix4x4 matrix = QQuickItemPrivate::get(parentItem())->itemToWindowTransform(); matrix *= QQuickItemPrivate::get(inputItem)->windowToItemTransform(); renderMatrix = viewportMatrix * matrix.inverted(); } else { // the input item is window's contentItem auto pd = QQuickItemPrivate::get(parentItem()); QMatrix4x4 parentMatrix = pd->itemToWindowTransform().inverted(); renderMatrix = viewportMatrix * parentMatrix; } } return renderMatrix;} WOutputViewport 提供了 Viewport 所需的所有参数,变换矩阵、源几何大小、目标几何大小等信息。 在 WOutputRenderWindow 的事件中,判断如果是渲染的事件,就执行渲染。 bool WOutputRenderWindow::event(QEvent *event){ Q_D(WOutputRenderWindow); if (event->type() == doRenderEventType) { QCoreApplication::removePostedEvents(this, doRenderEventType); d_func()->doRender(); return true; } if (QW::RenderWindow::beforeDisposeEventFilter(this, event)) { event->accept(); QW::RenderWindow::afterDisposeEventFilter(this, event); return true; } bool isAccepted = QQuickWindow::event(event); if (QW::RenderWindow::afterDisposeEventFilter(this, event)) return true; return isAccepted;} 在 doRender 中,遍历所有的 Output,执行 beginRender,然后执行 Output 的渲染。 void WOutputRenderWindowPrivate::doRender(const QList<OutputHelper *> &outputs, bool forceRender, bool doCommit){ Q_ASSERT(rendererList.isEmpty()); Q_ASSERT(!inRendering); inRendering = true; W_Q(WOutputRenderWindow); for (OutputLayer *layer : std::as_const(layers)) { layer->beforeRender(q); } rc()->polishItems(); if (QSGRendererInterface::isApiRhiBased(WRenderHelper::getGraphicsApi())) rc()->beginFrame(); rc()->sync(); QQuickAnimatorController_advance(animationController.get()); Q_EMIT q->beforeRendering(); runAndClearJobs(&beforeRenderingJobs); auto needsCommit = doRenderOutputs(outputs, forceRender); Q_EMIT q->afterRendering(); runAndClearJobs(&afterRenderingJobs); if (QSGRendererInterface::isApiRhiBased(WRenderHelper::getGraphicsApi())) rc()->endFrame(); if (doCommit) { for (auto i : std::as_const(needsCommit)) { bool ok = i.first->commit(i.second); if (i.second->currentBuffer()) { i.second->endRender(); } i.first->resetState(ok); } } resetGlState(); // On Intel&Nvidia multi-GPU environment, wlroots using Intel card do render for all // outputs, and blit nvidia's output buffer in drm_connector_state_update_primary_fb, // the 'blit' behavior will make EGL context to Nvidia renderer. So must done current // OpenGL context here in order to ensure QtQuick always make EGL context to Intel // renderer before next frame. if (glContext) glContext->doneCurrent(); inRendering = false; Q_EMIT q->renderEnd();} qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixelRatio, uint32_t format, RenderFlags flags){ Q_ASSERT(!state.buffer); Q_ASSERT(m_output); if (pixelSize.isEmpty()) return nullptr; Q_EMIT beforeRendering(); m_damageRing.set_bounds(pixelSize.width(), pixelSize.height()); // configure swapchain if (flags.testFlag(RenderFlag::DontConfigureSwapchain)) { auto renderFormat = pickFormat(m_output->renderer(), format); if (!renderFormat) { qWarning("wlr_renderer doesn't support format 0x%s", drmGetFormatName(format)); return nullptr; } if (!m_swapchain || QSize(m_swapchain->handle()->width, m_swapchain->handle()->height) != pixelSize || m_swapchain->handle()->format.format != renderFormat->format) { if (m_swapchain) delete m_swapchain; m_swapchain = qw_swapchain::create(m_output->allocator()->handle(), pixelSize.width(), pixelSize.height(), renderFormat); } } else if (flags.testFlag(RenderFlag::UseCursorFormats)) { bool ok = m_output->configureCursorSwapchain(pixelSize, format, &m_swapchain); if (!ok) return nullptr; } else { bool ok = m_output->configurePrimarySwapchain(pixelSize, format, &m_swapchain, !flags.testFlag(DontTestSwapchain)); if (!ok) return nullptr; } // TODO: Support scanout buffer of wlr_surface(from WSurfaceItem) int bufferAge; auto wbuffer = m_swapchain->acquire(&bufferAge); if (!wbuffer) return nullptr; auto buffer = qw_buffer::from(wbuffer); if (!m_renderHelper) m_renderHelper = new WRenderHelper(m_output->renderer()); m_renderHelper->setSize(pixelSize); auto wd = QQuickWindowPrivate::get(window()); Q_ASSERT(wd->renderControl); auto lastRT = m_renderHelper->lastRenderTarget(); auto rt = m_renderHelper->acquireRenderTarget(wd->renderControl, buffer); if (rt.isNull()) { buffer->unlock(); return nullptr; } auto rtd = QQuickRenderTargetPrivate::get(&rt); QSGRenderTarget sgRT; if (rtd->type == QQuickRenderTargetPrivate::Type::PaintDevice) { sgRT.paintDevice = rtd->u.paintDevice; } else { Q_ASSERT(rtd->type == QQuickRenderTargetPrivate::Type::RhiRenderTarget); sgRT.rt = rtd->u.rhiRt; sgRT.cb = wd->redirect.commandBuffer; Q_ASSERT(sgRT.cb); sgRT.rpDesc = rtd->u.rhiRt->renderPassDescriptor();#ifndef QT_NO_OPENGL if (wd->rhi->backend() == QRhi::OpenGLES2) { auto glRT = QRHI_RES(QGles2TextureRenderTarget, rtd->u.rhiRt); Q_ASSERT(glRT->framebuffer >= 0); auto glContext = QOpenGLContext::currentContext(); Q_ASSERT(glContext); QOpenGLContextPrivate::get(glContext)->defaultFboRedirect = glRT->framebuffer; }#endif } state.flags = flags; state.context = wd->context; state.pixelSize = pixelSize; state.devicePixelRatio = devicePixelRatio; state.bufferAge = bufferAge; state.lastRT = lastRT; state.buffer = buffer; state.renderTarget = rt; state.sgRenderTarget = sgRT; return buffer;} QVector<std::pair<OutputHelper*, WBufferRenderer*>>WOutputRenderWindowPrivate::doRenderOutputs(const QList<OutputHelper*> &outputs, bool forceRender){ QVector<OutputHelper*> renderResults; renderResults.reserve(outputs.size()); for (OutputHelper *helper : std::as_const(outputs)) { if (Q_LIKELY(!forceRender)) { if (!helper->renderable() || Q_UNLIKELY(!WOutputViewportPrivate::get(helper->output())->renderable()) || !helper->output()->output()->isEnabled()) continue; if (!helper->contentIsDirty()) { if (helper->needsFrame()) renderResults.append(helper); continue; } } Q_ASSERT(helper->output()->output()->scale() <= helper->output()->devicePixelRatio()); const auto &format = helper->qwoutput()->handle()->render_format; const auto renderMatrix = helper->output()->renderMatrix(); // maybe using the other WOutputViewport's QSGTextureProvider if (!helper->output()->depends().isEmpty()) updateDirtyNodes(); qw_buffer *buffer = helper->beginRender(helper->bufferRenderer(), helper->output()->output()->size(), format, WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject); Q_ASSERT(buffer == helper->bufferRenderer()->currentBuffer()); if (buffer) { helper->render(helper->bufferRenderer(), 0, renderMatrix, helper->output()->effectiveSourceRect(), helper->output()->targetRect(), helper->output()->preserveColorContents()); } renderResults.append(helper); } QVector<std::pair<OutputHelper*, WBufferRenderer*>> needsCommit; needsCommit.reserve(renderResults.size()); for (auto helper : std::as_const(renderResults)) { auto bufferRenderer = helper->afterRender(); if (bufferRenderer) needsCommit.append({helper, bufferRenderer}); } rendererList.clear(); return needsCommit;} void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix, const QRectF &sourceRect, const QRectF &targetRect, bool preserveColorContents){ Q_ASSERT(state.buffer); const auto &source = m_sourceList.at(sourceIndex); QSGRenderer *renderer = ensureRenderer(sourceIndex, state.context); auto wd = QQuickWindowPrivate::get(window()); const qreal devicePixelRatio = state.devicePixelRatio; state.renderer = renderer; state.worldTransform = renderMatrix; renderer->setDevicePixelRatio(devicePixelRatio); renderer->setDeviceRect(QRect(QPoint(0, 0), state.pixelSize)); renderer->setRenderTarget(state.sgRenderTarget); const auto viewportRect = scaleToRect(targetRect, devicePixelRatio); auto softwareRenderer = dynamic_cast<QSGSoftwareRenderer*>(renderer); { // before render if (softwareRenderer) { // because software renderer don't supports viewportRect, // so use transform to simulation. const auto mapTransform = inputMapToOutput(sourceRect, targetRect, state.pixelSize, state.devicePixelRatio); if (!mapTransform.isIdentity()) state.worldTransform = mapTransform * state.worldTransform; state.worldTransform.optimize(); auto image = getImageFrom(state.renderTarget); image->setDevicePixelRatio(devicePixelRatio); // TODO: Should set to QSGSoftwareRenderer, but it's not support specify matrix. // If transform is changed, it will full repaint. if (isRootItem(source.source)) { auto rootTransform = QQuickItemPrivate::get(wd->contentItem)->itemNode(); if (rootTransform->matrix() != state.worldTransform) rootTransform->setMatrix(state.worldTransform); } else { auto t = state.worldTransform.toTransform(); if (t.type() > QTransform::TxTranslate) { (image->operator QImage &()).fill(renderer->clearColor()); softwareRenderer->markDirty(); } applyTransform(softwareRenderer, t); } } else { state.worldTransform.optimize(); bool flipY = wd->rhi ? !wd->rhi->isYUpInNDC() : false; if (state.renderTarget.mirrorVertically()) flipY = !flipY; if (viewportRect.isValid()) { QRect vr = viewportRect; if (flipY) vr.moveTop(-vr.y() + state.pixelSize.height() - vr.height()); renderer->setViewportRect(vr); } else { renderer->setViewportRect(QRect(QPoint(0, 0), state.pixelSize)); } QRectF rect = sourceRect; if (!rect.isValid()) rect = QRectF(QPointF(0, 0), QSizeF(state.pixelSize) / devicePixelRatio); const float left = rect.x(); const float right = rect.x() + rect.width(); float bottom = rect.y() + rect.height(); float top = rect.y(); if (flipY) std::swap(top, bottom); QMatrix4x4 matrix; matrix.ortho(left, right, bottom, top, 1, -1); QMatrix4x4 projectionMatrix, projectionMatrixWithNativeNDC; projectionMatrix = matrix * state.worldTransform; if (wd->rhi && !wd->rhi->isYUpInNDC()) { std::swap(top, bottom); matrix.setToIdentity(); matrix.ortho(left, right, bottom, top, 1, -1); } projectionMatrixWithNativeNDC = matrix * state.worldTransform; renderer->setProjectionMatrix(projectionMatrix); renderer->setProjectionMatrixWithNativeNDC(projectionMatrixWithNativeNDC); auto textureRT = static_cast<QRhiTextureRenderTarget*>(state.sgRenderTarget.rt); if (preserveColorContents) { textureRT->setFlags(textureRT->flags() | QRhiTextureRenderTarget::PreserveColorContents); } else { textureRT->setFlags(textureRT->flags() & ~QRhiTextureRenderTarget::PreserveColorContents); } } } state.context->renderNextFrame(renderer); { // after render if (!softwareRenderer) { // TODO: get damage area from QRhi renderer m_damageRing.add_whole(); // ###: maybe Qt bug? Before executing QRhi::endOffscreenFrame, we may // use the same QSGRenderer for multiple drawings. This can lead to // rendering the same content for different QSGRhiRenderTarget instances // when using the RhiGles backend. Additionally, considering that the // result of the current drawing may be needed when drawing the next // sourceIndex, we should let the RHI (Rendering Hardware Interface) // complete the results of this drawing here to ensure the current // drawing result is available for use. wd->rhi->finish(); } else { auto currentImage = getImageFrom(state.renderTarget); Q_ASSERT(currentImage && currentImage == softwareRenderer->m_rt.paintDevice); currentImage->setDevicePixelRatio(1.0); const auto scaleTF = QTransform::fromScale(devicePixelRatio, devicePixelRatio); const auto scaledFlushRegion = scaleTF.map(softwareRenderer->flushRegion()); PixmanRegion scaledFlushDamage; bool ok = WTools::toPixmanRegion(scaledFlushRegion, scaledFlushDamage); Q_ASSERT(ok); { PixmanRegion damage; m_damageRing.get_buffer_damage(state.bufferAge, damage); if (viewportRect.isValid()) { QRect imageRect = (currentImage->operator const QImage &()).rect(); QRegion invalidRegion(imageRect); invalidRegion -= viewportRect; if (!scaledFlushRegion.isEmpty()) invalidRegion &= scaledFlushRegion; if (!invalidRegion.isEmpty()) { QPainter pa(currentImage); for (const auto r : std::as_const(invalidRegion)) pa.fillRect(r, softwareRenderer->clearColor()); } } if (!damage.isEmpty() && state.lastRT.first != state.buffer && !state.lastRT.second.isNull()) { auto image = getImageFrom(state.lastRT.second); Q_ASSERT(image); Q_ASSERT(image->size() == state.pixelSize); // TODO: Don't use the previous render target, we can get the damage region of QtQuick // before QQuickRenderControl::render for qw_damage_ring, and add dirty region to // QSGAbstractSoftwareRenderer to force repaint the damage region of current render target. QPainter pa(currentImage); PixmanRegion remainderDamage; ok = pixman_region32_subtract(remainderDamage, damage, scaledFlushDamage); Q_ASSERT(ok); int count = 0; auto rects = pixman_region32_rectangles(remainderDamage, &count); for (int i = 0; i < count; ++i) { auto r = rects[i]; pa.drawImage(r.x1, r.y1, *image, r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1); } } } if (!isRootItem(source.source)) applyTransform(softwareRenderer, state.worldTransform.inverted().toTransform()); m_damageRing.add(scaledFlushDamage); } } if (auto dr = qobject_cast<QSGDefaultRenderContext*>(state.context)) { QRhiResourceUpdateBatch *resourceUpdates = wd->rhi->nextResourceUpdateBatch(); dr->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates); } if (shouldCacheBuffer()) wTextureProvider()->setBuffer(state.buffer);} 处理完画面以后,如果需要上屏画面,就调用 commit 把画面送到屏幕上。 bool OutputHelper::commit(WBufferRenderer *buffer){ if (output()->offscreen()) return true; if (!buffer || !buffer->currentBuffer()) { Q_ASSERT(!this->buffer()); return WOutputHelper::commit(); } setBuffer(buffer->currentBuffer()); if (m_lastCommitBuffer == buffer) { if (pixman_region32_not_empty(&buffer->damageRing()->handle()->current)) setDamage(&buffer->damageRing()->handle()->current); } m_lastCommitBuffer = buffer; return WOutputHelper::commit();} 还会判断是否有硬件加速(GPU),会优先使用硬件来加速计算过程。 } else { state.worldTransform.optimize(); bool flipY = wd->rhi ? !wd->rhi->isYUpInNDC() : false; if (state.renderTarget.mirrorVertically()) flipY = !flipY; if (viewportRect.isValid()) { QRect vr = viewportRect; if (flipY) vr.moveTop(-vr.y() + state.pixelSize.height() - vr.height()); renderer->setViewportRect(vr); } else { renderer->setViewportRect(QRect(QPoint(0, 0), state.pixelSize)); } QRectF rect = sourceRect; if (!rect.isValid()) rect = QRectF(QPointF(0, 0), QSizeF(state.pixelSize) / devicePixelRatio); const float left = rect.x(); const float right = rect.x() + rect.width(); float bottom = rect.y() + rect.height(); float top = rect.y(); if (flipY) std::swap(top, bottom); QMatrix4x4 matrix; matrix.ortho(left, right, bottom, top, 1, -1); QMatrix4x4 projectionMatrix, projectionMatrixWithNativeNDC; projectionMatrix = matrix * state.worldTransform; if (wd->rhi && !wd->rhi->isYUpInNDC()) { std::swap(top, bottom); matrix.setToIdentity(); matrix.ortho(left, right, bottom, top, 1, -1); } projectionMatrixWithNativeNDC = matrix * state.worldTransform; renderer->setProjectionMatrix(projectionMatrix); renderer->setProjectionMatrixWithNativeNDC(projectionMatrixWithNativeNDC); auto textureRT = static_cast<QRhiTextureRenderTarget*>(state.sgRenderTarget.rt); if (preserveColorContents) { textureRT->setFlags(textureRT->flags() | QRhiTextureRenderTarget::PreserveColorContents); } else { textureRT->setFlags(textureRT->flags() & ~QRhiTextureRenderTarget::PreserveColorContents); }} Surface 渲染在 Treeland 中,为 Surface 创建了 WSurfaceItem,用于表示一个窗口,并创建了 WSurfaceContent 作为 WSurfaceItem 的 delegate。 void WSurfaceItemPrivate::initForDelegate(){ Q_Q(WSurfaceItem); std::unique_ptr<QQuickItem> newContentContainer; if (!delegate) { if (getItemContent()) { Q_ASSERT(!delegateIsDirty); return; } delegateIsDirty = false; auto contentItem = new WSurfaceItemContent(q); if (surface) contentItem->setSurface(surface); contentItem->setCacheLastBuffer(!surfaceFlags.testFlag(WSurfaceItem::DontCacheLastBuffer)); contentItem->setSmooth(q->smooth()); contentItem->setLive(!q->flags().testFlag(WSurfaceItem::NonLive)); QObject::connect(q, &WSurfaceItem::smoothChanged, contentItem, &WSurfaceItemContent::setSmooth); newContentContainer.reset(contentItem); } else if (delegateIsDirty) { auto obj = delegate->createWithInitialProperties({{"surface", QVariant::fromValue(q)}}, qmlContext(q)); if (!obj) { qWarning() << "Failed on create surface item from delegate, error mssage:" << delegate->errorString(); return; } delegateIsDirty = false; auto contentItem = qobject_cast<QQuickItem*>(obj); if (!contentItem) qFatal() << "SurfaceItem's delegate must is Item"; newContentContainer.reset(new QQuickItem(q)); QQmlEngine::setObjectOwnership(contentItem, QQmlEngine::CppOwnership); contentItem->setParent(newContentContainer.get()); contentItem->setParentItem(newContentContainer.get()); } if (!newContentContainer) return; newContentContainer->setZ(qreal(WSurfaceItem::ZOrder::ContentItem)); if (contentContainer) { newContentContainer->setPosition(contentContainer->position()); newContentContainer->setSize(contentContainer->size()); newContentContainer->setTransformOrigin(contentContainer->transformOrigin()); newContentContainer->setScale(contentContainer->scale()); contentContainer->disconnect(q); contentContainer->deleteLater(); } contentContainer = newContentContainer.release(); updateEventItem(false); updateBoundingRect(); if (eventItem) updateEventItemGeometry(); Q_EMIT q->contentItemChanged();} 之后当 WSurfaceItem 需要更新画面时,就能调用 updatePaintNode 更新渲染。 QSGNode *WSurfaceItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *){ W_D(WSurfaceItemContent); auto tp = wTextureProvider(); if (d->live || !tp->texture()) { auto texture = d->surface ? d->surface->handle()->get_texture() : nullptr; if (texture) { tp->setTexture(qw_texture::from(texture), d->buffer.get()); } else { tp->setBuffer(d->buffer.get()); } } if (!tp->texture() || width() <= 0 || height() <= 0) { delete oldNode; return nullptr; } auto node = static_cast<QSGImageNode*>(oldNode); if (Q_UNLIKELY(!node)) { node = window()->createImageNode(); node->setOwnsTexture(false); QSGNode *fpnode = new WSGRenderFootprintNode(this); node->appendChildNode(fpnode); } auto texture = tp->texture(); node->setTexture(texture); const QRectF textureGeometry = d->bufferSourceBox; node->setSourceRect(textureGeometry); const QRectF targetGeometry(d->ignoreBufferOffset ? QPointF() : d->bufferOffset, size()); node->setRect(targetGeometry); node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest); return node;} 而使用 delegate 的目的是为了能让多个 WSurfaceItem 使用相同的窗口画面,例如某些场景需要临时创建一个窗口的分身,窗口切换列表、多任务视图等。 QSGTextureProvider *WSurfaceItemContent::textureProvider() const{ if (QQuickItem::isTextureProvider()) return QQuickItem::textureProvider(); return wTextureProvider();}WSGTextureProvider *WSurfaceItemContent::wTextureProvider() const{ W_DC(WSurfaceItemContent); auto w = qobject_cast<WOutputRenderWindow*>(d->window); if (!w || !d->sceneGraphRenderContext() || QThread::currentThread() != d->sceneGraphRenderContext()->thread()) { qWarning("WQuickCursor::textureProvider: can only be queried on the rendering thread of an WOutputRenderWindow"); return nullptr; } if (!d->textureProvider) { d->textureProvider = new WSGTextureProvider(w); if (d->surface) { if (auto texture = d->surface->handle()->get_texture()) { d->textureProvider->setTexture(qw_texture::from(texture), d->buffer.get()); } else { d->textureProvider->setBuffer(d->buffer.get()); } } } return d->textureProvider;} Treeland 使用 WQuickTextureProxy 创建窗口的代理显示,而其中就是获取 WSurfaceItem 的 textureProvider。 QSGTextureProvider *WQuickTextureProxy::textureProvider() const{ if (QQuickItem::isTextureProvider()) return QQuickItem::textureProvider(); W_DC(WQuickTextureProxy); if (!d->sourceItem) return nullptr; return d->sourceItem->textureProvider();} 这样多个 proxy 就可以显示同一个窗口的内容,比 QML 的 ShaderEffectSource 效率更高。 结尾上述仅仅是 Treeland 实现 Qt 和 wlroots 缝合的一部分流程,实际上对事件的处理就十分复杂,不止键盘输入,还需要处理光标、触控、触摸等其他设备。还有光标的绘制也需要区分硬光标和软光标,渲染画面时的硬件加速及软件实现等。 后续准备写一下光标相关的处理,以及还没介绍 Treeland 的画面是怎么绘制的。 相关文档 💡 Qt QPA https://doc.qt.io/qt-6/qpa.htmlQOffscreenSurface https://doc.qt.io/qt-6/qoffscreensurface.html","categories":[{"name":"技术分享","slug":"技术分享","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"}],"tags":[{"name":"C++, Wayland, 图形学","slug":"C-Wayland-图形学","permalink":"https://blog.justforlxz.com/tags/C-Wayland-%E5%9B%BE%E5%BD%A2%E5%AD%A6/"}]},{"title":"Treeland:DDE 进步的阶梯","slug":"treeland-go-go-go","date":"2024-11-01T04:00:00.000Z","updated":"2024-12-31T08:44:49.079Z","comments":true,"path":"2024/11/01/treeland-go-go-go/","permalink":"https://blog.justforlxz.com/2024/11/01/treeland-go-go-go/","excerpt":"","text":"回顾DDE 在 v15 时期,使用 Mutter 作为带合成器的窗管,以及 Metacity 这种不带合成器的窗管,一个是在高性能设备上使用,一个是为低性能设备上使用。 在 V20 时期,DDE 更换 KWin 当窗口管理器,由于 KWin 自带有关闭合成器的模式,所以 DDE 也放弃了 Metacity 作为备用窗管的选项。 MutterMutter 是 GNOME 开发的带有合成器功能的窗口管理器。 Mutter Metacity一个不带有合成器功能的窗口管理器。 Metacity KWinKWin 是 KDE 开发的,具有动态切换渲染后端,动态开关合成器功能的窗口管理器。 KWin 需求和指标在一个面向用户的产品中,拥有友好的界面是一个非常重要的事情,所以设计师给了一大堆非常好看的界面交互设计。 但更多的动画效果,以及更多的组件交互,通常就要求使用更多的资源,更多的内存,更高的功耗。 设计师说要动画华丽,要动画流畅,要优秀的用户体验。 架构师说要低资源占用,低内存,不能卡。 虽说一切目标都是为了用户体验,但两个大指标在实现上竟然是冲突的,那么如何平衡二者,就需要研发献祭一些头发了。 对比在Linux下目前有两个技术栈,一个是历史悠久的X11,另一个是较新的 Wayland。 接下来,跟随我一起来对比两个技术。 X11架构X11采用了比较老的客户端-服务器架构。应用程序通过X服务器与硬件设备进行通信,这种架构非常灵活,支持网络传输,使得远程显示变得可能。然而,X11协议复杂且过时,在很多方面不再符合现代的图形需求。 性能由于其设计较老,X11在一些现代场景下的性能表现不佳。它需要依赖很多扩展和补丁才能实现现代图形效果,例如合成管理器(Compositor)等。这些叠加的复杂性导致了额外的性能开销。 安全性X11的安全性问题比较明显,因为所有应用程序都可以访问彼此的窗口信息。这意味着一个恶意程序可以读取或干扰其他程序的输入输出。虽然可以通过一些扩展和工具加强安全性,但这并不是X11的设计初衷。 可扩展性和兼容性由于存在了几十年,X11有非常广泛的应用支持和兼容性。许多老的应用程序和桌面环境仍然依赖于X11协议,尤其是在远程桌面和某些专业领域。 输入设备支持由于其历史悠久,X11对各种输入设备的支持相当成熟。但随着新硬件和新输入技术的出现,X11的设计显得有些不灵活,特别是在多点触控和手势支持方面。 远程桌面和网络透明性X11的一个优势是其天然的网络透明性。它支持通过网络将显示内容远程传输,这是很多专业领域(如科学计算、服务器管理)中非常有用的功能。 Wayland架构Wayland采用的是更简单的设计,去掉了X服务器的复杂层次。消除了很多中间环节,提高了效率和响应速度。Wayland专注于本地显示,不像X11那样直接支持远程显示。 性能Wayland的设计更加现代化,它简化了渲染过程,减少了中间层,性能提升显著。特别是在动画和窗口操作方面,Wayland通常比X11更为流畅。 安全性Wayland在设计时考虑了安全性。应用程序之间的隔离更强,应用程序无法访问其他程序的图像或输入输出,这显著提高了系统的整体安全性。 可扩展性和兼容性尽管Wayland逐渐被主流桌面环境(如GNOME、KDE等)支持,但其应用生态仍然不如X11广泛。在某些老旧或专业的应用场景中,可能需要通过兼容层(如XWayland)来运行基于X11的应用程序。 输入设备支持Wayland针对现代硬件进行了优化,尤其是在多点触控、触摸板手势等方面有更好的支持。此外,它对显示器的DPI缩放、刷新率等也有更灵活的处理。 远程桌面和网络透明性Wayland默认没有X11那样的网络透明性功能。这意味着原生的远程桌面功能比较有限,尽管可以通过一些第三方工具或协议(如VNC、RDP)实现远程桌面,但这不是Wayland的核心功能。 总结总结下来,X11已经进入维护阶段,不再进行大幅更新。随着时间的推移,开发者和社区的注意力逐渐转移到Wayland上,X11可能会逐步淡出主流桌面环境。Wayland正在逐步成为Linux桌面的标准。随着越来越多的应用程序和桌面环境转向支持Wayland,其生态系统正在不断成熟和扩展。 架构设计从技术层面上,我们认为是时候更新技术方案了,曾经的X11+窗口管理器+合成器的模式,灵活但不满足需求,Wayland从底层就将三者融合在了一起,并且更新画面是以每幅完整的画面作为基础的,这确保了画面不会因为不同窗口更新界面的时机不一致导致画面撕裂。 更重要的一点,自研的窗口管理器,它是以实现DDE的需求为目的的,这是第三方窗口管理器不能比拟的。通常使用第三方的项目时,都要进行大量破坏性的调整,导致fork后的项目无法和上游同步,不能及时获取更新和修复,并且第三方的项目已经发展了很长时间,内部有许多DDE用不上、甚至冲突的功能,都需要进行大量调整,更加剧了维护成本。 所以 DDE 决定开发一个新的窗口管理器——Treeland。 Treeland在底层使用 wlroots 作为 Wayland 的基础库,不修改 wlroots 的代码,也就意味着可以随时同步上游进度,获得新的功能与修复。上层使用 Qt,可以充分利用公司内大量的 Qt 开发者,不再需要一直有专人负责特定项目,让DDE的技术栈更加统一。 该图描述了 Treeland 在整体上会使用哪些项目或接口完成功能。 Treeland 结构图 简单介绍一些 Treeland 里涉及的重要项目。 QWlroots wlroots 的 Qt 绑定,将 wayland 信号转换成 Qt 风格的信号。 Waylib 将 wlroots 中的组件封装成 QtQuick 对象,使用 QPA 为 Treeland 提供事件转换与分发。 DtkDeclarative DTK 的 QtQuick 组件,封装了大部分 DTK style 的控件。 在 Waylib中,会使用到 Qt 的 QPA 功能,将 wlroots 作为一个新的平台来处理一部分功能。 QPA 在 Treeland 中有着举足轻重的地位,来自系统底层的事件会先进入到 Waylib中,在 Waylib 里将事件转换成 Qt 内部事件,发送给上层。这样 Treeland 就可以在 QtQuick 中确定用户的点击位置、按键事件等行为。当用户点击的是窗口时,Treeland 还会通过 Waylib 生成一个事件,通过 seat 的接口发送给客户端,完成界面交互。 Treeland 处理底层事件与上层事件的流程 界面效果与优化Treeland 作为一个窗口管理器,最重要的功能还是对窗口的管理及显示效果的控制,Treeland 所有的窗口都带有圆角和阴影,以及一些窗口模糊效果。 圆角DDE及deepin社区应用都大量采用了窗口圆角的设计,由合成器提供圆角裁剪可以带来更加统一的界面设计。 红色为QtQuick圆角/黄色为Treeland圆角 QtQuick 圆角是由 Rectangle 组件提供的,它只能同时对四个角进行操作(红色块)。但 DTK 程序具备异形窗口的能力,所以 Treeland 提供了自己的裁圆角控件(黄色块)。新的造型算法、几何顶点数量比 QtQuick 原生的 Rectangle 减少50%,GPU顶点渲染和三角细分性能提升100%。采用新的抗锯齿算法,提高了GPU片元着色器性能,相比于普通 4xMSAA 抗锯齿算法,计算量减少1/4。 模糊 QtQuick 模糊控件 QtQuick 的模糊组件仅支持对控件自身进行模糊,这并不符合 Treeland 的需求。 Treeland 模糊控件 Treeland 重新实现了模糊组件,能从显存里获取组件下方图像数据,再使用融合的模糊算法优化性能。 阴影 Qt Quick 有一个 BorderImage 组件,它能以九宫格的方式,四个角保持不变,四边和中间的部分拉伸,来达到在组件底部充当装饰的效果。并通过 ImageProvider 的机制,手动控制图片资源的创建。 Treeland 使用 BorderImage 作为窗口阴影贴图,通过 ImageProvider 手动创建贴图材质,在相同大小下可复用同一份材质。 动画Treeland 直接使用 QtQuick 提供的动画组件,来为界面提供动画效果。 使用 State 和 Transition 为组件定义属性变化,例如窗口最大化和还原,两个 State 切换会触发不同的 Transition 执行属性变化,在属性变化时,使用 QtQuick 的动画组件完成动画播放。 多用户Treeland 作为解决方案的一部分,目的之一就是多用户共用合成器。在经典模式下,不同用户的切换需要在 tty 层面前端程序转移控制权,每个用户独占一个 tty 进行画面上屏。但切换tty所需的工作量不小,这导致切换时屏幕的缓冲区被不同的程序覆盖,给人的观感就是屏幕闪烁了一下,甚至是黑屏一会儿。 DDM 通过上图可以看出,LightDM模式下,每个用户拥有完整的一套进程组,都需要运行窗管、任务栏、文件管理器等。不同的用户会单独占用一个 tty,那么用户切换时,必然伴随着底层 DRM 以及显卡驱动等操作的切换,带来的结果就是会看到闪黑屏,而且两个用户都要跑一个锁屏界面来维持”假装是同一个界面“,也带来了跨用户进程的信息同步难题。 而 DDM 和 Treeland 重新设计了工作流程,将 Treeland 单独抽离出来,每个用户都通过相同的一套机制将窗口画面发送给 Treeland,而 Treeland 负责最终的画面合成以及上屏。 带来的好处显而易见,内存方面节省了窗管、锁屏等进程,切换用户也不会有黑屏闪烁,状态也不用想办法同步了。 一个简化的 DDM 与 Treeland 的多用户登录流程 与 systemd 的集成DDE 的每一个用户会话,都已切换至 systemd 服务,而非所有进程都挂载到会话入口的服务上。这样做有很多好处,包括快速重启桌面环境,而非注销再登录。远程桌面的铺垫,会话的启动不再局限于本地会话。 在 Treeland 模式下,DDE 会加载一个单独的服务,用于为用户会话注入显示环境变量。该服务使用 systemd 提供的 socket 机制实行懒加载,当 DDE 需要显示窗口时,即时向 Treeland 注册,完成用户显示服务的初始化。当 Treeland 崩溃重启时,该服务也会等待 Treeland 启动完成,并再次连接回 Treeland,确保用户侧的窗口能正常显示。 多用户登录时的基本流程 总结本文介绍了深度操作系统(DDE)在窗口管理器方面的演进,从早期使用Mutter和Metacity,到后来采用KWin,最终决定开发自己的窗口管理器Treeland。Treeland基于wlroots和Qt技术栈,实现了更好的性能和更统一的技术框架。 本文概述了 Treeland 的技术架构、界面效果优化(如圆角、模糊、阴影等),以及其在多用户场景下的优势。此外,还介绍了Treeland与systemd的集成,展示了DDE在系统架构和用户体验方面的持续创新和改进。","categories":[{"name":"技术分享","slug":"技术分享","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"}]},{"title":"Add macvlan net","slug":"Add-macvlan-net","date":"2024-04-15T05:10:41.000Z","updated":"2024-04-15T05:11:47.503Z","comments":true,"path":"2024/04/15/Add-macvlan-net/","permalink":"https://blog.justforlxz.com/2024/04/15/Add-macvlan-net/","excerpt":"","text":"[Unit]Description=Script to enable macvlan on boot[Service]Type=oneshotExecStartPre=-/usr/bin/bash -c 'mount --make-rshared /' # for k3s in lxc (optional)ExecStartPre=-/usr/bin/bash -c '/usr/bin/ip link del mac0'ExecStartPre=/usr/bin/bash -c '/usr/bin/ip link set eth0 up'ExecStartPre=/usr/bin/bash -c '/usr/bin/ip link add link eth0 mac0 type macvlan mode bridge'ExecStartPre=/usr/bin/bash -c '/usr/bin/ip addr add 192.168.1.11/24 dev mac0'ExecStartPre=/usr/bin/bash -c '/usr/bin/ip link set mac0 up'ExecStartPre=-/usr/bin/bash -c '/usr/bin/ip route add 192.168.1.0/24 dev mac0'ExecStartPre=/usr/bin/bash -c '/usr/bin/ip route add default via 192.168.1.1'ExecStartPre=/usr/bin/bash -c '/usr/bin/ip route add 198.18.0.0/16 via 192.168.1.1' # for fake ip (optional)ExecStartPre=/usr/bin/bash -c 'ip rule add to 192.168.1.0/24 priority 2500 lookup main'ExecStart=/usr/bin/bash -c '/usr/bin/echo done'Restart=on-failure[Install]WantedBy=multi-user.target","categories":[],"tags":[{"name":"k8s","slug":"k8s","permalink":"https://blog.justforlxz.com/tags/k8s/"}]},{"title":"solved run k8s in wsl2","slug":"solved-run-k8s-in-wsl2","date":"2024-03-01T03:30:11.000Z","updated":"2024-04-15T05:09:55.032Z","comments":true,"path":"2024/03/01/solved-run-k8s-in-wsl2/","permalink":"https://blog.justforlxz.com/2024/03/01/solved-run-k8s-in-wsl2/","excerpt":"","text":"When I use wsl2 join my k8s cluster, I have some problem. It can be start, but pod is not runing. Normal Scheduled 95m default-scheduler Successfully assigned longhorn-system/longhorn-csi-plugin-bvnmk to company-wsl-1Warning FailedCreatePodSandBox 4m kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to setup network for sandbox "a13a250444d61af44800db054a047059c66202ab9d8a47732748c7c4d380131b": plugin type="multus" failed (add): Multus: [longhorn-system/longhorn-csi-plugin-bvnmk/2203cd9f-608d-42b1-82b9-dad186a9b5cb]: error waiting for pod: Get "https://[10.43.0.1]:443/api/v1/namespaces/longhorn-system/pods/longhorn-csi-plugin-bvnmk?timeout=1m0s": dial tcp 10.43.0.1:443: i/o timeoutNormal SandboxChanged 2m (x3 over 8m1s) kubelet Pod sandbox changed, it will be killed and re-created.Warning FailedCreatePodSandBox 2m kubelet Failed to create pod sandbox: rpc error: code = Unknown desc = failed to reserve sandbox name "longhorn-csi-plugin-bvnmk_longhorn-system_2203cd9f-608d-42b1-82b9-dad186a9b5cb_2": name "longhorn-csi-plugin-bvnmk_longhorn-system_2203cd9f-608d-42b1-82b9-dad186a9b5cb_2" is reserved for "a13a250444d61af44800db054a047059c66202ab9d8a47732748c7c4d380131b" Looks like network problem, but when I open journal in wsl2, I see iptables-restore can not runing. Mar 01 11:26:59 DESKTOP-TH5NA7O k3s[223]: E0301 11:26:59.262472 223 proxier.go:1521] "Failed to execute iptables-restore" err=<Mar 01 11:26:59 DESKTOP-TH5NA7O k3s[223]: exit status 2: iptables-restore v1.8.7 (nf_tables): Couldn't load match `recent':No such file or directoryMar 01 11:26:59 DESKTOP-TH5NA7O k3s[223]:Mar 01 11:26:59 DESKTOP-TH5NA7O k3s[223]: Error occurred at line: 145Mar 01 11:26:59 DESKTOP-TH5NA7O k3s[223]: Try `iptables-restore -h' or 'iptables-restore --help' for more information.Mar 01 11:26:59 DESKTOP-TH5NA7O k3s[223]: >Mar 01 11:26:59 DESKTOP-TH5NA7O k3s[223]: I0301 11:26:59.262516 223 proxier.go:801] "Sync failed" retryingTime="30s" Now let’s fix! build before install depends. apt install -y git build-essential flex bison libssl-dev libelf-dev bc dwarves python3 clone wsl2 kernel code. git clone --depth 1 https://github.com/microsoft/WSL2-Linux-Kernel.git && cd WSL2-Linux-Kernel enable xt_recent. sed -i 's/# CONFIG_NETFILTER_XT_MATCH_RECENT is not set/CONFIG_NETFILTER_XT_MATCH_RECENT=y/' Microsoft/config-wsl build kernel. make -j2 KCONFIG_CONFIG=Microsoft/config-wsl cp kernel to some path. cp arch/x86/boot/bzImage /mnt/c/Users/<your-user-name>/wsl-kernel``5. Create a .wslconfig file in C:\\Users\\<your-user-name>\\```text[wsl2]kernel=C:\\\\Users\\\\<your-user-name>\\\\wsl-kernel reboot wsl wsl --shutdownwsl","categories":[],"tags":[{"name":"k8s","slug":"k8s","permalink":"https://blog.justforlxz.com/tags/k8s/"}]},{"title":"solved Could not find a declaration file for module 'vuex'.","slug":"solved-Could-not-find-a-declaration-file-for-module-vuex","date":"2023-09-11T02:14:56.000Z","updated":"2024-04-15T05:09:55.028Z","comments":true,"path":"2023/09/11/solved-Could-not-find-a-declaration-file-for-module-vuex/","permalink":"https://blog.justforlxz.com/2023/09/11/solved-Could-not-find-a-declaration-file-for-module-vuex/","excerpt":"","text":"Recently I was rewriting the email client using Vue and encountered an error when using Vuex to process data. Could not find a declaration file for module 'vuex'. Vuex does not have @types/vuex, so it can only be solved by adding d.ts manually. Add vuex.d.ts in the project directory and add the following content: declare module "vuex" { export * from "vuex/types/index.d.ts"; export * from "vuex/types/helpers.d.ts"; export * from "vuex/types/logger.d.ts"; export * from "vuex/types/vue.d.ts";}","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/categories/Vue/"},{"name":"Solution","slug":"Vue/Solution","permalink":"https://blog.justforlxz.com/categories/Vue/Solution/"}],"tags":[{"name":"vuex","slug":"vuex","permalink":"https://blog.justforlxz.com/tags/vuex/"}]},{"title":"solved react-native error: SDK iphoneos cannot be located","slug":"solved-react-native-error-SDK-iphoneos-cannot-be-located","date":"2023-06-29T01:40:13.000Z","updated":"2024-04-15T05:09:55.032Z","comments":true,"path":"2023/06/29/solved-react-native-error-SDK-iphoneos-cannot-be-located/","permalink":"https://blog.justforlxz.com/2023/06/29/solved-react-native-error-SDK-iphoneos-cannot-be-located/","excerpt":"","text":"While initializing the project, react native has the following error. patching file config.subchecking for a BSD-compatible install... /usr/bin/install -cchecking whether build environment is sane... yeschecking for arm-apple-darwin-strip... nochecking for strip... stripchecking for a thread-safe mkdir -p... ./install-sh -c -dchecking for gawk... nochecking for mawk... nochecking for nawk... nochecking for awk... awkchecking whether make sets $(MAKE)... yeschecking whether make supports nested variables... yeschecking for arm-apple-darwin-gcc... /Library/Developer/CommandLineTools/usr/bin/cc -arch arm64 -isysrootchecking whether the C compiler works... noxcrun: error: SDK "iphoneos" cannot be locatedxcrun: error: SDK "iphoneos" cannot be locatedxcrun: error: SDK "iphoneos" cannot be locatedxcrun: error: unable to lookup item 'Path' in SDK 'iphoneos'/Users/lxz/Library/Caches/CocoaPods/Pods/External/glog/2263bd123499e5b93b5efe24871be317-04b94/missing: Unknown `--is-lightweight' optionTry `/Users/lxz/Library/Caches/CocoaPods/Pods/External/glog/2263bd123499e5b93b5efe24871be317-04b94/missing --help' for more informationconfigure: WARNING: 'missing' script is too old or missingconfigure: error: in `/Users/lxz/Library/Caches/CocoaPods/Pods/External/glog/2263bd123499e5b93b5efe24871be317-04b94':configure: error: C compiler cannot create executablesSee `config.log' for more details✖ Installing CocoaPods dependencies (this may take a few minutes)error Looks like your iOS environment is not properly set. Please go to https://reactnative.dev/docs/environment-setup?os=macos&platform=android and follow the React Native CLI QuickStart guide for macOS and iOS.info Run CLI with --verbose flag for more details. Notice in the error that /Library/Developer/CommandLineTools/ is being used, which should use the path of xcode. works for me: sudo xcode-select --switch /Applications/Xcode.app switch to project directory. cd ios && npx pod install && cd .. && react-native run-ios","categories":[{"name":"ReactNative","slug":"ReactNative","permalink":"https://blog.justforlxz.com/categories/ReactNative/"},{"name":"Solution","slug":"ReactNative/Solution","permalink":"https://blog.justforlxz.com/categories/ReactNative/Solution/"}],"tags":[{"name":"reactnative","slug":"reactnative","permalink":"https://blog.justforlxz.com/tags/reactnative/"},{"name":"ios","slug":"ios","permalink":"https://blog.justforlxz.com/tags/ios/"}]},{"title":"开发一个 KWin 特效插件","slug":"How-to-develop-a-kwin-special-effects-plugin","date":"2023-06-25T05:25:55.000Z","updated":"2024-04-15T05:09:54.914Z","comments":true,"path":"2023/06/25/How-to-develop-a-kwin-special-effects-plugin/","permalink":"https://blog.justforlxz.com/2023/06/25/How-to-develop-a-kwin-special-effects-plugin/","excerpt":"","text":"KWin 是 KDE 开发的窗口管理器,提供了非常丰富的插件,可以对功能进行大量的定制。 本篇文章是对窗口特效插件的开发介绍。 插件开发插件定义KWin 的插件通常可以使用一些宏辅助生成代码,例如使用 KPluginFactory 进行插件的定义,内容是用来生成插件的入口类。 #define EffectPluginFactory_iid "org.kde.kwin.EffectPluginFactory" KWIN_PLUGIN_VERSION_STRING#define KWIN_PLUGIN_FACTORY_NAME KPLUGINFACTORY_PLUGIN_CLASS_INTERNAL_NAME#define KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED(className, jsonFile, supported, enabled ) \\ class KWIN_PLUGIN_FACTORY_NAME : public KWin::EffectPluginFactory \\ { \\ Q_OBJECT \\ Q_PLUGIN_METADATA(IID EffectPluginFactory_iid FILE jsonFile) \\ Q_INTERFACES(KPluginFactory) \\ public: \\ explicit KWIN_PLUGIN_FACTORY_NAME() {} \\ ~KWIN_PLUGIN_FACTORY_NAME() {} \\ bool isSupported() const override { \\ supported \\ } \\ bool enabledByDefault() const override { \\ enabled \\ } \\ KWin::Effect *createEffect() const override { \\ return new className(); \\ } \\ };#define KWIN_EFFECT_FACTORY_ENABLED(className, jsonFile, enabled ) \\ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED(className, jsonFile, return true;, enabled )#define KWIN_EFFECT_FACTORY_SUPPORTED(className, jsonFile, supported ) \\ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED(className, jsonFile, supported, return true; )#define KWIN_EFFECT_FACTORY(className, jsonFile ) \\ KWIN_EFFECT_FACTORY_SUPPORTED_ENABLED(className, jsonFile, return true;, return true; ) 大部分宏只是为了方便结构修改,我们只需要使用 K_PLUGIN_FACTORY 进行插件定义即可。 假设我们开发了一个插件,名字叫 demo,我们只需要在 main.cpp 中使用 KWIN_EFFECT_FACTORY_SUPPORTED 定义 KWIN_EFFECT_FACTORY_SUPPORTED( Demo, "metadata.json", return true;) 代码展开后是这样的。 class KPLUGINFACTORY_PLUGIN_CLASS_INTERNAL_NAME : public KWin::EffectPluginFactory \\{ Q_OBJECT Q_PLUGIN_METADATA(IID EffectPluginFactory_iid FILE "metadata.json") Q_INTERFACES(KPluginFactory)public: \\ explicit KPLUGINFACTORY_PLUGIN_CLASS_INTERNAL_NAME() {} ~KPLUGINFACTORY_PLUGIN_CLASS_INTERNAL_NAME() {} bool isSupported() const override { return true; } bool enabledByDefault() const override { return true; } KWin::Effect *createEffect() const override { return new Demo(); }}; 可以看到,其实 KWIN_EFFECT_FACTORY_SUPPORTED 只是为我们生成了工厂函数,辅助生成了一些必要的重载。 metadata.json 文件是用来作为插件的描述信息使用的。 { "KPlugin": { "Category": "Accessibility", "Description": "Allow clip of window content", "EnabledByDefault": true, "Id": "scissor", "License": "GPL", "Name": "ScissorWindow", "Name[zh_CN]": "窗口圆角" }, "org.kde.kwin.effect": { "enabledByDefaultMethod": true }} 特效插件特效插件是一类可以改变窗口画面的插件,例如我们可以在插件里对窗口进行贴图、变形和裁切,在 DDE 中,就使用特效插件完成了圆角裁切和窗口模糊。 这里使用圆角裁切插件作为例子,首先使用 KWIN_EFFECT_FACTORY_SUPPORTED 宏对插件进行定义, KWIN_EFFECT_FACTORY_SUPPORTED 接受一个 class 作为返回的接口类,它需要继承自 Effect,第二个参数是元信息的 json 文件,第三个参数是返回是否支持,在启用插件时可对当前环境进行判断,例如插件需要使用 OpenGL 对图形进行一些操作,但是当前环境不支持 OpenGL,那么插件就不会启用。 #include "scissorwindow.h"namespace KWin{KWIN_EFFECT_FACTORY_SUPPORTED(ScissorWindow, "metadata.json.stripped", return ScissorWindow::supported();)} // namespace KWin#include "main.moc" 在 Effect 类中有几个不同阶段的方法可以重载。 prePaintScreen 设置是否变换窗口或整个屏幕 更改将要绘制的屏幕区域 做各种内务处理任务,比如初始化你的效果变量 用于即将到来的绘画过程或更新动画的进度 paintScreen 在窗口上画东西(调用后画 effect->paintScreen()) 绘制多个桌面和/或同一桌面的多个副本 postPaintScreen 在动画的情况下安排下一次重绘,不应该在这里画任何东西。 prePaintWindow 启用或禁用窗口的绘制(例如启用最小化窗口的绘制) 将窗口设置为半透明 设置要转换的窗口 请求将窗口分成多个部分 paintWindow 做各种转换 改变窗口的不透明度 改变亮度和/或饱和度,如果支持的话 postPaintWindow 在动画的情况下为单个窗口安排下一次重绘 不应该在这里画任何东西。 paintEffectFrame 在绘制 EffectFrame 之前直接调用此方法。 如果需要绑定shader或者执行,可以实现这个方法帧渲染前的其他操作。 drawWindow 可以调用以绘制一个窗口的多个副本(例如缩略图)。 可以在这里改变窗口的不透明度/亮度/等,但不能做任何转换。 在基于 OpenGL 的合成中,框架确保上下文是最新的 在方法名称中可以看出,在场景及窗口绘制的过程中,分别可以在实际绘制的前后分别执行一些动作,圆角插件就是在 drawWindow 函数中,使用 OpenGL 对窗口使用着色器进行窗口裁切,并绘制到屏幕上。 void ScissorWindow::drawWindow(EffectWindow *w, int mask, const QRegion& region, WindowPaintData &data) { if (w->isDesktop() || isMaximized(w)) { return effects->drawWindow(w, mask, region, data); } QPointF cornerRadius; const QVariant valueRadius = w->data(WindowRadiusRole); if (valueRadius.isValid()) { cornerRadius = w->data(WindowRadiusRole).toPointF(); const qreal xMin{ std::min(cornerRadius.x(), w->width() / 2.0) }; const qreal yMin{ std::min(cornerRadius.y(), w->height() / 2.0) }; const qreal minRadius{ std::min(xMin, yMin) }; cornerRadius = QPointF(minRadius, minRadius); } if (cornerRadius.x() < 2 && cornerRadius.y() < 2) { return effects->drawWindow(w, mask, region, data); } const QString& key = QString("%1+%2").arg(cornerRadius.toPoint().x()).arg(cornerRadius.toPoint().y() ); if (!m_texMaskMap.count(key)) { QImage img(QSize(radius.x() * 2, radius.y() * 2), QImage::Format_RGBA8888); img.fill(QColor(0, 0, 0, 0)); QPainter painter(&img); painter.setPen(Qt::NoPen); painter.setBrush(QColor(255, 255, 255, 255)); painter.setRenderHint(QPainter::Antialiasing); painter.drawEllipse(0, 0, radius.x() * 2, radius.y() * 2); painter.end(); m_texMaskMap[key] = new GLTexture(img.copy(0, 0, radius.x(), radius.y())); m_texMaskMap[key]->setFilter(GL_LINEAR); m_texMaskMap[key]->setWrapMode(GL_CLAMP_TO_EDGE); } ShaderManager::instance()->pushShader(m_filletOptimizeShader); m_filletOptimizeShader->setUniform("typ1", 1); m_filletOptimizeShader->setUniform("sampler", 0); m_filletOptimizeShader->setUniform("msk1", 1); m_filletOptimizeShader->setUniform("k", QVector2D(w->width() / cornerRadius.x(), w->height() / cornerRadius.y())); if (w->hasDecoration()) { m_filletOptimizeShader->setUniform("typ2", 0); } else { m_filletOptimizeShader->setUniform("typ2", 1); } auto old_shader = data.shader; data.shader = m_filletOptimizeShader; glActiveTexture(GL_TEXTURE1); m_texMaskMap[key]->bind(); glActiveTexture(GL_TEXTURE0); effects->drawWindow(w, mask, region, data); ShaderManager::instance()->popShader(); data.shader = old_shader; glActiveTexture(GL_TEXTURE1); m_texMaskMap[key]->unbind(); glActiveTexture(GL_TEXTURE0); return;} 如果窗口是桌面类型,或者已经最大化了,则无需处理,直接返回 Effect 原本的处理函数。 之后尝试从窗口属性中取出圆角大小的值,如果没有设置圆角大小,或者值小于2,则无需处理。 尝试查询缓存,在这里为窗口的四个角构建一份遮罩对象并缓存,使用 OpenGL 将遮罩和着色器进行关联,激活两个材质分别绘制窗口内容和四个角的遮罩,在着色器中完成窗口圆角的半透明效果。 限于篇幅,本文不展开介绍如何实现圆角插件的全部实现过程,仅挑选关键步骤。 安装将动态库复制到 /usr/share/kwin/effects/plugins ,并使用 DBus 激活插件。 qdbus --literal org.kde.KWin /Effects org.kde.kwin.Effects.loadEffect scissor","categories":[{"name":"deepin","slug":"deepin","permalink":"https://blog.justforlxz.com/categories/deepin/"}],"tags":[{"name":"deepin","slug":"deepin","permalink":"https://blog.justforlxz.com/tags/deepin/"},{"name":"kwin","slug":"kwin","permalink":"https://blog.justforlxz.com/tags/kwin/"}]},{"title":"How to count the cpu usage of a process","slug":"How-to-count-the-cpu-usage-of-a-process","date":"2023-05-24T02:43:04.000Z","updated":"2024-04-15T05:09:54.913Z","comments":true,"path":"2023/05/24/How-to-count-the-cpu-usage-of-a-process/","permalink":"https://blog.justforlxz.com/2023/05/24/How-to-count-the-cpu-usage-of-a-process/","excerpt":"","text":"Sometimes we want to collect the cpu usage of the process, and usually use a script to make simple statistics. Under Linux, the cpu time slice of the process can be obtained through the stat provided by procfs. Or simply use some commands to get directly. #!/bin/bashFULL_PATH=$1$@ > /dev/null 2>&1 &while true ; do PID=$(pidof ${FULL_PATH}) if [ -z "$PID" ]; then exit 0 fi echo "$(date) :: $FULL_PATH[$PID] $(ps -C ${FULL_PATH} -o %cpu | tail -1)%" sleep 0.5done","categories":[{"name":"Solution","slug":"Solution","permalink":"https://blog.justforlxz.com/categories/Solution/"}],"tags":[{"name":"Bash","slug":"Bash","permalink":"https://blog.justforlxz.com/tags/Bash/"},{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"How to Solve the Module not found: Cannot resolve 'fs' error in Next.js","slug":"How-to-Solve-the-Module-not-found-Cannot-resolve-fs-error-in-Next-js","date":"2023-04-19T05:58:21.000Z","updated":"2024-04-15T05:09:54.913Z","comments":true,"path":"2023/04/19/How-to-Solve-the-Module-not-found-Cannot-resolve-fs-error-in-Next-js/","permalink":"https://blog.justforlxz.com/2023/04/19/How-to-Solve-the-Module-not-found-Cannot-resolve-fs-error-in-Next-js/","excerpt":"","text":"Webpack 4 versionTo resolve the error with Webpack 4, you need to tell webpack to set the module to ‘empty’ on the client-side (!isServer). This is also a solution when you are working with older Next.js versions. The configuration essentially tells Webpack to create an empty module for fs, which effectively suppresses the error. Update your next.config.js with the following: module.exports = { webpack: (config, { isServer }) => { if (!isServer) { // set 'fs' to an empty module on the client to prevent this error on build --> Error: Can't resolve 'fs' config.node = { fs: "empty", }; } return config; },}; Webpack 5 versionTo resolve the error with Webpack 5, you need to tell webpack not to resolve the module on the client-side (!isServer). Update your next.config.js with the following: module.exports = { webpack: (config, { isServer }) => { if (!isServer) { // don't resolve 'fs' module on the client to prevent this error on build --> Error: Can't resolve 'fs' config.resolve.fallback = { fs: false, }; } return config; },};","categories":[{"name":"Solution","slug":"Solution","permalink":"https://blog.justforlxz.com/categories/Solution/"}],"tags":[{"name":"Next.js","slug":"Next-js","permalink":"https://blog.justforlxz.com/tags/Next-js/"},{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/tags/Web/"}]},{"title":"Kitty configuration for macOS","slug":"Kitty-configuration-for-macOS","date":"2023-04-17T08:39:51.000Z","updated":"2024-04-15T05:09:54.915Z","comments":true,"path":"2023/04/17/Kitty-configuration-for-macOS/","permalink":"https://blog.justforlxz.com/2023/04/17/Kitty-configuration-for-macOS/","excerpt":"","text":"I’ve been using Kitty for a couple of days. I use it because there is a plugin for nvim that can seamlessly switch focus with kitty, so I don’t need to repeat the settings, I like it very much. After a period of use. I have completed part of the configuration, and now I want to share it. I use different folders for related functions. Such as themes, tabs, windows, and shortcuts. # lxz @ lxzMacBook-Pro in ~/.dot/kitty/.config/kitty on git:master o [16:38:58]$ tree.├── kitty.conf├── kitty.d│ ├── init│ │ └── init.conf│ ├── keybind│ │ ├── init.conf│ │ ├── nvim.conf│ │ ├── tab.conf│ │ └── window.conf│ ├── session│ │ └── init.conf│ └── theme│ ├── background.conf│ ├── color.conf│ ├── font.conf│ ├── tabbar.conf│ └── window.conf└── session.conf6 directories, 13 files Some screenshotsession (look at the lower left corner) multi splits Base settingsIn kitty.conf, I just set to load configuration files in other directories. globinclude kitty.d/**/*.conf I won’t show the configuration after splitting, just give a hint according to the function. InitIn init.conf, I set some default variables. term xterm-256colorshell_integration enabledallow_hyperlinks yeseditor nvim Theme settingsTabs settingsIn tabs settings, I like powerline style. tab_bar_style powerline Windows settingswindow_border_width 0.5ptwindow_resize_step_cells 2window_resize_step_lines 2initial_window_width 640initial_window_height 400draw_minimal_borders yesinactive_text_alpha 0.7hide_window_decorations nomacos_titlebar_color backgroundmacos_thicken_font 0.75active_border_color none# default layout is vertical splits onlyenabled_layouts splitsenable_audio_bell no Fonts settingsfont_family FiraCode Nerd Font Mono Retinafont_size 16.0 Color settings# Dark One Nuanced by ariasuni, https://store.kde.org/p/1225908# Imported from KDE .colorscheme format by thematdev, https://thematdev.org# For migrating your schemes from Konsole format see# https://git.thematdev.org/thematdev/konsole-scheme-migration# importing Backgroundbackground #282c34# importing BackgroundFaint# importing BackgroundIntense# importing Color0color0 #3f4451# importing Color0Faintcolor16 #282c34# importing Color0Intensecolor8 #4f5666# importing Color1color1 #e06c75# importing Color1Faintcolor17 #c25d66# importing Color1Intensecolor9 #ff7b86# importing Color2color2 #98c379# importing Color2Faintcolor18 #82a566# importing Color2Intensecolor10 #b1e18b# importing Color3color3 #d19a66# importing Color3Faintcolor19 #b38257# importing Color3Intensecolor11 #efb074# importing Color4color4 #61afef# importing Color4Faintcolor20 #5499d1# importing Color4Intensecolor12 #67cdff# importing Color5color5 #c678dd# importing Color5Faintcolor21 #a966bd# importing Color5Intensecolor13 #e48bff# importing Color6color6 #56b6c2# importing Color6Faintcolor22 #44919a# importing Color6Intensecolor14 #63d4e0# importing Color7color7 #e6e6e6# importing Color7Faintcolor23 #c8c8c8# importing Color7Intensecolor15 #ffffff# importing Foregroundforeground #abb2bf# importing ForegroundFaint# importing ForegroundIntense# importing General Shortcuts settingsInit# clear the terminal screenmap cmd+k combine : clear_terminal scrollback active : send_text normal,application \\x0c# jump to beginning and end of wordmap alt+left send_text all \\x1b\\x62map alt+right send_text all \\x1b\\x66# jump to beginning and end of linemap cmd+left send_text all \\x01map cmd+right send_text all \\x05# Map cmd + <num> to corresponding tabsmap cmd+1 goto_tab 1map cmd+2 goto_tab 2map cmd+3 goto_tab 3map cmd+4 goto_tab 4map cmd+5 goto_tab 5map cmd+6 goto_tab 6map cmd+7 goto_tab 7map cmd+8 goto_tab 8map cmd+9 goto_tab 9# changing font sizesmap cmd+equal change_font_size all +2.0map cmd+minus change_font_size all -2.0map cmd+0 change_font_size all 0map cmd+c copy_to_clipboardmap cmd+v paste_from_clipboard Tabmap alt+1 goto_tab 1map alt+2 goto_tab 2map alt+3 goto_tab 3map alt+4 goto_tab 4map alt+5 goto_tab 5map alt+6 goto_tab 6map alt+7 goto_tab 7map alt+8 goto_tab 8map alt+9 goto_tab 9map alt+0 goto_tab 0# open new tab with cmd+tmap cmd+t new_tab# switch between next and previous splitsmap cmd+] next_windowmap cmd+[ previous_window Window# open new split (window) with cmd+d retaining the cwdmap cmd+w close_windowmap cmd+shif+n new_os_windowmap cmd+d launch --location=hsplit --cwd=currentmap cmd+shift+d launch --location=vsplit --cwd=current Neovimmap ctrl+j kitten pass_keys.py neighboring_window bottom ctrl+j "^.* - nvim$"map ctrl+k kitten pass_keys.py neighboring_window top ctrl+k "^.* - nvim$"map ctrl+h kitten pass_keys.py neighboring_window left ctrl+h "^.* - nvim$"map ctrl+l kitten pass_keys.py neighboring_window right ctrl+l "^.* - nvim$"# the 3 here is the resize amount, adjust as neededmap alt+j kitten pass_keys.py relative_resize down 3 alt+j "^.* - nvim$"map alt+k kitten pass_keys.py relative_resize up 3 alt+k "^.* - nvim$"map alt+h kitten pass_keys.py relative_resize left 3 alt+h "^.* - nvim$"map alt+l kitten pass_keys.py relative_resize right 3 alt+l "^.* - nvim$" Moving in shell and nvim. SessionKitty supports session management, I added some default sessions, and opened session sockets for nvim. The session.conf at the root is the location configuration of the session. new_tab homelayout splitscd ~launch zshfocusnew_tab workcd ~/Develop/linuxdeepin/launch zshnew_tab nvimcd ~/.config/nvimlaunch zsh Initstartup_session session.conf# Other unix systems:allow_remote_control yeslisten_on unix:/tmp/.kitty","categories":[{"name":"development tools","slug":"development-tools","permalink":"https://blog.justforlxz.com/categories/development-tools/"}],"tags":[{"name":"Kitty","slug":"Kitty","permalink":"https://blog.justforlxz.com/tags/Kitty/"}]},{"title":"How to solve parallels desktop linux usb problem","slug":"How-to-solve-parallels-desktop-linux-usb-problem","date":"2023-04-11T04:59:39.000Z","updated":"2024-04-15T05:09:54.914Z","comments":true,"path":"2023/04/11/How-to-solve-parallels-desktop-linux-usb-problem/","permalink":"https://blog.justforlxz.com/2023/04/11/How-to-solve-parallels-desktop-linux-usb-problem/","excerpt":"","text":"I am runing a linux virtual machine macos using parallels desktop. One day when I booted normally, I got a usb error and couldn’t use the keyboard and mouse in virtual machine. An error messages like this is output on the screen. usb 3-1: can't set config #1, error -62xhci_hcd Error while assigning device slot ID: Command Aborted.xhci_hcd Max number of devices this xHCI host supports is 32.usb usb3-port2: couldn't allocate usb_device Finally I found a solution on the parallels desktop forum. Start your virtual machine. Press e to edit the grub menu during the boot phase, passing a new parameter to the kernel. Append xhci_hcd.quirks=0x40 after quiet F10 booting the kernel After entering the system, open the terminal and edit the /etc/default/grub file Replace that line with the following line: GRUB_CMDLINE_LINUX_DEFAULT=”quiet xhci_hcd.quirks=0x40” Next, execute the following command: sudo update-grub","categories":[{"name":"Solution","slug":"Solution","permalink":"https://blog.justforlxz.com/categories/Solution/"}],"tags":[{"name":"parallels desktop","slug":"parallels-desktop","permalink":"https://blog.justforlxz.com/tags/parallels-desktop/"}]},{"title":"How to remove all Terminating pods","slug":"How-to-remove-all-Terminating-pods","date":"2023-03-15T06:47:59.000Z","updated":"2024-04-15T05:09:54.914Z","comments":true,"path":"2023/03/15/How-to-remove-all-Terminating-pods/","permalink":"https://blog.justforlxz.com/2023/03/15/How-to-remove-all-Terminating-pods/","excerpt":"","text":"Sometimes all pods of k8s will be in Terminating state, use this command to clean up all pods. kubectl get pods --all-namespaces | grep Terminating | while read line; do pod_name=$(echo $line | awk '{print $2}' ) \\ name_space=$(echo $line | awk '{print $1}' ); \\ kubectl delete pods $pod_name -n $name_space --grace-period=0 --forcedone","categories":[{"name":"Solution","slug":"Solution","permalink":"https://blog.justforlxz.com/categories/Solution/"}],"tags":[{"name":"k8s","slug":"k8s","permalink":"https://blog.justforlxz.com/tags/k8s/"}]},{"title":"solved duplicate hostname or contents","slug":"solved-duplicate-hostname-or-contents","date":"2023-02-01T06:48:21.000Z","updated":"2024-04-15T05:09:55.028Z","comments":true,"path":"2023/02/01/solved-duplicate-hostname-or-contents/","permalink":"https://blog.justforlxz.com/2023/02/01/solved-duplicate-hostname-or-contents/","excerpt":"","text":"Feb 01 05:40:37 fv-az406-375 k3s[5352]: time="2023-02-01T05:40:37Z" level=info msg="Waiting to retrieve agent configuration; server is not ready: Node password rejected, duplicate hostname or contents of '/etc/rancher/node/password' may not match server node-passwd entry, try enabling a unique node name with the --with-node-id flag" When you join a cluster, you are reminded that it already exists, but the same node does not exist in the cluster. you can do this. kubectl -n kube-system delete secrets <node name>.node-password.k3s example: first, we list all node to check, if the node exists, we should not continue the operation, we need to modify the name of the node that is joining the cluster to avoid conflicts with existing ones. kubectl get node rpi4 Ready control-plane,master 2d3h v1.25.6+k3s1 Now the homenas-vm node does not exist. But the logs tell us that the cluster already has the password, it doesn’t match the current one. Then we need to manually delete the old password in the cluster and let the new node join. you can use this command to show all secrets, the node password in here. kubectl get -n kube-system secrets then you will see all secrets. NAME TYPE DATA AGEcompany-laptop.node-password.k3s Opaque 1 2d1hcompany-pc.node-password.k3s Opaque 1 2d2hhomenas-vm.node-password.k3s Opaque 1 50mk3s-serving kubernetes.io/tls 2 2d3hrpi4.node-password.k3s Opaque 1 2d3hsh.helm.release.v1.traefik-crd.v1 helm.sh/release.v1 1 2d3hsh.helm.release.v1.traefik.v1 helm.sh/release.v1 1 2d3h if the homenas-vm is invalid, we need to delete it manually. kubectl -n kube-system delete secrets homenas-vm.node-password.k3s it’s done!","categories":[{"name":"Solution","slug":"Solution","permalink":"https://blog.justforlxz.com/categories/Solution/"}],"tags":[]},{"title":"how to use nvim dap to debug cpp","slug":"how-to-use-nvim-dap-to-debug-cpp","date":"2022-12-08T02:16:45.000Z","updated":"2024-04-15T05:09:54.994Z","comments":true,"path":"2022/12/08/how-to-use-nvim-dap-to-debug-cpp/","permalink":"https://blog.justforlxz.com/2022/12/08/how-to-use-nvim-dap-to-debug-cpp/","excerpt":"","text":"在之前我已经分享过了一份简单的 nvim 配置,它已经实现了编程所需的智能提示,语法高亮,代码跳转等功能,今天我打算整一下 nvim 的调试框架 dap。 dap 是一个框架,客户端负责在 nvim 上显示各种调试信息,比如显示断点、调用栈、对象内存信息等,服务端则提供客户端所需的功能,服务端通常是一个调试器,或者是调试器包装。 本篇会用到 Mason 这个插件去安装 dap 的服务端,本篇不会展开 Mason,将来有机会详细说一下。 首先先看几张正常工作的图: 运行界面 查看变量信息 快捷键 函数调用栈 安装 dap在 Mason 的安装列表中添加上 codelldb,codelldb 是 vscode 用的调试服务端,负责给 vscode 提供调试信息,有了这个后端,我们就可以方便的实现和 vscode 相同的调试功能。 配置 dap在 plugins 目录下新建 _dap.lua 文件。 return { "mfussenegger/nvim-dap", opt = true, module = { "dap" }, requires = { { "theHamsta/nvim-dap-virtual-text", module = { "nvim-dap-virtual-text" }, }, { "rcarriga/nvim-dap-ui", module = { "dapui" }, }, "nvim-telescope/telescope-dap.nvim", { "jbyuki/one-small-step-for-vimkind", module = "osv", }, }, config = function() require("config.dap").setup() end, disable = false,} 有些人会在 packer 里用 use 安装,把 return 改成 use 就可以了。 packer 的代码已经写好了,现在写 config 函数,在我的例子中,我把文件放在了 lua/config/dap/ 目录下,因为要配置不同的语言,这样会方便管理一些。 首先要先在 dap 目录下新建一个 init.lua,这里是模块入口,初始化的工作从这里开始。 local M = {}local function configure()endlocal function configure_exts()endlocal function configure_debuggers()endfunction M.setup() configure() -- Configuration configure_exts() -- Extensions configure_debuggers() -- Debuggerendconfigure_debuggers()return M 在 _dap.lua 中调用了 require("config.dap").setup(),这个 setup 函数就是 config/dap/init.lua 中的 M.setup() 函数。 目前只是写了一个壳子,现在让我们正式配置它吧。 快捷键在 nvim 中进行调试,界面显然还是在终端里的,所以我们要使用快捷键进行一些操作,比如标记断点、单步进入、跳出等。 在 config/dap/keymaps.lua 中进行快捷键的配置。 local M = {}local whichkey = require "which-key"-- local legendary = require "legendary"-- local function keymap(lhs, rhs, desc)-- vim.keymap.set("n", lhs, rhs, { silent = true, desc = desc })-- endfunction M.setup() local keymap = { d = { name = "DAP", R = { "<cmd>lua require'dap'.run_to_cursor()<cr>", "Run to Cursor" }, E = { "<cmd>lua require'dapui'.eval(vim.fn.input '[Expression] > ')<cr>", "Evaluate Input" }, C = { "<cmd>lua require'dap'.set_breakpoint(vim.fn.input '[Condition] > ')<cr>", "Conditional Breakpoint" }, U = { "<cmd>lua require'dapui'.toggle()<cr>", "Toggle UI" }, b = { "<cmd>lua require'dap'.step_back()<cr>", "Step Back" }, c = { "<cmd>lua require'dap'.continue()<cr>", "Continue" }, d = { "<cmd>lua require'dap'.disconnect()<cr>", "Disconnect" }, e = { "<cmd>lua require'dapui'.eval()<cr>", "Evaluate" }, g = { "<cmd>lua require'dap'.session()<cr>", "Get Session" }, h = { "<cmd>lua require'dap.ui.widgets'.hover()<cr>", "Hover Variables" }, S = { "<cmd>lua require'dap.ui.widgets'.scopes()<cr>", "Scopes" }, i = { "<cmd>lua require'dap'.step_into()<cr>", "Step Into" }, o = { "<cmd>lua require'dap'.step_over()<cr>", "Step Over" }, p = { "<cmd>lua require'dap'.pause.toggle()<cr>", "Pause" }, q = { "<cmd>lua require'dap'.close()<cr>", "Quit" }, r = { "<cmd>lua require'dap'.repl.toggle()<cr>", "Toggle Repl" }, s = { "<cmd>lua require'dap'.continue()<cr>", "Start" }, t = { "<cmd>lua require'dap'.toggle_breakpoint()<cr>", "Toggle Breakpoint" }, x = { "<cmd>lua require'dap'.terminate()<cr>", "Terminate" }, u = { "<cmd>lua require'dap'.step_out()<cr>", "Step Out" }, }, } local opts = { mode = "n", prefix = "<leader>", buffer = nil, silent = true, noremap = true, nowait = false, } whichkey.register(keymap, opts) --- require("legendary.integrations.which-key").bind_whichkey(keymap, opts, false) local keymap_v = { d = { name = "Debug", e = { "<cmd>lua require'dapui'.eval()<cr>", "Evaluate" }, }, } opts = { mode = "v", prefix = "<leader>", buffer = nil, silent = true, noremap = true, nowait = false, } whichkey.register(keymap_v, opts) --- require("legendary.integrations.which-key").bind_whichkey(keymap_v, opts, false)endreturn M 在这里我将快捷键绑定在了 <leader> d 上面。 现在返回到 init.lua 中,在 setup 函数中调用 keymaps。 function M.setup() require("config.dap.keymaps").setup() -- Keymapsend dapuidapui 是一个美化 dap 界面的插件,通常大家都会配置的吧! local function configure_exts() require("nvim-dap-virtual-text").setup({ commented = true, }) local dap, dapui = require("dap"), require("dapui") dapui.setup({ expand_lines = true, icons = { expanded = "", collapsed = "", circular = "" }, mappings = { -- Use a table to apply multiple mappings expand = { "<CR>", "<2-LeftMouse>" }, open = "o", remove = "d", edit = "e", repl = "r", toggle = "t", }, layouts = { { elements = { { id = "scopes", size = 0.33 }, { id = "breakpoints", size = 0.17 }, { id = "stacks", size = 0.25 }, { id = "watches", size = 0.25 }, }, size = 0.33, position = "right", }, { elements = { { id = "repl", size = 0.45 }, { id = "console", size = 0.55 }, }, size = 0.27, position = "bottom", }, }, floating = { max_height = 0.9, max_width = 0.5, -- Floats will be treated as percentage of your screen. border = vim.g.border_chars, -- Border style. Can be 'single', 'double' or 'rounded' mappings = { close = { "q", "<Esc>" }, }, }, }) -- use default dap.listeners.after.event_initialized["dapui_config"] = function() dapui.open({}) end dap.listeners.before.event_terminated["dapui_config"] = function() dapui.close({}) end dap.listeners.before.event_exited["dapui_config"] = function() dapui.close({}) endend 配置基本上大家都没差多少,说不定都是从一个人的配置里搬运的。 配置 icon我还修改了几个默认的 icon,在 configure 函数里。 local function configure() local dap_breakpoint = { breakpoint = { text = "", texthl = "LspDiagnosticsSignError", linehl = "", numhl = "", }, rejected = { text = "", texthl = "LspDiagnosticsSignHint", linehl = "", numhl = "", }, stopped = { text = "", texthl = "LspDiagnosticsSignInformation", linehl = "DiagnosticUnderlineInfo", numhl = "LspDiagnosticsSignInformation", }, } vim.fn.sign_define("DapBreakpoint", dap_breakpoint.breakpoint) vim.fn.sign_define("DapStopped", dap_breakpoint.stopped) vim.fn.sign_define("DapBreakpointRejected", dap_breakpoint.rejected)end 断点标记 单步停止 配置客户端现在还差一个客户端的函数没有写,在这里只是为了调用针对不同语言设置的服务端,内容也非常的简单。 新建一个 config/dap/cpp.lua,在里面配置 c++ 相关的参数就行了,需要注意的是,codelldb 可以调试 c、c++、rust 等语言,就不会再拆分成更精细的文件了。 local M = {}function M.setup() -- local dap_install = require "dap-install" -- dap_install.config("codelldb", {}) local dap = require("dap") local install_root_dir = vim.fn.stdpath("data") .. "/mason" local extension_path = install_root_dir .. "/packages/codelldb/extension/" local codelldb_path = extension_path .. "adapter/codelldb" dap.adapters.codelldb = { type = "server", port = "${port}", executable = { command = codelldb_path, args = { "--port", "${port}" }, -- On windows you may have to uncomment this: -- detached = false, }, } dap.configurations.cpp = { { name = "Launch file", type = "codelldb", request = "launch", program = function() return vim.fn.input("Path to executable: ", vim.fn.getcwd() .. "/", "file") end, cwd = "${workspaceFolder}", stopOnEntry = true, }, } dap.configurations.c = dap.configurations.cpp dap.configurations.rust = dap.configurations.cppendreturn M Mason 在这里终于露面了,但是我们只是看到查找了 Mason 安装 codelldb 的路径而已。 配置的内容是固定的,设置一下执行文件的路径和参数,设置一下调试这个语言所需的启动参数,这里默认给了一个输入可执行文件路径启动调试的简单方法。 配置 launch.json上面的内容就已经足够调试 c++ 程序了,但是 dap 还支持 vscode 的 launch.json,将启动配置作为固定模板填入启动调试的列表,并且在 launch.json 中我们还可以控制程序的环境变量,启动参数等,会比较方便一些。 dap 支持这个只需要在 setup 函数加上一行代码就足够了。 require("dap.ext.vscode").load_launchjs(nil, { codelldb = { "c", "cpp", "rust" } }) 这句话的意思是 launch.json 中的类型是 codelldb 时,使用 c、cpp、rust 的调试配置,而上面我们配置了 codelldb 的参数 和 cpp 的参数,而且还将 cpp 的配置复制给了 c 和 rust。 但是有一个需要注意的地方,launch.json 现在环境变量换成了 environment 字段,并且结构也发生了变化,dap 目前只支持 env 字段,我在考虑贡献一个 pr 做一个自动转换。 这里给一个 launch.json 的例子: { "version": "0.2.0", "configurations": [ { "name": "(codelldb) Launch", "type": "codelldb", "request": "launch", "program": "./build/bin/deepin-kwin_x11", "args": [ "--replace" ], "stopAtEntry": true, "cwd": "${workspaceFolder}", "env": { "DISPLAY": ":0", "PATH": "${workspaceFolder}/build/bin:$PATH", "XDG_CURRENT_DESKTOP": "Deepin", "QT_PLUGIN_PATH": "${workspaceFolder}/build", "QT_LOGGING_RULES": "kwin_*.debug=true" }, "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "Enable pretty-printing for gdb", "text": "-enable-pretty-printing", "ignoreFailures": true } ] } ]} 需要注意的是,这里的 codelldb 其实是一个标识字符串,vscode 默认提供的 type 是 cppgdb,我们也可以改成相同的字段。 想要在线查看最终文件内容,可以看下面几个链接: cpp.lua init.luakeymaps.lua_dap.lua","categories":[{"name":"neovim","slug":"neovim","permalink":"https://blog.justforlxz.com/categories/neovim/"}],"tags":[{"name":"neovim","slug":"neovim","permalink":"https://blog.justforlxz.com/tags/neovim/"},{"name":"dap","slug":"dap","permalink":"https://blog.justforlxz.com/tags/dap/"}]},{"title":"1. init project","slug":"photo-1-init-project","date":"2022-11-07T08:38:31.000Z","updated":"2024-04-15T05:09:55.027Z","comments":true,"path":"2022/11/07/photo-1-init-project/","permalink":"https://blog.justforlxz.com/2022/11/07/photo-1-init-project/","excerpt":"","text":"这是一个系列的文章,用来记录我的相册应用的开发过程,内容可能会比较枯燥,还请读者见谅。 我使用 NextCloud 作为我的存储中心,但是当我备份相册的时候,我感到了莫名的蛋疼,实在是太难用了,不支持相册,不支持标记,不支持各种视图,所以我决定自己写一个新的客户端,只提供相册功能。 技术选型我用的是 iPhone 12,理所当然我会选择 iOS 客户端开发,在我面前有这么几种方案可以选择: object-c swift swiftui flutter qml react-native 本着三短一长选最长的原则,我计划使用 react-native 作为项目的技术方案。 object-c 已经很老旧了,我只是想业余时间做一个应用满足自己的需求,排除。 swfit 和 swiftui 是苹果目前主推的,特别是 swfitui,用来写界面真的很方便,但是我不想学新的,排除。 flutter 是谷歌在推的一个框架,从我的研发角度来看,flutter 和 qml 没有什么本质区别,都是自己实现了绘制,在此基础上完善控件等高级功能,既然我是一个 Qt 开发者,我肯定不会选择再去学一套类似的技术了,排除。 qml,Qt 目前主推的界面开发框架,采用 JSON like 的方式描述界面,并且可以运行一部分的标准 js 语法,配合 C++ 在 native 端提供本地功能,Qt 自己是一套平台一样的框架,用起来很爽,但是我不想写 qml,排除。 最终就只能用 react native 了,我个人想学一下前端开发,使用相关的技术栈对我来说性价比最高,选择。 为了写 js 而找了这么多借口( 功能设计确定了技术方案,就需要考虑实现哪些功能了,作为一个相册 App,它最基本的功能肯定是 能启动,嗯,看图。 浏览服务器和本地的图片 上传和下载图片 相册分类 标记信息 查看文件详情 一开始先不考虑那么多,做一个基本的 TimeLine 样式就可以了。 界面设计功能也已经确定了,先实现一个 TimeLine 的功能,那么就要确定界面的样式了,我个人比较欣赏 Google Photo 的设计,打算就按像素复制了。 初始化项目现在可以考虑怎么写代码了,首先初始化一下项目。 npx react-native init photos --template react-native-template-typescript 初始化一个带有 typescript 的 react native 项目,然后在 vscode 里安装一些 react 和 react native 相关的插件就可以了。 删掉初始化项目的 App.tsx 和相关的文件,新建 src 目录和 App.tsx 文件,开始写新的界面。","categories":[{"name":"photos 开发笔记","slug":"photos-开发笔记","permalink":"https://blog.justforlxz.com/categories/photos-%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"}],"tags":[{"name":"typescript","slug":"typescript","permalink":"https://blog.justforlxz.com/tags/typescript/"},{"name":"react","slug":"react","permalink":"https://blog.justforlxz.com/tags/react/"},{"name":"react native","slug":"react-native","permalink":"https://blog.justforlxz.com/tags/react-native/"}]},{"title":"docker-buildx-multi-arch-images","slug":"docker-buildx-multi-arch-images","date":"2022-08-25T05:32:03.000Z","updated":"2024-04-15T05:09:54.959Z","comments":true,"path":"2022/08/25/docker-buildx-multi-arch-images/","permalink":"https://blog.justforlxz.com/2022/08/25/docker-buildx-multi-arch-images/","excerpt":"","text":"最近一直在搞 github 的 ci,为了方便公司的开发快速修复其他发行版上的构建问题,我研究了一下 distrobox 启动容器来作为本地验证环境的可行性,结果发现还不错,就顺手做了几个镜像。 v23 的仓库是支持多个架构的,想着顺手做一份 v23 的镜像提交到 docker hub 上,结果遇到了一点多平台的坑。 docker buildxDocker Buildx是一个CLI插件,它扩展了Docker命令,完全支持 Moby BuildKit 构建工具包提供的特性。它提供了与docker 构建相同的用户体验,提供了许多新特性,比如创建作用域的构建器实例和同时针对多个节点构建。 Moby BuildKit 构建工具提供了一些诸如跨平台启动的功能。 首次尝试最开始我是打算使用 docker import 直接把 base.tgz 导入进去的,docker import 支持 –platform 参数指定架构,我就跑了两遍,生成了 linux/arm64 和 linux/amd64 两个架构的。 结果我看到 docker images 里只有一份,而且用 docker image inspect beige:base 查看发现里面的 Architecuture 只有 amd64,推送到 docker hub 后也只有一份,这显然是不正确的。 使用 dockerfile经过我一番的搜索,我看到了一种使用 docker buildx 配合 dockerfile 的多架构构建方式,然后我就快速的写了一份 dockerfile。 FROM --platform=$TARGETPLATFORM scratchARG TARGETARCHADD beige-${TARGETARCH}.tgz /CMD [ "sh" ] dockerfile 需要注意的是,变量需要先使用 ARG 声明,在 build 阶段,遇到变量会产生一次分叉,这样就会在不同的架构里继续运行了(这也是坑我很长的时间,最终我将文件名修改为方便获取的方式……)。 接下来需要创建一份构建环境。 docker buildx create --use 这条命令可以创建一个基本环境,可以使用 docker buildx ls 查看当前的环境,可以看到默认就支持的有很多种架构。 然后使用 build 命令开始构建: docker buildx build --platform=linux/amd64,linux/arm64 -t linuxdeepin/beige:base --push . 简单说一下参数,platform 参数负责控制本次 build 传入架构,t 参数设置 tag 名称,这里我用了 –push 直接推送上去了,它默认用的 docker-compose 处理,构建产物不会出现在 docker images 里,所以就直接推送了。 现在我就有一份 v23 的 docker base 可用了,很开心。 点击前往:https://hub.docker.com/repository/docker/linuxdeepin/beige","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"}],"tags":[{"name":"docker","slug":"docker","permalink":"https://blog.justforlxz.com/tags/docker/"}]},{"title":"meet unity shader","slug":"meet-unity-shader","date":"2022-07-21T13:44:25.000Z","updated":"2024-04-15T05:09:54.896Z","comments":true,"path":"2022/07/21/meet-unity-shader/","permalink":"https://blog.justforlxz.com/2022/07/21/meet-unity-shader/","excerpt":"","text":"在我还在上高中的时候,我就开始学了一些 Unity,也尝试制作了一些简单的游戏,那时候更多是出于好玩,后来沉迷 Linux,就逐渐淡忘了游戏开发。 机缘巧合,我现在入手了一台 M1 mbp,接触到了 Apple 家的 Metal,又快速过了一下 Vulkan,也尝试做了一些入门级的项目,更幻想制作一个游戏引擎,所以最近有时间我就在看 Games104 的课程,补充一下相关知识,计划跟完 Games104,就去看一下闫令琪老师的 Games101,主攻一下计算机图形学。 有天中午我无意间看到了 onevcat 大佬写的一篇介绍 unity shader 的文章,并且还分享了一篇使用 shader 模拟物体表面的雪的效果,让我也想跟着做一个。 Unity shaderUnity shader 并不像平常我们见的 OpenGL、Vulkan 和 Metal 的 shader 文件一样,Unity shader 更像是配置文件,它使用特定的结构语法保存各种信息,并和 Unity Editor 中其他对象交互。 一个基础的 Unity shader 的结构是这样的: Shader "Custom/myshader" { Properties {} SubShader {} Fallback ""} 可以看出,一个标准的 Unity shader 拥有四个部分,一个 Shader 的命名,一个属性对象,一个 SubShader 对象,一个 Fallback 字符串。 属性对象是 Unity Editor 和 shader 沟通的桥梁,我们可以在 Properties 中声明输入,从外部接受参数。 SubShader 对象是可以多个重复的,当第一个 SubShader 对象中的代码无法在当前的 GPU 中运行时,Unity 会切换到下一个 SubShader 对象。我们可以在同一份 Shader 文件中对不同的 GPU 实现不同的支持。 Fallback 字符串则是如果 SubShader 都无法运行时,采用的最终的渲染方法。 一个简单的理解就是,从上到下,画面效果是依次降低的。 自定义属性SubShaderSubShader { Tags {} Pass { }} Pass { CGPROGRAM ENDCG} CGPROGRAM// TODO:CGPROGRAM 是发送到 GPU 运行的程序,也是一般概念中的 shader。 自定义光照法线Shader 代码// Upgrade NOTE: replaced '_Object2World' with 'unity_ObjectToWorld'Shader "Custom/SnowShader" { Properties { _MainTex ("Base (RGB)", 2D) = "white" {} _Bump ("Bump", 2D) = "bump" {} _Snow ("Snow Level", Range(0,1) ) = 0 _SnowColor ("Snow Color", Color) = (1.0,1.0,1.0,1.0) _SnowDirection ("Snow Direction", Vector) = (0,1,0) _SnowDepth ("Snow Depth", Range(0,0.3)) = 0.1 } SubShader { Tags { "RenderType"="Opaque" } LOD 200 CGPROGRAM #pragma surface surf CustomDiffuse vertex:vert sampler2D _MainTex; sampler2D _Bump; float _Snow; float4 _SnowColor; float4 _SnowDirection; float _SnowDepth; struct Input { float2 uv_MainTex; float2 uv_Bump; float3 worldNormal; INTERNAL_DATA }; inline float4 LightingCustomDiffuse (SurfaceOutput s, fixed3 lightDir, fixed atten) { float difLight = dot (s.Normal, lightDir); float hLambert = difLight * 0.5 + 0.5; float4 col; col.rgb = s.Albedo * _LightColor0.rgb * (hLambert * atten * 2); col.a = s.Alpha; return col; } void vert (inout appdata_full v) { float4 sn = mul(transpose(unity_ObjectToWorld) , _SnowDirection); if(dot(v.normal, sn.xyz) >= lerp(1,-1, (_Snow * 2) / 3)) { v.vertex.xyz += (sn.xyz + v.normal) * _SnowDepth * _Snow; } } void surf (Input IN, inout SurfaceOutput o) { half4 c = tex2D (_MainTex, IN.uv_MainTex); o.Normal = UnpackNormal(tex2D(_Bump, IN.uv_Bump)); if (dot(WorldNormalVector(IN, o.Normal), _SnowDirection.xyz) > lerp(1,-1,_Snow)) { o.Albedo = _SnowColor.rgb; } else { o.Albedo = c.rgb; } o.Alpha = 1; } ENDCG } FallBack "Diffuse"} 相关文章https://onevcat.com/2013/07/shader-tutorial-1/","categories":[{"name":"Game","slug":"Game","permalink":"https://blog.justforlxz.com/categories/Game/"}],"tags":[{"name":"Game","slug":"Game","permalink":"https://blog.justforlxz.com/tags/Game/"},{"name":"Unity","slug":"Unity","permalink":"https://blog.justforlxz.com/tags/Unity/"},{"name":"Shader","slug":"Shader","permalink":"https://blog.justforlxz.com/tags/Shader/"}]},{"title":"使用 React 的 JSX","slug":"jsx","date":"2022-07-01T16:11:59.000Z","updated":"2024-04-15T05:09:55.010Z","comments":true,"path":"2022/07/02/jsx/","permalink":"https://blog.justforlxz.com/2022/07/02/jsx/","excerpt":"","text":"jsxJSX 是 javascript XML 的缩写,可以在 javascript 代码中书写 HTML 结构的一种方式。 优点采用类似于 HTML 语法 充分利用 js 自身的可编程能力创建 HTML 结构 使用基本说明需要使用 babel 进行语法转换,对于 react 而言,以下代码是等价的。 function render() { return (<div id='d'> <p>hello world</p> </div>)} function render() { return ReactDOM.createElement('div', { id: 'd' }, ReactDOM.createElement('p', null, 'hello world'));} 表达式可以在 JSX 中使用表达式,表达式使用一对花括号对表达式进行标记。 const text = 'hello';const html = <div>{ text }</div>;const flag = false;function test() { return 'test function';}const newHtml = <div>{ flag ? test() : 'no' }</div>; 通过上面的例子可以看出,JSX 的表达式支持以下几种方式: 识别常规变量 原生 js 方法调用 三元运算符 特别注意 JSX 中无法使用 if/switch/变量声明等语句,他们不是表达式,不支持在 jsx 中使用。 列表渲染在 vue 中,我们可以使用 v-for 对一个列表数据进行遍历,可以在模板中实现元素的重复生成。在 angular 中可以使用 *ngFor 实现相同的事情,在 JSX 中我们也可以做到相同的事情。 可以使用 map 方法返回包含 jsx 的表达式 const songs = [ { id: 1, name: '可惜没如果' }, { id: 3, name: '我继续' }, { id: 2, name: '黑夜问白天' },];function App() { return ( <div className='App'> <ul> { songs.map(item => <li key={ item.id} >{ item.name }</li>) } </ul> </div> );}export default App; 注意事项 由于是重复元素渲染,需要为生成的元素分配一个 key,否则会影响 virtual dom 的性能。 key 只能使用 number/string 类型,key 属性不会出现在真实的 dom 属性上,进在内部使用。 条件渲染JSX 支持满足条件生成对应的 HTML 结构,可以使用 三元运算符 实现。 const flag = true;function App() { return ( <div className="App"> { flag ? 'flag is true' : 'flag is false' } { flag ? <div>flag is true</div> : null } </div> );} 样式处理JSX 支持 css 样式处理 行内样式 - style - 在元素属性上绑定 style 属性 function App() { return ( <div className="App"> <div style={{ color: red }}>here is a div</div> </div> );} 行内样式 - style - 更优写法 const styleObj = { color: 'red'};function App() { return ( <div className="App"> <div style={ styleObj }>here is a div</div> </div> );} 类名样式 - 在元素身上绑一个 className 属性 .active { color: red;} import './app.css'function App() { return ( <div className="App"> <div className="active">here is a div</div> </div> );} 注意事项 在第一个例子中,由于 style 属性需要的是一个对象,所以第一层 {} 是表达式,第二层 {} 是对象的定义括号,所以通常会写成 Object 的方式,这样控制也更加方便。 动态类名控制在上面的例子中,已经使用 css 中的类名样式进行了样式设置,但是有时候我们会希望控制一个元素的样式在某些场景下,会发生改变,这个时候就需要使用动态类名控制了。 import './app.css'const flag = false;function App() { return ( <div className="App"> <div className={ flag ? "activate" : "" }>here is a div</div> </div> );} 注意事项JSX 在实际应用时的注意事项 JSX 必须有一个根结点,也就是说 React 无法使用 jsx 创建最顶层的 html 元素,我们必须先提供一个空的元素作为 React 的根节点。(或者使用幽灵节点 <></> 创建) 所有标签必须形成闭合,成对闭合或者自闭合均可。 JSX 中的语法更贴近 javascript 的语法,属性采用小驼峰命名法 class -> className for -> htmlFor 。 JSX 支持多行(换行),如果需要换行,可以使用 () 进行包裹。","categories":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/categories/Web/"}],"tags":[{"name":"javascript","slug":"javascript","permalink":"https://blog.justforlxz.com/tags/javascript/"},{"name":"jsx","slug":"jsx","permalink":"https://blog.justforlxz.com/tags/jsx/"}]},{"title":"栈分配问题","slug":"stack-problem","date":"2022-06-24T08:44:30.000Z","updated":"2024-04-15T05:09:55.033Z","comments":true,"path":"2022/06/24/stack-problem/","permalink":"https://blog.justforlxz.com/2022/06/24/stack-problem/","excerpt":"","text":"前阵子在写一个新的项目,为了提升一些速度,所以没有使用 Qt 之类的大型库,在做进程管理的时候,遇到了奇怪的崩溃问题。 因为平时很少写这样的代码,所以觉得出问题很正常,但是排查了很久,都没有找到问题所在。 在 @black-desk 大佬的帮助下,重新复习了一遍操作系统如何管理进程,找到了问题所在。 先来一份简单的例子: #include <iostream>#include <unistd.h>#include <sys/types.h>int child(){ int pid = fork(); switch (pid) { case 0: std::cout << "[child] I'm child." << std::endl; sleep(5); std::cout << "[child] I'm quit." << std::endl; break; case -1: std::cout << "fork() failed." << std::endl; break; default: std::cout << "[parent] I'm meself." << std::endl; std::cout << "[parent] I will wait child." << std::endl; wait(nullptr); std::cout << "[parent] I'm quit." << std::endl; break; } return pid;}int main(int argc, char *argv[]){ child(); return 0;} 我们来跑一下这段代码,可以看到进程的输出。 g++ child.cpp $ ./a.out[parent] I'm meself.[parent] I will wait child.[child] I'm child.[child] I'm quit.[parent] I'm quit. 上面是一个非常简单和基本的 fork() 系统调用的用法,目前为止这里是没有问题的。 除了 fork() 系统调用,还有 clone() 系统调用,他们的作用分别是: fork 会创建一个父进程的完整副本,复制父进程所有的资源。 clone 也可以创建一个新的进程,但是它可以比 fork 更加精细的控制与子进程共享的资源,因此参数会更加复杂一些,通常我们可以用它来实现线程。 在我的需求中,我需要控制子进程运行在一个新的 proc namespace 中,所以我会选择使用 clone() 系统调用控制子进程所属的 namespace。 大概的代码如下: #include <sched.h>#include <stdlib.h>#include <sys/types.h>#include <sys/wait.h>#include <iostream>#define CHILD_STACK 8192int count = 0;int child_run(void *arg){ printf("count in child: %d\\n", ++count); return 0;}int main(int argc, char *argv[]){ int pid; int status; void *child_stack = malloc(CHILD_STACK); if (!child_stack) { fprintf(stderr, "failed to allocate child stack\\n"); exit(1); } printf("count before clone: %d\\n", count); /* Simulate vfork */ pid = clone(child_run, (void *) ((char *) child_stack + CHILD_STACK), CLONE_NEWPID, 0); if (pid == -1) { fprintf(stderr, "failed to clone\\n"); perror("clone failed: "); exit(2); } else { waitpid(pid, &status, 0); printf("count after clone: %d\\n", count); } return 0;} 这是一份很常见的 clone() 使用方法,作为一个例子,它没毛病,直到我运行了大量的函数,它崩溃了。 gdb 跟踪了一下,崩溃在了 std 的函数调用中,看起来很奇怪,我并没有写什么特别奇怪的代码,然后我开始精简代码,用二分简单定位了一下,发现有一个函数不调用,就不会崩溃,然后我就跟进去看代码,也没发现里面有什么奇怪的,就是一些 std 的代码。 正当我发愁怎么处理这个问题的时候, @black-desk 大佬来我旁边看我在干啥,我就给他看了一下代码和问题,他也觉得奇怪,就挺有兴趣的来帮我检查了。 经过一波 debug,最后定位可能是 stack 空间不够用了,然后被操作系统干掉了,最终将 stack 调大了一些,发现可以正常运行了,这说明问题确实是这里。 然后我就去复习 linux 进程内存分配的知识了。 进程地址空间中最顶部的段是栈,大多数编程语言将之用于存储函数参数和局部变量。调用一个方法或函数会将一个新的栈帧(stack frame)压入到栈中,这个栈帧会在函数返回时被清理掉。由于栈中数据严格的遵守FIFO的顺序,这个简单的设计意味着不必使用复杂的数据结构来追踪栈中的内容,只需要一个简单的指针指向栈的顶端即可,因此压栈(pushing)和退栈(popping)过程非常迅速、准确。进程中的每一个线程都有属于自己的栈。 通过不断向栈中压入数据,超出其容量就会耗尽栈所对应的内存区域,这将触发一个页故障(page fault),而被 Linux 的 expand_stack() 处理,它会调用 acct_stack_growth() 来检查是否还有合适的地方用于栈的增长。如果栈的大小低于 RLIMIT_STACK(通常为8MB),那么一般情况下栈会被加长,程序继续执行,感觉不到发生了什么事情。这是一种将栈扩展到所需大小的常规机制。然而,如果达到了最大栈空间的大小,就会栈溢出(stack overflow),程序收到一个段错误(segmentation fault)。 动态栈增长是唯一一种访问未映射内存区域而被允许的情形,其他任何对未映射内存区域的访问都会触发页错误,从而导致段错误。一些被映射的区域是只读的,因此企图写这些区域也会导致段错误。 最终我没有再使用这套方案,所以问题也就不需要解决了,但是这个问题让我对 Linux 进程的内存布局有了更加深刻的了解。","categories":[{"name":"技术","slug":"技术","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"一个邪恶的想法","slug":"an-evil-idea","date":"2022-04-26T07:39:49.000Z","updated":"2024-04-15T05:09:54.955Z","comments":true,"path":"2022/04/26/an-evil-idea/","permalink":"https://blog.justforlxz.com/2022/04/26/an-evil-idea/","excerpt":"","text":"哪个男孩子可以抵御服务器集群的诱惑呢? 我有几台性能偏弱的机器,并且分散在不同的地方,我就想着如何利用起来这些废旧设备。 我学习了一下 docker swarm 和 kubernetes,并半成功的使用 docker swarm 部署了一些服务,但是遇到了一些无法处理的难题,部署 php 服务时不知道为什么,只能在 manager 节点才能访问数据库,即使两个服务都在同一个节点上工作,这个问题困扰我了很久,一度以为是 docker 集群的网络不太行。 kubernetes 由于太过复杂,我目前还处于学习的状态,想要部署一些服务用作练习,但是我的机器都太垃圾了,想要使用 kubernetes 有点太困难,我就需要寻求其他人的帮助(白嫖),最终,我遇到了我的好朋友 Github。 Github 是一位非常热心的好朋友,它的家底雄厚,愿意给我一些房间,让我运行一些小的任务,但是它也规定了,每次只能使用六个小时,很多情况下我只是用它来启动一些检查或者编译任务,时间也不会很长,但是它提供的房间实在是太好了,以至于我动了歪脑筋( 一个邪恶的计划在我的脑海中逐渐成型。 首先,作为计划的第一步,我需要提供一个 VPN 服务,将所有的机器都联合进一个网络。 第二步,我需要想办法将 Github action 纳入我的管理。 第三步,自动更换房间,利用集群的自动迁移达到服务永不停的目的。 我有一台香港阿里云的轻量应用服务器,它的性能非常的弱,甚至坚持不了一个 go build,但是跑 VPN 和 kubernetes 还是足够了。 我需要写一个小的服务,提供 VPN 配置池,每个 action 启动时都会请求配置文件,从而加入集群网络。 启动的 action 会先请求 VPN 配置,加入集群网络后,初始化 kubernetes。作为 node 的 action 在启动后会加入到集群中,接受集群的管理。如果某个 action 即将过期,那么 master 会提前启动新的 node,并等待 node 被销毁,触发集群的 service 自动转移。 最终,我就拥有了一套性能非常强劲的集群,可以在上面部署各种服务。 当然,我这个邪恶的想法,也一定会伤了我好朋友的心。","categories":[],"tags":[{"name":"kubernetes","slug":"kubernetes","permalink":"https://blog.justforlxz.com/tags/kubernetes/"},{"name":"Github Action","slug":"Github-Action","permalink":"https://blog.justforlxz.com/tags/Github-Action/"},{"name":"evil","slug":"evil","permalink":"https://blog.justforlxz.com/tags/evil/"}]},{"title":"hello-metal.5 材质贴图","slug":"hello-metal-5","date":"2022-04-09T03:01:52.000Z","updated":"2024-04-15T05:09:54.984Z","comments":true,"path":"2022/04/09/hello-metal-5/","permalink":"https://blog.justforlxz.com/2022/04/09/hello-metal-5/","excerpt":"","text":"这个系列是我用来学习 Metal API 的笔记,我的最终目的是希望实现一个基于 Metal 的游戏引擎。 目前系列有: hello-metal.1 看到了绿色 hello-metal.2 第一个三角形 hello-metal.3 四边形 hello-metal.4 动画 hello-metal.5 材质贴图 点击查看上一篇 hello-metal.4 动画 上一篇我们成功的绘制了一个四边形,并且运行了一个简单的动画,今天我们来一起搞定材质贴图。 在前面的文章里已经介绍到了,我们通过两个三角形组合成了一个矩形,并且为每个顶点都增加偏移,以便我们在外部控制每帧绘制时坐标偏移。 在发送数据给 GPU 时,在 CPU 端准备的数据,必须设定好内存布局,然后在 shader 中接受时,也要使用相同的内存布局,否则读取就会出现问题,这也是为什么我们很多地方都在计算 offset 的原因。 这次我们拆分下数据,将原本一个 buffer 中的顶点数据,拆分为多个 buffer,一同发送给 GPU。 多条 MTLBuffer在 MTLVertexDescriptor 中我们可以添加多条 attributes,需要指定一个新的 bufferIndex,因为每一条属性对应了新的 MTLBuffer。 let vertexDescriptor = MTLVertexDescriptor()// positionvertexDescriptor.attributes[0].format = .float3vertexDescriptor.attributes[0].offset = 0vertexDescriptor.attributes[0].bufferIndex = 0// colorvertexDescriptor.attributes[1].format = .float4vertexDescriptor.attributes[1].offset = 0vertexDescriptor.attributes[1].bufferIndex = 1vertexDescriptor.layouts[0].stride = MemoryLayout<simd_float4>.stridevertexDescriptor.layouts[1].stride = MemoryLayout<simd_float4>.stride 每条 attributes 设置完毕后,我们还需要指定三条布局,和之前的指令相比,相当于我们将所有数据放在一条 buffer中,现在我们拆分成并行的数据,一块发送给 GPU。 在这张图中可以看出,内存布局是由 layout 决定的,数据是由 attributes 组成。 多条缓冲区是单条缓冲区的结构复制,会并行将数据都发送到 GPU。 设置好内存布局以后,我们就需要创建对应的 MTLBuffer 来保存独立的顶点数据。 var positionBuffer: MTLBuffer?var colorBuffer: MTLBuffer? 在我们的 buildModel 函数中将数据初始化。 positionBuffer = device.makeBuffer(bytes: positionVertices, length: positionVertices.count * MemoryLayout<simd_float4>.size, options: [])colorBuffer = device.makeBuffer(bytes: colorVertices, length: colorVertices.count * MemoryLayout<simd_float4>.size, options: []) 然后我们需要将顶点数据发送到 GPU,在 draw 函数中,将原本的 commandEncoder.setVertexBuffer 删除,并使用新的 MTLBuffer,而且需要注意的是,index 参数需要和 MTLVertexDescriptor 中的 index 保持一致,否则就无法正确的读取。 commandEncoder.setVertexBuffer(positionBuffer, offset: 0, index: 0)commandEncoder.setVertexBuffer(colorBuffer, offset: 0, index: 1) 那么数据发送到 GPU 之后,shader 要如何使用数据呢? 我们需要在 shader 中增加一个结构体,并使用一些语法标记。 struct VertexIn { float4 position [[ attribute(0) ]]; float4 color [[ attribute(1) ]];};struct VertexOut { float4 position [[ position ]]; float4 color;}; [[ attribute(0) ]] 的意思就是为了取得对应的 attributes。 还需要修改一下顶点着色器代码,传入结构体。 vertex VertexOut vertex_shader(const VertexIn vertexIn [[ stage_in ]]) { VertexOut vertexOut; vertexOut.position = vertexIn.position; vertexOut.color = vertexIn.color; return vertexOut;} 我们注意到,参数里有一个 [[ stage_in ]] 的标记,[[ stage_in ]] 可以修饰结构体,参数中只允许有一个参数使用该标记进行修饰。 由于我们的顶点是原始信息,所以只需要正常的赋值新的结构体,然后返回即可。 绘制材质材质贴图又称为纹理,纹理也有像素的称呼,但是需要区分一下,这里的像素并不是指屏幕上的物理像素。纹理使用不一样的坐标系,其原点在左上角,并且可以使用归一化将坐标系压缩至 (0.0, 1.0),当然不强制使用归一化坐标系,但是当你想使用不同分辨率的纹理时,只要映射关系正确,就可以正常工作。 绘制材质需要先进行顶点映射,材质的坐标系与顶点的坐标系不相同,所以我们需要提供一种映射,在执行片元着色器时,获取到某个位置正确的图片颜色。 坐标映射我们在 vertexDescriptor 中增加一条新的属性,用来保存映射关系。 // texturevertexDescriptor.attributes[2].format = .float2vertexDescriptor.attributes[2].offset = 0vertexDescriptor.attributes[2].bufferIndex = 2vertexDescriptor.layouts[2].stride = MemoryLayout<simd_float2>.stride 创建新的 MTLBuffer,保存映射关系。由于我们只使用了四个顶点,两个三角形组成了矩形,所以我们只需要将四个点的坐标对应起来就可以了。 textureBuffer = device.makeBuffer(bytes: textureVertices, length: textureVertices.count * MemoryLayout<simd_float2>.size, options: []) 在 draw 函数中,我们增加新的顶点信息。 commandEncoder.setVertexBuffer(textureBuffer, offset: 0, index: 2) 在 shader 的结构体中增加属性的接收。 struct VertexIn { float4 position [[ attribute(0) ]]; float4 color [[ attribute(1) ]]; float2 textureCoordinates [[ attribute(2) ]];};struct VertexOut { float4 position [[ position ]]; float4 color; float2 textureCoordinates;};vertex VertexOut vertex_shader(const VertexIn vertexIn [[ stage_in ]]) { VertexOut vertexOut; vertexOut.position = vertexIn.position; vertexOut.color = vertexIn.color; vertexOut.textureCoordinates = vertexIn.textureCoordinates; return vertexOut;} 读取文件好,我们现在已经成功的设置好了顶点属性,可是我们还没有讲如何读取文件呢。 首先,在 Metal 中,材质的读取是通过 MTKTextureLoader 创建一个加载器,可以使用它提供的 newTexture 方法创建一个 MTLTexture 对象。 func createTexture(device: MTLDevice, imageName: String) -> MTLTexture? { let textureLoader = MTKTextureLoader(device: device) var texture: MTLTexture? = nil // change direction let textureLoaderOptions: [MTKTextureLoader.Option: Any] = [.origin: MTKTextureLoader.Origin.bottomLeft] //let textureLoaderOptions: [MTKTextureLoader.Option: Any] = [:] if let textureURL = Bundle.main.url(forResource: imageName, withExtension: nil) { do { texture = try textureLoader.newTexture(URL: textureURL, options: textureLoaderOptions) } catch { print("texture not created") } } return texture} 在上面的代码中,我通过 MTKTextureLoader.Option 修改了坐标系的方向,将原点从左上角改为左下角(第四象限位置改为第一象限),可以不用修改,仅仅是个人喜好。 设置采样根据一般的流程,这个时候应该会先说画出来,然后发现画面比较模糊,这时候才开始说采样需要调整。 我不按套路,我就是要先讲采样,略略略。 Metal 使用 MTLSamplerDescriptor 来控制采样,我才用最基本的线性采样。Metal 的着色器工作流程是,先执行顶点着色器,处理顶点的坐标信息,再执行光栅化,确定像素边界,裁剪超出的像素。当光栅化结束后,执行片元着色器,计算每个像素的颜色。我们在片元着色器中使用材质,为片元着色器传入材质、顶点信息和采样属性,就可以完成像素颜色的输出。 var samplerState: MTLSamplerState?private func buildSamplerState(device: MTLDevice) { let descriptor = MTLSamplerDescriptor() descriptor.minFilter = .linear descriptor.magFilter = .linear samplerState = device.makeSamplerState(descriptor: descriptor)} 在构造函数中执行该函数,就可以完成 MTLSamplerState 构建。 紧接着,我们为 MTLCommandEncoder 设置片元采样状态。 commandEncoder.setFragmentSamplerState(node.samplerState, index: 0) 在 shader 中修改片元着色器代码,使用上面设置好的全部信息。 fragment half4 fragment_shader(VertexOut vertexIn [[ stage_in ]], sampler sampler2d [[ sampler(0) ]], texture2d<float> texture [[ texture(0) ]]){ float4 color = texture.sample(sampler2d, vertexIn.textureCoordinates); return half4(color.r, color.g, color.b, 1);} 在参数中使用 [[ sampler(0) ]] 来获取为 MTLCoMTLCommandEncoder 设置的 samplerState 的 index,[[ texture(0) ]] 获取 draw 函数中传入的材质。 渲染终于到最后了,我们好像还没说 textureVertices 应该存什么样的数据。我们现在有四个顶点,我们的矩形是两个三角形组成的,所以我们的的数据就是,四个角的点,与图片的四个角保持一致即可,我已经将材质的坐标系原点改为左下角,所以数据就是: var textureVertices: [simd_float2] = [ simd_float2(0, 1), // 左上角 simd_float2(0, 0), // 左下角(原点) simd_float2(1, 0), // 右下角 simd_float2(1, 1), // 右上角] 好,我们已经完成了所有的准备工作,现在是时候加载我老婆了! 也许你注意到了,照片看起来似乎饱和度有一些不太对,这是因为我们还没有调整色彩空间,只是简单的读取了原始信息,所以我们需要做一些处理,这就留到下一篇章吧~ 参考资料 https://developer.apple.com/documentation/metal/mtlvertexdescriptorhttps://developer.apple.com/metal/Metal-Shading-Language-Specification.pdfhttps://metalbyexample.com/vertex-descriptors/https://metalbyexample.com/textures-and-samplers/","categories":[{"name":"HelloMetal系列","slug":"HelloMetal系列","permalink":"https://blog.justforlxz.com/categories/HelloMetal%E7%B3%BB%E5%88%97/"}],"tags":[{"name":"Metal","slug":"Metal","permalink":"https://blog.justforlxz.com/tags/Metal/"},{"name":"GameEngine","slug":"GameEngine","permalink":"https://blog.justforlxz.com/tags/GameEngine/"}]},{"title":"hello-metal.4 动画","slug":"hello-metal-4","date":"2022-04-04T08:20:24.000Z","updated":"2024-04-15T05:09:54.984Z","comments":true,"path":"2022/04/04/hello-metal-4/","permalink":"https://blog.justforlxz.com/2022/04/04/hello-metal-4/","excerpt":"","text":"这个系列是我用来学习 Metal API 的笔记,我的最终目的是希望实现一个基于 Metal 的游戏引擎。 目前系列有: hello-metal.1 看到了绿色 hello-metal.2 第一个三角形 hello-metal.3 四边形 hello-metal.4 动画 hello-metal.5 材质贴图 点击查看上一篇 hello-metal.3 四边形 在上一篇已经完成了四边形的绘制,这一篇我们来实现一个简单的动画效果。 动起来现在我们需要增加一个结构体,用来保存画面的偏移,这样每次画面更新的时候,我们都可以使用偏移来控制顶点的坐标,达到动画的效果。 增加存储数据的结构体在 Renderer 中增加一个结构体,用来保存动画的值,增加一个 Float 类型的变量,保存每帧时间。 struct Constants { var animateBy: Float = 0}var constants = Constants()var time: Float = 0 计算每帧移动的距离在 draw 函数中,我们使用画面的最佳刷新率作为累加值。 time += 1 / Float(view.preferredFramesPerSecond)let animateBy = abs(sin(time) / 2 + 0.5)constants.animateBy = animateBy 发送数据到 GPU然后我们将结构体放进 GPU 中,MTLCommandEncoder 提供了 setVertexBytes 函数来保存数据。 commandEncoder?.setVertexBytes(&constants, length: MemoryLayout<Constants>.stride, index: 1) 我们为这个数据设置一个索引值 1,这样我们就可以在着色器代码中访问了。 修改着色器struct Constants { float animateBy;};vertex float4 vertex_shader(const device packed_float3 *vertices [[ buffer(0) ]], constant Constants &constants [[ buffer(1) ]], uint vertexId [[ vertex_id ]]) { float4 position = float4(vertices[vertexId], 1); position.x += constants.animateBy; return position;} 我们只需要在着色器代码中增加一个 struct,保持相同的内存布局,然后在函数参数中使用 constant 修饰结构体和buffer。 const 和 constant 的不同在于,constant 是地址空间,const 是类型限定符。 现在我们再跑一下,就可以看到一个动画效果了。 完整代码//// Renderer.swift// HelloMetal//// Created by lxz on 2022/4/4.//import MetalKitenum Colors { static let wenderlichGreen = MTLClearColor(red: 0.0, green: 0.4, blue: 0.21, alpha: 1.0)}class Renderer: NSObject { let device: MTLDevice let commandQueue: MTLCommandQueue var vertices: [Float] = [ -1, 1, 0, // 左上角 -1, -1, 0, // 左下角 1, -1, 0, // 右下角 1, 1, 0, // 右上角 ] let indices: [UInt16] = [ 0, 1, 2, // 左边的三角形 2, 3, 0 // 右边的三角形 ] var pipelineState: MTLRenderPipelineState? var vertexBuffer: MTLBuffer? var indexBuffer: MTLBuffer? struct Constants { var animateBy: Float = 0 } var constants = Constants() var time: Float = 0 init(device: MTLDevice) { self.device = device commandQueue = device.makeCommandQueue()! super.init() buildModel() buildPipelineState() } private func buildModel() { vertexBuffer = device.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<Float>.size, options: []) indexBuffer = device.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt16>.size, options: []) } private func buildPipelineState() { let library = device.makeDefaultLibrary() let vertexFunction = library?.makeFunction(name: "vertex_shader") let fragmentFunction = library?.makeFunction(name: "fragment_shader") let pipelineDescriptor = MTLRenderPipelineDescriptor() pipelineDescriptor.vertexFunction = vertexFunction pipelineDescriptor.fragmentFunction = fragmentFunction pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm do { pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor) } catch let error as NSError { print("error: \\(error.localizedDescription)") } }}extension Renderer: MTKViewDelegate { func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable, let pipelineState = pipelineState, let indexBuffer = indexBuffer, let descriptor = view.currentRenderPassDescriptor else { return } time += 1 / Float(view.preferredFramesPerSecond) let animateBy = abs(sin(time) / 2 + 0.5) constants.animateBy = animateBy let commandBuffer = commandQueue.makeCommandBuffer() let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: view.currentRenderPassDescriptor!) commandEncoder?.setRenderPipelineState(pipelineState) commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0) commandEncoder?.setVertexBytes(&constants, length: MemoryLayout<Constants>.stride, index: 1) commandEncoder?.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint16, indexBuffer: indexBuffer, indexBufferOffset: 0) commandEncoder?.endEncoding() commandBuffer?.present(view.currentDrawable!) commandBuffer?.commit() }} //// Shader.metal// HelloMetal//// Created by lxz on 2022/4/4.//#include <metal_stdlib>using namespace metal;struct Constants { float animateBy;};vertex float4 vertex_shader(const device packed_float3 *vertices [[ buffer(0) ]], constant Constants &constants [[ buffer(1) ]], uint vertexId [[ vertex_id ]]) { float4 position = float4(vertices[vertexId], 1); position.x += constants.animateBy; return position;}fragment half4 fragment_shader() { return half4(1, 0, 0, 1);} //// ViewController.swift// HelloMetal//// Created by lxz on 2022/4/4.//import UIKitimport MetalKitclass ViewController: UIViewController { var metalView: MTKView { return view as! MTKView } var renderer: Renderer! override func viewDidLoad() { super.viewDidLoad() metalView.device = MTLCreateSystemDefaultDevice() metalView.clearColor = Colors.wenderlichGreen renderer = Renderer(device: metalView.device!) metalView.delegate = renderer }}","categories":[{"name":"HelloMetal系列","slug":"HelloMetal系列","permalink":"https://blog.justforlxz.com/categories/HelloMetal%E7%B3%BB%E5%88%97/"}],"tags":[{"name":"Metal","slug":"Metal","permalink":"https://blog.justforlxz.com/tags/Metal/"},{"name":"GameEngine","slug":"GameEngine","permalink":"https://blog.justforlxz.com/tags/GameEngine/"}]},{"title":"hello-metal.3 四边形","slug":"hello-metal-3","date":"2022-04-04T05:05:40.000Z","updated":"2024-04-15T05:09:54.982Z","comments":true,"path":"2022/04/04/hello-metal-3/","permalink":"https://blog.justforlxz.com/2022/04/04/hello-metal-3/","excerpt":"","text":"这个系列是我用来学习 Metal API 的笔记,我的最终目的是希望实现一个基于 Metal 的游戏引擎。 目前系列有: hello-metal.1 看到了绿色 hello-metal.2 第一个三角形 hello-metal.3 四边形 hello-metal.4 动画 hello-metal.5 材质贴图 点击查看上一篇 hello-metal.2 第一个三角形 上一篇文章其实已经把基础内容讲完了,我们已经把绘制的流程走通了,这篇文章会说一下如何绘制一个四边形。 重构代码首先我们需要稍微重构一下代码,现在的代码都耦合在一个函数里,这对我们非常的不好,我们抽出来一个 Renderer 的文件,专门存放绘制代码。 首先基于 NSObject 派生出 Renderer 类,将各种成员变量都转移到这里,并在在构造函数接受最重要的 MTLDevice 对象。 //// Renderer.swift// HelloMetal//// Created by lxz on 2022/4/4.//import MetalKitenum Colors { static let wenderlichGreen = MTLClearColor(red: 0.0, green: 0.4, blue: 0.21, alpha: 1.0)}class Renderer: NSObject { let device: MTLDevice let commandQueue: MTLCommandQueue var vertices: [Float] = [ 0, 1, 0, -1, -1, 0, 1, -1, 0 ] var pipelineState: MTLRenderPipelineState? var vertexBuffer: MTLBuffer? init(device: MTLDevice) { self.device = device commandQueue = device.makeCommandQueue()! super.init() buildModel() buildPipelineState() } private func buildModel() { vertexBuffer = device.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<Float>.size, options: []) } private func buildPipelineState() { let library = device.makeDefaultLibrary() let vertexFunction = library?.makeFunction(name: "vertex_shader") let fragmentFunction = library?.makeFunction(name: "fragment_shader") let pipelineDescriptor = MTLRenderPipelineDescriptor() pipelineDescriptor.vertexFunction = vertexFunction pipelineDescriptor.fragmentFunction = fragmentFunction pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm do { pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor) } catch let error as NSError { print("error: \\(error.localizedDescription)") } }} 我们把所有的代码都转移到新的类中,只需要在 ViewController 中保留 Renderer 对象就可以了。 //// ViewController.swift// HelloMetal//// Created by lxz on 2022/4/4.//import UIKitimport MetalKitclass ViewController: UIViewController { var metalView: MTKView { return view as! MTKView } var renderer: Renderer! override func viewDidLoad() { super.viewDidLoad() metalView.device = MTLCreateSystemDefaultDevice() metalView.clearColor = Colors.wenderlichGreen renderer = Renderer(device: metalView.device!) }} metalView 支持 delegate,我们可以让 Renderer 派生自 MTKViewDelegate,就可以将原本绘制三角形部分的代码转移到 draw 函数中。 extension Renderer: MTKViewDelegate { func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { let commandBuffer = commandQueue.makeCommandBuffer() let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: view.currentRenderPassDescriptor!) commandEncoder?.setRenderPipelineState(pipelineState!) commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0) commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count) commandEncoder?.endEncoding() commandBuffer?.present(view.currentDrawable!) commandBuffer?.commit() }} 回到 ViewController.swift 中,我们需要在 viewDidLoad 函数中将 metalView 的 delegate 设置为 renderer。 metalView.delegate = renderer swift 提供一个 guard 的语法,可以方便的进行一些检查,可以把 draw 里一定存在的变量统一检查,如果任何一个不存在,都会运行到 else 中。 guard let drawable = view.currentDrawable, let pipelineState = pipelineState, let descriptor = view.currentRenderPassDescriptorelse { return} 顶点索引在上一篇讲过,顶点数组需要处理成顶点缓冲区,如果我们准备画一个四边形,正常来说我们需要准备两份三角形的坐标,但是!注意了,但是啊!由于两个三角形的斜边其实是同一条,那么意味着两个三角形的两个顶点,其实坐标是一样的,他们是重复的,如果我们完整的发送了全部顶点信息,虽然可以正常使用,但是数据量比较大的时候,就会浪费更多的资源。 一个模型通常由上万个三角形组成,每两个三角形都会浪费两个顶点,那么浪费掉的空间就不能忽略了,所以 Metal、OpenGL 这些 API 都提供了顶点索引功能,我们只需要发送顶点的坐标,然后使用顶点索引将点联系起来,这样就不用发送重复的顶点信息,只需要告诉 GPU,嘿老兄,这个点又被我用了,麻烦你读取一下吧。 我们将顶点数组换成这样的数据: var vertices: [Float] = [ -1, 1, 0, // 左上角 -1, -1, 0, // 左下角 1, -1, 0, // 右下角 1, 1, 0, // 右上角] 我们创建一个 indices,用来保存顶点索引,同样是三位一组,一共两组,使用了顶点数组中的三组坐标信息。 let indices: [UInt16] = [ 0, 1, 2, // 左边的三角形 2, 3, 0 // 右边的三角形] 同样的,我们还需要使用一个 MTLBuffer 保存顶点索引缓存。 var indexBuffer: MTLBuffer? 在 buildModel 函数里初始化顶点索引缓存。 indexBuffer = device.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt16>.size, options: []) 绘制四边形我们已经有顶点缓冲了,也有顶点索引缓冲,可以去试一下了。 在 guard 中赋值一下,保证它不为空。 guard let drawable = view.currentDrawable, let pipelineState = pipelineState, let indexBuffer = indexBuffer, let descriptor = view.currentRenderPassDescriptorelse { return} 接下来就是比较重要的部分了,我们要如何使用顶点索引呢? MTLCommandEncoder 有一个 drawIndexedPrimitives 函数,可以接受顶点索引缓冲。 commandEncoder?.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint16, indexBuffer: indexBuffer, indexBufferOffset: 0) 让我们运行一下吧! 非常棒,我们看到整个屏幕都是红色了。 完整代码//// Renderer.swift// HelloMetal//// Created by lxz on 2022/4/4.//import MetalKitenum Colors { static let wenderlichGreen = MTLClearColor(red: 0.0, green: 0.4, blue: 0.21, alpha: 1.0)}class Renderer: NSObject { let device: MTLDevice let commandQueue: MTLCommandQueue var vertices: [Float] = [ -1, 1, 0, // 左上角 -1, -1, 0, // 左下角 1, -1, 0, // 右下角 1, 1, 0, // 右上角 ] let indices: [UInt16] = [ 0, 1, 2, // 左边的三角形 2, 3, 0 // 右边的三角形 ] var pipelineState: MTLRenderPipelineState? var vertexBuffer: MTLBuffer? var indexBuffer: MTLBuffer? init(device: MTLDevice) { self.device = device commandQueue = device.makeCommandQueue()! super.init() buildModel() buildPipelineState() } private func buildModel() { vertexBuffer = device.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<Float>.size, options: []) indexBuffer = device.makeBuffer(bytes: indices, length: indices.count * MemoryLayout<UInt16>.size, options: []) } private func buildPipelineState() { let library = device.makeDefaultLibrary() let vertexFunction = library?.makeFunction(name: "vertex_shader") let fragmentFunction = library?.makeFunction(name: "fragment_shader") let pipelineDescriptor = MTLRenderPipelineDescriptor() pipelineDescriptor.vertexFunction = vertexFunction pipelineDescriptor.fragmentFunction = fragmentFunction pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm do { pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor) } catch let error as NSError { print("error: \\(error.localizedDescription)") } }}extension Renderer: MTKViewDelegate { func mtkView(_ view: MTKView, drawableSizeWillChange size: CGSize) { } func draw(in view: MTKView) { guard let drawable = view.currentDrawable, let pipelineState = pipelineState, let indexBuffer = indexBuffer, let descriptor = view.currentRenderPassDescriptor else { return } let commandBuffer = commandQueue.makeCommandBuffer() let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: view.currentRenderPassDescriptor!) commandEncoder?.setRenderPipelineState(pipelineState) commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0) commandEncoder?.drawIndexedPrimitives(type: .triangle, indexCount: indices.count, indexType: .uint16, indexBuffer: indexBuffer, indexBufferOffset: 0) commandEncoder?.endEncoding() commandBuffer?.present(view.currentDrawable!) commandBuffer?.commit() }} //// Shader.metal// HelloMetal//// Created by lxz on 2022/4/4.//#include <metal_stdlib>using namespace metal;vertex float4 vertex_shader(const device packed_float3 *vertices [[ buffer(0) ]], uint vertexId [[ vertex_id ]]) { float4 position = float4(vertices[vertexId], 1); return position;}fragment half4 fragment_shader() { return half4(1, 0, 0, 1);} //// ViewController.swift// HelloMetal//// Created by lxz on 2022/4/4.//import UIKitimport MetalKitclass ViewController: UIViewController { var metalView: MTKView { return view as! MTKView } var renderer: Renderer! override func viewDidLoad() { super.viewDidLoad() metalView.device = MTLCreateSystemDefaultDevice() metalView.clearColor = Colors.wenderlichGreen renderer = Renderer(device: metalView.device!) metalView.delegate = renderer }}","categories":[{"name":"HelloMetal系列","slug":"HelloMetal系列","permalink":"https://blog.justforlxz.com/categories/HelloMetal%E7%B3%BB%E5%88%97/"}],"tags":[{"name":"Metal","slug":"Metal","permalink":"https://blog.justforlxz.com/tags/Metal/"},{"name":"GameEngine","slug":"GameEngine","permalink":"https://blog.justforlxz.com/tags/GameEngine/"}]},{"title":"hello-metal.2 第一个三角形","slug":"hello-metal-2","date":"2022-04-04T02:05:43.000Z","updated":"2024-04-15T05:09:54.976Z","comments":true,"path":"2022/04/04/hello-metal-2/","permalink":"https://blog.justforlxz.com/2022/04/04/hello-metal-2/","excerpt":"","text":"这个系列是我用来学习 Metal API 的笔记,我的最终目的是希望实现一个基于 Metal 的游戏引擎。 目前系列有: hello-metal.1 看到了绿色 hello-metal.2 第一个三角形 hello-metal.3 四边形 hello-metal.4 动画 hello-metal.5 材质贴图 今天听到了一句话,觉得很有道(ji)理(tang): 你所谓的顿悟,可能是别人的基本功 自勉! 点击查看上一篇 hello-metal.1 看到了绿色 接上文,我们已经成功的提交了渲染命令到管线中,成功得到了渲染结果,所以我们需要开始写着色器代码,完成我们的第一个三角形,接下来让我们继续吧! 注意事项Metal 的着色器语言是基于 C++ 11 的语言,对我来说比 OpenGL 的着色器语言更加熟悉。 但是需要注意的是 Metal 中不支持 C++11 的如下特性: Lambda 表达式 递归函数调用 动态转换操作符 类型识别 对象创建 new 和销毁 delete 操作符 操作符 noexcept goto 变量存储修饰符 register 和 thread_local 虚函数修饰符 派生类 异常处理 C++ 标准库在 Metal 语言中也不可使用 Metal 语言对于指针使用的限制 Metal 图形和并行计算函数用到的入参(比如指针 / 引用),如果是指针 / 引用必须使用地址空间修饰符(比如 device、threadgroup、constant) 不支持函数指针 函数名不能出现 main 着色器在 xcode 左侧对着 ViewController.swift 右键,选择 New File,新建 Metal File,起名为 Shader 就可以创建一个着色器文件。 xcode 会帮助我们自动生成一部分代码,比如默认帮助我们 include 了 metal_stdlib。 顶点着色器我们首先新建一个 vertex_shader 的函数,让顶点着色器去执行这个函数。 vertex float4 vertex_shader(const device packed_float3 *vertices [[ buffer(0) ]], uint vertexId [[ vertex_id ]]) { float4 position = float4(vertices[vertexId], 1); return position;} 熟悉 OpenGL 或者其他 GPU 编程的人应该很熟悉,float4 就是一个向量。在参数中的 [[ buffer(0) ]],是在代码部分设置的顶点缓冲区 id,[[ vertex_id ]] 顶点id 标识符。 片元着色器除了顶点着色器函数,我们还需要准备一个片元着色器函数。 fragment half4 fragment_shader() { return half4(1, 0, 0, 1);} 啊这,half4 是个什么玩意儿。 查了一波 Apple 的文档,发现 half 是 16位的浮点数,所以这里其实我们用 float4 也是可以的,虽说是跟着教程跑,但是也要理解一下为什么要这么用。 片元着色器代码其实就返回了一个红色,没有任何的参数,所以当我们成功运行以后,我们应该看到红色。 绘制已经完成了基本的着色器代码,那么就可以开始使用它了。回到 ViewController.swift,我们首先需要创建一个顶点数组,我们依赖这些顶点信息去构建空间中,以三角形为基本的面。 顶点数组新增一个变量,用来保存顶点数组: var vertices: [Float] = [ 0, 1, 0, -1, -1, 0, 1, -1, 0] 我们都知道,三个点可以形成一个面,形成的就是三角形,在计算机中使用顶点 vertex 来保存空间信息,并且顶点不仅仅是用来保存坐标的,还可以保存其他信息,比如某个点的颜色,某个点的法线等等。 由于空间是三维空间,所以我们将 Z 轴都设置为 0,这样三角形就只需要关心 X 轴和 Y 轴了。 但是需要注意的是,此时我们的顶点信息,还只是没有意义的点,甚至连点都算不上,因为我们还没有“规定”它是什么。 顶点缓冲区我们需要创建一个 MTLBuffer 对象来保存顶点缓冲区,只有经过处理的顶点缓冲区才是我们需要的。 var vertexBuffer: MTLBuffer?private func buildModel() { vertexBuffer = device.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<Float>.size, options: [])} 为了方便使用 device 对象,增加一个变量来保存初始化的 device。在 viewDidLoad 函数中调用 buildModel,初始化顶点缓冲区。 PipelineState为了使用 MTLRenderCommandEncoder 对象来编码渲染指令,必须先设置一个 MTLRenderPipelineState 对象来定义每个绘制命令的图形状态。 一个渲染管线 state 对象是一个拥有长生命周期的对象,它可以在 render command encoder 对象生效范围外被创建,最后可以被缓存起来,然后被重用于多个 render command encoder 对象。当描述相同的图形状态,重用先前创建的渲染管线 state 对象,这样可以避免高成本的重新评估和转换操作(将特定状态转换成 GPU 指令)。 渲染管线 state 对象是一个不可变对象。要创建一个渲染管线 state 对象,首先创建一个可变的 MTLRenderPipelineDescriptor 对象,它描述了渲染管线 state 的属性。然后你可以使用这个 descriptor 来创建一个 MTLRenderPipelineState 对象。 我们新增一个函数用来初始化 MTLRenderPipelineState。 var pipelineState: MTLRenderPipelineStateprivate func buildPipelineState() { let library = device.makeDefaultLibrary() let vertexFunction = library?.makeFunction(name: "vertex_shader") let fragmentFunction = library?.makeFunction(name: "fragment_shader") let pipelineDescriptor = MTLRenderPipelineDescriptor() pipelineDescriptor.vertexFunction = vertexFunction pipelineDescriptor.fragmentFunction = fragmentFunction pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm do { pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor) } catch let error as NSError { print("error: \\(error.localizedDescription)") }} 使用 device.makeDefaultLibrary() 可以创建一个 library 对象,用来获取上面创建的着色器函数,Metal 会在项目编译时就完成着色器编译,这点和 OpenGL 是不同的。 我们只需要为 MTLRenderPipelineDescriptor 设置顶点着色器函数、片元着色器函数和颜色格式就行了。 最后在 viewDidLoad 中调用该函数即可。 绘制三角形准备工作已经接近尾声了,我们现在可以向 commandEncoder 中编码命令了。 为 commandEncoder 设置 pipelineState 为 commandEncoder 设置 vertexBuffer 为 commandEncoder 设置 绘制三角形命令 在 commandEncoder?.endEncoding() 前插入代码: commandEncoder?.setRenderPipelineState(pipelineState)commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0)commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count) setVertexBuffer 函数的 index 参数就是着色器中,vertex_shader 参数的 [[ buffer(0) ]],我们没有设置偏移,那么坐标信息就是从 0 开始读取。 这里提一下为什么会有 offset,有时候我们会准备一份顶点信息,只需要送一次给 GPU,不同的着色器可以通过 offset 读取同一份顶点缓冲,只需要按偏移使用就可以了。 drawPrimitives 函数是告诉 GPU,嘿哥们,我给你的顶点是用来画三角形的,请你三个一组的使用。 最终我们成功的绘制了一个红色的三角形。 完整代码ViewController//// ViewController.swift// HelloMetal//// Created by lxz on 2022/4/4.//import UIKitimport MetalKitenum Colors { static let wenderlichGreen = MTLClearColor(red: 0.0, green: 0.4, blue: 0.21, alpha: 1.0)}class ViewController: UIViewController { var metalView: MTKView { return view as! MTKView } var commandQueue: MTLCommandQueue! var device: MTLDevice! var vertexBuffer: MTLBuffer? var vertices: [Float] = [ 0, 1, 0, -1, -1, 0, 1, -1, 0 ] var pipelineState: MTLRenderPipelineState! override func viewDidLoad() { super.viewDidLoad() device = MTLCreateSystemDefaultDevice() metalView.device = device metalView.clearColor = Colors.wenderlichGreen commandQueue = device.makeCommandQueue() buildModel() buildPipelineState() let commandBuffer = commandQueue.makeCommandBuffer() let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: metalView.currentRenderPassDescriptor!) commandEncoder?.setRenderPipelineState(pipelineState) commandEncoder?.setVertexBuffer(vertexBuffer, offset: 0, index: 0) commandEncoder?.drawPrimitives(type: .triangle, vertexStart: 0, vertexCount: vertices.count) commandEncoder?.endEncoding() commandBuffer?.present(metalView.currentDrawable!) commandBuffer?.commit() } private func buildModel() { vertexBuffer = device.makeBuffer(bytes: vertices, length: vertices.count * MemoryLayout<Float>.size, options: []) } private func buildPipelineState() { let library = device.makeDefaultLibrary() let vertexFunction = library?.makeFunction(name: "vertex_shader") let fragmentFunction = library?.makeFunction(name: "fragment_shader") let pipelineDescriptor = MTLRenderPipelineDescriptor() pipelineDescriptor.vertexFunction = vertexFunction pipelineDescriptor.fragmentFunction = fragmentFunction pipelineDescriptor.colorAttachments[0].pixelFormat = .bgra8Unorm do { pipelineState = try device.makeRenderPipelineState(descriptor: pipelineDescriptor) } catch let error as NSError { print("error: \\(error.localizedDescription)") } }} Shader//// Shader.metal// HelloMetal//// Created by lxz on 2022/4/4.//#include <metal_stdlib>using namespace metal;vertex float4 vertex_shader(const device packed_float3 *vertices [[ buffer(0) ]], uint vertexId [[ vertex_id ]]) { float4 position = float4(vertices[vertexId], 1); return position;}fragment half4 fragment_shader() { return half4(1, 0, 0, 1);}","categories":[{"name":"HelloMetal系列","slug":"HelloMetal系列","permalink":"https://blog.justforlxz.com/categories/HelloMetal%E7%B3%BB%E5%88%97/"}],"tags":[{"name":"Metal","slug":"Metal","permalink":"https://blog.justforlxz.com/tags/Metal/"},{"name":"GameEngine","slug":"GameEngine","permalink":"https://blog.justforlxz.com/tags/GameEngine/"}]},{"title":"hello-metal.1 看到了绿色","slug":"hello-metal-1","date":"2022-04-04T00:59:33.000Z","updated":"2024-04-15T05:09:54.966Z","comments":true,"path":"2022/04/04/hello-metal-1/","permalink":"https://blog.justforlxz.com/2022/04/04/hello-metal-1/","excerpt":"","text":"这个系列是我用来学习 Metal API 的笔记,我的最终目的是希望实现一个基于 Metal 的游戏引擎。 目前系列有: hello-metal.1 看到了绿色 hello-metal.2 第一个三角形 hello-metal.3 四边形 hello-metal.4 动画 hello-metal.5 材质贴图 Metal 是由苹果公司所开发的一个应用程序接口,兼顾图形与计算功能,面向底层、低开销的硬件加速。其类似于将OpenGL 与 OpenCL 的功能集成到了同一个API上。Metal也通过引入计算着色器来进一步提高GPGPU编程的能力。Metal 使用一种基于C++11的新着色语言,其实现借助了 Clang 和 LLVM。 从今天开始,我会开始写一个 Metal 的入门系列,作为我学习 Metal 的笔记和过程。我学习的平台以 raywenderlich.com 的视频为主,代码和流程也基本保持一致。 现在就让我们开始吧! 新建项目首先打开 xcode,新建一个 iOS App,需要注意的是,interface 默认是 swiftui,需要修改成 Storyboard,应该使用 swiftui 也没有问题的,只是先按照教程一步步来吧。 设置 MTKView当项目新建完成后,打开左侧的 Main.storyboard 文件,在中间选择到 view Controller Scene -> View Controller -> View,展开到 View 节点,并单击它。 右侧面板会显示该节点的详细信息,在最右侧选择 Show the Identity inspector 按钮,看起来像是名片按钮的,将 Class 修改为 MTKView,因为我们的界面不是普通的 View,而是使用 Metal 绘制的界面。 开启 Metal 之旅导入 MetalKit打开 ViewController.swift 文件,我们可以看到 xcode 已经自动生成了一部分代码,我们首先在 import UIKit 下一行写入 import MetalKit,用来导入 Metal API 相关的文件。 首先我们需要获取到界面上的 View,在 ViewController 类中写一个变量,用来访问 View。 增加代码: var metalView: MTKView { return view as! MTKView} 最终代码如下: import UIKitimport MetalKitclass ViewController: UIViewController { var metalView: MTKView { return view as! MTKView } override func viewDidLoad() { super.viewDidLoad() }} 访问 metaView 就是访问界面上的 view。 创建 Device接下来就开始我们的 Metal 之旅,首先我们需要创建一个默认设备,这个设备是抽象的硬件资源,有了这个硬件,我们才可以去将着色器代码等各种代码发送到真正的设备上去使用。 在 metalView 中有个 device 成员,我们可以使用 MTLCreateSystemDefaultDevice 函数创建默认设备,我们就可以使用这个 device 了。 import UIKitimport MetalKitclass ViewController: UIViewController { var metalView: MTKView { return view as! MTKView } override func viewDidLoad() { super.viewDidLoad() metalView.device = MTLCreateSystemDefaultDevice() }} 设置背景清除色有了设备以后呢? 我们可以先给屏幕来一个清除色,或者叫背景色。 在 ViewController.swift 中新增一个枚举,使用 MTLClearColor 函数可以创建出所需的值。 enum Colors { static let wenderlichGreen = MTLClearColor(red: 0.0, green: 0.4, blue: 0.21, alpha: 1.0)} 在 viewDidLoad 中我们为 metalView 设置 clear color。 metalView.clearColor = Colors.wenderlichGreen 这时候运行一下例子,然后就会发现屏幕是黑的,这是为啥捏?不是设置了背景色了吗? 哦吼吼,别忘了,和 OpenGL 一样,想要利用 GPU 去绘制画面,需要有顶点信息,由各种着色器处理过后,才能得到一幅画面,而我们还没有开始写着色器代码,也没有写绘制命令。 绘制命令队列我们需要新建一个命令队列 MTLCommandQueue,用来缓冲我们的操作命令。 let commandQueue: MTLCommandQueue 在 viewDidLoad 函数中使用 metalView.device.makeCommandQueue()! 初始化 commandQueue。 还需要创建一个 MTLCommandBuffer 来缓冲命令,这里可能就有疑惑了,MTLCommandQueue 自己就是个队列,怎么还需要一个 buffer 呢?MTLCommandBuffer 是从 MTLCommandQueue 中创建出来的,用于本次绘制所需的全部状态,例如顶点信息、颜色、顶点连接顺序等等,当完成所有设置以后,就可以使用 commandBuffer.commit() 提交到队列中。 命令编码我们还需要一个 MTLRenderCommandEncoder,MTLRenderCommandEncoder 对象表示一个单独的图形渲染 command encoder。MTLParallelRenderCommandEncoder 对象使得一个单独的渲染 pass 被分成若干个独立的 MTLRenderCommandEncoder 对象,每一个都可以被分配到不同的线程。这些 command encoders 中的指令随后将串行起来,并以一致的可预测的顺序被执行。 这里是一张 Metal 渲染管线图。 提交绘制最终我们新增的代码是这样的: commandQueue = metalView.device?.makeCommandQueue()let commandBuffer = commandQueue.makeCommandBuffer()let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: metalView.currentRenderPassDescriptor!)commandEncoder?.endEncoding()commandBuffer?.present(metalView.currentDrawable!)commandBuffer?.commit() 可以看到有些变量后面跟了一个!,它的意思是这里绝对不为空,是一种断言,同样还有?,它的意思是如果不为空就执行。 测试运行我们已经完成了最终的代码,整个 ViewController.swift 的代码如下: import UIKitimport MetalKitenum Colors { static let wenderlichGreen = MTLClearColor(red: 0.0, green: 0.4, blue: 0.21, alpha: 1.0)}class ViewController: UIViewController { var metalView: MTKView { return view as! MTKView } var commandQueue: MTLCommandQueue! override func viewDidLoad() { super.viewDidLoad() metalView.device = MTLCreateSystemDefaultDevice() metalView.clearColor = Colors.wenderlichGreen commandQueue = metalView.device?.makeCommandQueue() let commandBuffer = commandQueue.makeCommandBuffer() let commandEncoder = commandBuffer?.makeRenderCommandEncoder(descriptor: metalView.currentRenderPassDescriptor!) commandEncoder?.endEncoding() commandBuffer?.present(metalView.currentDrawable!) commandBuffer?.commit() }} 这时候就可以看到屏幕出现了绿色,这是我们设置的 clearColor。 本篇先讲了一个基本的 Metal 项目的使用,下一篇会开始讲顶点和着色器的用法,完成我们的第一个三角形。","categories":[{"name":"HelloMetal系列","slug":"HelloMetal系列","permalink":"https://blog.justforlxz.com/categories/HelloMetal%E7%B3%BB%E5%88%97/"}],"tags":[{"name":"Metal","slug":"Metal","permalink":"https://blog.justforlxz.com/tags/Metal/"},{"name":"GameEngine","slug":"GameEngine","permalink":"https://blog.justforlxz.com/tags/GameEngine/"}]},{"title":"vim简单配置","slug":"vim简单配置","date":"2022-03-22T12:36:51.000Z","updated":"2024-04-15T05:09:55.080Z","comments":true,"path":"2022/03/22/vim简单配置/","permalink":"https://blog.justforlxz.com/2022/03/22/vim%E7%AE%80%E5%8D%95%E9%85%8D%E7%BD%AE/","excerpt":"","text":"作为一个爱折腾的同学,折腾几个编辑器不过分吧。 我折腾编辑器的历史还算跟得上潮流,最开始用 sublime,后来 atom 出来了,吊打 sublime,我就去用 atom了。再后来宇宙第一编辑器 vscode 出来了,吊打了(我认为)所有的编辑器,我就又去用 vscode。 (怎么感觉我一直在追新?喵?) 工作以后,坐我左边的是一个 emacs 用户,右边是一个 vim 用户,然后他俩偶尔会拉我看他们搞的新插件,或者是他们互相在自己的编辑器上实现了对方的插件的功能。说实话我也不是没用过 vim 或者 emacs,但是最大的问题其实是,对我来说学习成本非常大,遇到一点小问题,我就会不由自主的回想起 vscode 的好,然后就不想折腾了。这对我其实是不好的,毕竟我是一个爱折腾的人。 所以我又一次折腾起了 emacs,每一次折腾都会让我学习到新的内容,从最开始的懵逼,到现在已经会简单的用 lisp 写一些插件。每一次都有提升,不过就是无用功比较多。 但是本篇先不折腾 emacs,这次我想先写一下我学习到的 vim 的配置。 neovimvim 阵营其实可以简单的分为 vim 和 neovim,为什么会有两个阵营呢?简单来说就是 neovim 的作者受不了 vim 的作者,所以 fork 出来增加自己的功能,结果从原版那里拉来了很多的用户,vim 作者一看阵势不对,赶紧又反向增加功能,现在两者的区别已经不是很大了。 使用 neovim 作为我的主 vim 编辑器是因为它支持 lua 脚本作为配置文件,lua 脚本比较好上手一些。 基本配置neovim 的配置文件在 ~/.config/nvim/。 入口文件是 init.vim,里面写的内容很简单,就是加载 lua 脚本。 " 基础设置lua require('basic') 需要注意的是,lua 脚本必须都放在 init.vim 同级的 lua 目录里。 我使用 packer 作为 vim 的插件管理系统,vim 有很多插件系统,我选择它没有啥理由,就是随手用了一个。 安装 picker 需要手动下载,但是这样可能会在更换环境的时候忘记,所以我写在了脚本里,检测并自动下载。 在 ~/.config/nvim/lua/basic.lua 里就可以写 lua 脚本去执行更复杂的指令,比如加载插件。 -- utf8-- 高亮所在行vim.wo.cursorline = true-- 右侧参考线,超过表示代码太长了,考虑换行vim.wo.colorcolumn = "80"-- 缩进2个空格等于一个Tabvim.o.tabstop = 2local fn = vim.fn-- 下载 packerlocal install_path = fn.stdpath('data')..'/site/pack/packer/start/packer.nvim'local is_startup = falseif fn.empty(fn.glob(install_path)) > 0 then fn.system({'git', 'clone', '--depth', '1', 'https://github.com/wbthomason/packer.nvim', install_path}) vim.cmd[[packadd packer.nvim]] is_startup = trueend-- 初始化 packerreturn require('packer').startup( function() local use = require('packer').use local home = os.getenv("HOME") -- 使用 ~/.config/nvim/lua/plugins-d 作为插件的目录 local plugins = io.popen('find "'.. home .. '/.config/nvim/lua/plugins-d/'..'" -type f') for plugin in plugins:lines() do -- 插件的标准文件名是这种形式 _lsp.lua local part1, part2 = string.match(plugin,home .. "/[.]config/nvim/lua/(.*)_(.*)[.]lua") if part1 ~= nil and part2 ~= nil then plugin = part1 .. '_' .. part2 else plugin = '' end if plugin ~= '' then use(require(plugin)) end end if is_startup then require('packer').sync() end end) 新建一个 plugins-d 目录,就可以写插件了,使用 vim 作为开发工具,第一件事肯定是先配置好语言服务器。 新建 ~/.config/nvim/lua/plugins-d/_lsp.lua -- https://github.com/neovim/nvim-lspconfig-- Description:-- lsp configs for neovim builtin lspCapabilities = vim.lsp.protocol.make_client_capabilities()-- Use an on_attach function to only map the following keys-- after the language server attaches to the current bufferOn_Attach_hooks = {}function On_Attach (client, bufnr) local wk = require("which-key") local key_opts = { -- mode Help Affected Equivalent -- '' mapmode-nvo Normal/Visual/Select/Operator-pending :map -- 'n' mapmode-n Normal :nmap -- 'v' mapmode-v Visual/Select :vmap -- 's' mapmode-s Select :smap -- 'x' mapmode-x Visual :xmap -- 'o' mapmode-o Operator-pending :omap -- '!' mapmode-ic Insert/Command-line :map! -- 'i' mapmode-i Insert :imap -- 'l' mapmode-l Insert/Command-line/Lang-Arg :lmap -- 'c' mapmode-c Command-line :cmap -- 't' mapmode-t Terminal :tmap mode = "n", buffer = 0, -- local mappings silent = true, -- use `silent ` when creating keymaps noremap = true, -- use `noremap` when creating keymaps } wk.register( { ["K"] = { "<cmd>lua vim.lsp.buf.hover()<CR>", "LSP:: hover" }, ["<C-k>"] = { "<cmd>lua vim.lsp.buf.signature_help()<CR>", "LSP:: signature help" }, ["<space>rn"] = { "<cmd>lua vim.lsp.buf.rename()<CR>", "LSP:: rename" }, ["<space>f"] = { "<cmd>lua vim.lsp.buf.formatting()<CR>", "LSP:: format" }, ["<space>E"] = { "<cmd>lua vim.diagnostic.open_float()<CR>", "LSP:: float diagnostic" }, }, key_opts ) key_opts = { mode = "n", buffer = 0, -- local mappings silent = true, -- use `silent ` when creating keymaps noremap = false, -- not use `noremap` when creating keymaps } wk.register( { ["gd"] = { "<cmd>lua vim.lsp.buf.definition()<cr>", "LSP:: definition" }, ["gr"] = { "<cmd>lua vim.lsp.buf.references()<cr>", "LSP:: reference" }, ["gi"] = { "<cmd>lua vim.lsp.buf.implementation()<cr>", "LSP:: implementation" }, ["gy"] = { "<cmd>lua vim.lsp.buf.type_definition()<cr>", "LSP:: type definition" }, }, key_opts ) for _, hook in ipairs(On_Attach_hooks) do if hook ~= nil then hook(client, bufnr) end endendlocal function config() local nvim_lsp = require('lspconfig') -- Use a loop to conveniently call 'setup' on multiple servers and -- map buffer local keybindings when the language server attaches local servers ={ 'ccls', } for _, lsp in ipairs(servers) do local default = { flags = { debounce_text_changes = 150, }, on_attach = On_Attach, capabilities = Capabilities } local cfg = vim.tbl_deep_extend('force', default, require('lsp-d/'..lsp..'_')) nvim_lsp[lsp].setup(cfg) endendreturn { 'neovim/nvim-lspconfig', config = config, after = { 'nvim-cmp', }} 不要看文件很长,其实大部分都是 nvim 的默认 lsp-config 配置,而且大部分配置一眼就能看得懂,最开始 On_Attach 函数是在处理快捷键相关的,即使不了解 vim 如何定义快捷键,也不影响看这个配置文件。 在 config 函数中,有一个数组,包含了 ccls,这里的意思是去加载 ccls 语言服务器,nvim 给了一分列表,可以查找自己使用的语言服务器的名称。 在 config 函数里,local cfg 这里是定义查找文件的,我这里是听从了 black_desk 的建议,如果文件名是 _ 开头的,就屏蔽这个文件,可以快速进行调试。 在 ~/.config/nvim/lua/lsp-d/ccls_.lua 里写入配置内容,就可以启用 ccls 语言服务器了。 local home = os.getenv('HOME')return { init_options = { cache = { directory = home .. "/.ccls-cache", retainInMemory = 0, }, -- highlight = { -- lsRanges = true, -- } };} 默认 nvim 是定义了很多值,并不是每一个语言服务器都需要定义,但是需要注意的是,即使没有使用配置文件,也需要创建相应的文件,并返回一个空对象。 基本配置已经完成了,只差最后一步了,还记得一开始我说的使用了 packer 作为插件管理器了么,现在就需要编译一遍配置文件,让 packer 能加载我们的插件。 执行命令 nvim +PackerCompile,然后退出 vim。如果改动了配置文件,都需要重新执行一次这样的命令,否则配置将不会生效。 下面是我的使用截图。","categories":[],"tags":[{"name":"vim","slug":"vim","permalink":"https://blog.justforlxz.com/tags/vim/"}]},{"title":"CSS 外边距折叠问题","slug":"css-mastering-margin-collapsing","date":"2022-03-16T07:35:56.000Z","updated":"2024-04-15T05:09:54.958Z","comments":true,"path":"2022/03/16/css-mastering-margin-collapsing/","permalink":"https://blog.justforlxz.com/2022/03/16/css-mastering-margin-collapsing/","excerpt":"","text":"在学习 CSS 练习元素浮动的时候,我发现了一个奇怪的现象,就是上下两个块元素似乎距离太近了,无论我怎么增加上下 margins,始终都不会按照我预期的那样显示。 查阅了一些资料发现这是一个 CSS 的特定,叫 外边距折叠,在CSS中,两个或多个相邻的普通流中的盒子(可能是父子元素,也可能是兄弟元素)在垂直方向上的外边距会发生叠加,这种形成的外边距称之为外边距叠加。 W3C 对于外边距叠加的定义: In CSS, the adjoining margins of two or more boxes (which might or might not be siblings) can combine to form a single margin. Margins that combine this way are said to collapse, and the resulting combined margin is called a collapsed margin. 在 W3C 中,对相邻也有定义: Two margins are adjoining if and only if: both belong to in-flow block-level boxes that participate in the same block formatting context no line boxes, no clearance, no padding and no border separate them both belong to vertically-adjacent box edges, i.e. form one of the following pairs: top margin of a box and top margin of its first in-flow child bottom margin of box and top margin of its next in-flow following sibling bottom margin of a last in-flow child and bottom margin of its parent if the > parent has “auto” computed height top and bottom margins of a box that does not establish a new block formatting context and that has zero computed “min-height”, zero or “auto” computed “height”, and no in-flow children 翻译: 两个边距相邻当且仅当: 都属于普通流的块级盒子且参与到相同的块级格式上下文中 没有被padding、border、clear和line box分隔开 都属于垂直相邻盒子边缘: 盒子的top margin和它第一个普通流子元素的top margin 盒子的bottom margin和它下一个普通流兄弟的top margin 盒子的bottom margin和它父元素的bottom margin 盒子的top margin和bottom margin,且没有创建一个新的块级格式上下文,且有被计算为0的min-height,被计算为0或auto的height,且没有普通流子元素 下面来写一个小例子: .container1 { height: 30px; background: green; margin-bottom: 20px;}.container2 { margin: 10px 0 20px;}.container3 { height: 20px; background: blue; margin-top: 20px;}.child { background: red; height: 10px; margin: 10px 0 20px;}<div class="container1"></div><div class="container2"> <div class="child"></div> <div class="child"></div> <div class="child"></div></div><div class="container3"></div> 显示效果如下: 可以看到,上一个元素的范围已经到了下一个元素的顶部,而下一个元素的上外边缘,已经被折叠在了一起。 这样的话我们就无法按照期望去布局了,那么有什么办法可以解决吗? 既然外边距的折叠是有条件的,只要想办法破坏条件,就可以避免这个问题了。 而且 W3C 也给出了解决办法: Margins between a floated box and any other box do not collapse (not even between a float and its in-flow children). Margins of elements that establish new block formatting contexts (such as floats and elements with “overflow” other than “visible”) do not collapse with their in-flow children. Margins of absolutely positioned boxes do not collapse (not even with their in-flow children). Margins of inline-block boxes do not collapse (not even with their in-flow children). The bottom margin of an in-flow block-level element always collapses with the top margin of its next in-flow block-level sibling, unless that sibling has clearance. The top margin of an in-flow block element collapses with its first in-flow block-level child”s top margin if the element has no top border, no top padding, and the child has no clearance. The bottom margin of an in-flow block box with a “height” of “auto” and a “min-height” of zero collapses with its last in-flow block-level child”s bottom margin if the box has no bottom padding and no bottom border and the child”s bottom margin does not collapse with a top margin that has clearance. A box”s own margins collapse if the “min-height” property is zero, and it has neither top or bottom borders nor top or bottom padding, and it has a “height” of either 0 or “auto”, and it does not contain a line box, and all of its in-flow children”s margins (if any) collapse. 翻译下就是: 浮动元素不会与任何元素发生叠加,也包括它的子元素 创建了 BFC 的元素不会和它的子元素发生外边距叠加 绝对定位元素和其他任何元素之间不发生外边距叠加,也包括它的子元素 inline-block 元素和其他任何元素之间不发生外边距叠加,也包括它的子元素 普通流中的块级元素的 margin-bottom 永远和它相邻的下一个块级元素的 margin-top 叠加,除非相邻的兄弟元素 clear 普通流中的块级元素(没有 border-top、没有 padding-top)的 margin-top 和它的第一个普通流中的子元素(没有clear)发生 margin-top 叠加 普通流中的块级元素(height为 auto、min-height为0、没有 border-bottom、没有 padding-bottom)和它的最后一个普通流中的子元素(没有自身发生margin叠加或clear)发生 margin-bottom叠加 如果一个元素的 min-height 为0、没有 border、没有padding、高度为0或者auto、不包含子元素,那么它自身的外边距会发生叠加 还好以前写了一个浮动的笔记,可以拿来用。 跳转查看 CSS浮动笔记","categories":[],"tags":[{"name":"CSS","slug":"CSS","permalink":"https://blog.justforlxz.com/tags/CSS/"},{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/tags/Web/"}]},{"title":"使用 if constexpr 实现条件编译","slug":"if-constexpr","date":"2022-03-10T05:03:27.000Z","updated":"2024-04-15T05:09:55.009Z","comments":true,"path":"2022/03/10/if-constexpr/","permalink":"https://blog.justforlxz.com/2022/03/10/if-constexpr/","excerpt":"","text":"在项目开发中,我们通常会使用条件编译对代码进行裁剪,选择性地排除不需要的代码,比如在某个平台下完全不支持某个功能,那么这个功能就不应该被编译。 一般我们使用宏来判断代码,选择性的挑选需要编译的部分,并在构建系统中开启这样的条件。 #ifdef XXXXXXXXXX std::cout << "hello world!" << std::endl;#else std::cout << "good bye!" << std::endl;#endif 在 C 语言的项目中,这样的行为是很正常的,甚至在 C++ 项目中,我们也会选择使用宏进行条件判断。 但是如果定义的宏多了,则很容易导致代码碎片化,并且无法直观地看到工作流程。 例如这样的代码: #define KWIN_VERSION_CHECK(major, minor, patch, build) ((major<<24)|(minor<<16)|(patch<<8)|build)#ifdef KWIN_VERSION_STR#define KWIN_VERSION KWIN_VERSION_CHECK(KWIN_VERSION_MAJ, KWIN_VERSION_MIN, KWIN_VERSION_PAT, KWIN_VERSION_BUI)#endif#if defined(KWIN_VERSION) && KWIN_VERSION < KWIN_VERSION_CHECK(5, 21, 3, 0)typedef int TimeArgType;#elsetypedef std::chrono::milliseconds TimeArgType;#endif#if defined(Q_OS_LINUX) && !defined(QT_NO_DYNAMIC_LIBRARY) && !defined(QT_NO_LIBRARY)QT_BEGIN_NAMESPACEQFunctionPointer qt_linux_find_symbol_sys(const char *symbol);QT_END_NAMESPACEQFunctionPointer KWinUtils::resolve(const char *symbol){ return QT_PREPEND_NAMESPACE(qt_linux_find_symbol_sys)(symbol);#elseQFunctionPointer KWinUtils::resolve(const char *symbol){ static QString lib_name = "kwin.so." + qApp->applicationVersion(); return QLibrary::resolve(lib_name, symbol);#endif} 从上面的例子中可以看到,代码中一旦出现大量重复的判断条件,代码非常不直观,而且被宏分割成了很多部分。 在一次偶然的机会,我看到了一篇介绍 C++ 17 中的 if constexpr 的用法,可以在编译期进行一些计算,虽然我很早就知道了 constexpr 的用法,但是大家举的例子基本上都是数值计算,让编译器在编译期间将数值进行计算,从而减轻运行时的消耗,我也从来想到其他用法,所以一直没有在项目中使用到。 constexpr 的作用并不是编译期计算数值,而是编译期进行的代码分析,如果代码较小且非常直观,比如大家经常举的例子,在编译期间计算斐波那契数列,这种例子即使不使用 constexpr 显式要求,编译器也会帮助我们开启优化,直接给出结果。 但是如果代码非常复杂,编译器就不一定会为我们做这样的优化,就需要我们手动标记可以计算的位置,要求编译器在编译期间进行求值和优化。 我设想的是,使用 cmake 在构建时,先生成一份文件,将开关的值记录下来,在需要进行判断的地方,就可以直接使用 if constexpr 进行条件判断,在编译期间,编译器会发现有一个分支确定不会被执行(相当于 if(false) {}),那么这个分支就不会进行编译,直接剔除。 CMakeLists.txt 中需要做一些工作,将编译参数加入构建系统。 option (ENABLE_MODULE "Enable Module" ON)if(ENABLE_MODULE) set(ENABLE_MODULE "1")else() set(ENABLE_MODULE "0")endif(ENABLE_MODULE)configure_file ( "${CMAKE_CURRENT_SOURCE_DIR}/options/options.h.in" "${CMAKE_CURRENT_BINARY_DIR}/options/options.h") 在 options/options.h.in 文件里,按照 cmake 的要求将变量导入进文件中,进行内容替换。 #pragma once#cmakedefine01 ENABLE_MODULE 这里我仍然使用的是宏定义,也可以直接写成如下形式: option (ENABLE_MODULE "Enable Module" ON)if(ENABLE_MODULE) set(ENABLE_MODULE "true")else() set(ENABLE_MODULE "false")endif(ENABLE_MODULE)configure_file ( "${CMAKE_CURRENT_SOURCE_DIR}/options/options.h.in" "${CMAKE_CURRENT_BINARY_DIR}/options/options.h") #pragma onceconst bool ENABLE_MODULE{@ENABLE_MODULE@}; 在 main.cpp 中写一段测试代码: #include "options/options.h"#include <iostream>int main() { if constexpr (ENABLE_MODULE) { std::cout << "Now Enable Module" << std::endl; } if constexpr (!ENABLE_MODULE) { std::cout << "Now Disable Module" << std::endl; } return 0;} 执行结果是符合预期的。 # lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:39]$ cmake ../ -G Ninja -DENABLE_MODULE=ON-- The CXX compiler identification is AppleClang 13.0.0.13000029-- Detecting CXX compiler ABI info-- Detecting CXX compiler ABI info - done-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - skipped-- Detecting CXX compile features-- Detecting CXX compile features - done-- Configuring done-- Generating done-- Build files have been written to: /Users/lxz/Develop/constexpr-demo/build# lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:45]$ ninja[2/2] Linking CXX executable src/constexpr# lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:48]$ ./src/constexprNow Enable Module# lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:52]$ cmake ../ -G Ninja -DENABLE_MODULE=OFF-- Configuring done-- Generating done-- Build files have been written to: /Users/lxz/Develop/constexpr-demo/build# lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:28:58]$ ninja[2/2] Linking CXX executable src/constexpr# lxz @ lxz-MacBook-Pro in ~/Develop/constexpr-demo/build on git:master o [13:29:00]$ ./src/constexprNow Disable Module 虽然结果是符合的,但是我们其实不确定是否真的在编译期间就完成了代码剔除,所以使用命令进行汇编,查看汇编中是否包含了判断指令和两段输出的字符串。 clang -S main.cpp -o main.s -I./ main.s .text .file "main.cpp" .globl main // -- Begin function main .p2align 2 .type main,@functionmain: // @main .cfi_startproc// %bb.0: stp x29, x30, [sp, #-32]! // 16-byte Folded Spill str x19, [sp, #16] // 8-byte Folded Spill mov x29, sp .cfi_def_cfa w29, 32 .cfi_offset w19, -16 .cfi_offset w30, -24 .cfi_offset w29, -32 adrp x19, :got:_ZSt4cout ldr x19, [x19, :got_lo12:_ZSt4cout] adrp x1, .L.str add x1, x1, :lo12:.L.str mov w2, #18 mov x0, x19 bl _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l ldr x8, [x19] ldur x8, [x8, #-24] add x8, x8, x19 ldr x19, [x8, #240] cbz x19, .LBB0_5// %bb.1: ldrb w8, [x19, #56] cbz w8, .LBB0_3// %bb.2: ldrb w1, [x19, #67] b .LBB0_4.LBB0_3: mov x0, x19 bl _ZNKSt5ctypeIcE13_M_widen_initEv ldr x8, [x19] mov w1, #10 mov x0, x19 ldr x8, [x8, #48] blr x8 mov w1, w0.LBB0_4: adrp x0, :got:_ZSt4cout ldr x0, [x0, :got_lo12:_ZSt4cout] bl _ZNSo3putEc bl _ZNSo5flushEv ldr x19, [sp, #16] // 8-byte Folded Reload mov w0, wzr ldp x29, x30, [sp], #32 // 16-byte Folded Reload ret.LBB0_5: bl _ZSt16__throw_bad_castv.Lfunc_end0: .size main, .Lfunc_end0-main .cfi_endproc // -- End function .section .text.startup,"ax",@progbits .p2align 2 // -- Begin function _GLOBAL__sub_I_main.cpp .type _GLOBAL__sub_I_main.cpp,@function_GLOBAL__sub_I_main.cpp: // @_GLOBAL__sub_I_main.cpp .cfi_startproc// %bb.0: stp x29, x30, [sp, #-32]! // 16-byte Folded Spill str x19, [sp, #16] // 8-byte Folded Spill mov x29, sp .cfi_def_cfa w29, 32 .cfi_offset w19, -16 .cfi_offset w30, -24 .cfi_offset w29, -32 adrp x19, _ZStL8__ioinit add x19, x19, :lo12:_ZStL8__ioinit mov x0, x19 bl _ZNSt8ios_base4InitC1Ev adrp x0, :got:_ZNSt8ios_base4InitD1Ev ldr x0, [x0, :got_lo12:_ZNSt8ios_base4InitD1Ev] mov x1, x19 ldr x19, [sp, #16] // 8-byte Folded Reload adrp x2, __dso_handle add x2, x2, :lo12:__dso_handle ldp x29, x30, [sp], #32 // 16-byte Folded Reload b __cxa_atexit.Lfunc_end1: .size _GLOBAL__sub_I_main.cpp, .Lfunc_end1-_GLOBAL__sub_I_main.cpp .cfi_endproc // -- End function .type _ZStL8__ioinit,@object // @_ZStL8__ioinit .local _ZStL8__ioinit .comm _ZStL8__ioinit,1,1 .hidden __dso_handle .type .L.str,@object // @.str .section .rodata.str1.1,"aMS",@progbits,1.L.str: .asciz "Now Disable Module" // 关键在这里 .size .L.str, 19 .section .init_array,"aw",@init_array .p2align 3 .xword _GLOBAL__sub_I_main.cpp .ident "clang version 13.0.1" .section ".note.GNU-stack","",@progbits .addrsig .addrsig_sym _GLOBAL__sub_I_main.cpp .addrsig_sym _ZStL8__ioinit .addrsig_sym __dso_handle .addrsig_sym _ZSt4cout 查看整个 main.s 汇编,发现只在 .L.str 段中有预期的文本字符串,可以得出结论,代码是在编译期间完成了剔除,符合我们的要求。","categories":[],"tags":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"}]},{"title":"latex 环境配置","slug":"latex-environment-config","date":"2022-03-08T09:55:49.000Z","updated":"2024-04-15T05:09:55.010Z","comments":true,"path":"2022/03/08/latex-environment-config/","permalink":"https://blog.justforlxz.com/2022/03/08/latex-environment-config/","excerpt":"","text":"在我刚开始学习使用 Linux 的时候,经常能看到一些人在鼓吹 tex、markdown、org-mode 之类的文档编写方式的好处,甚至分化出不同的阵营,互相拉拢(忽悠)其他刚入门的人。 后来我渐渐喜欢上了 markdown,因为它写起来足够简单,上手难度较低,而且我的博客是用 hexo 搭建的,它需要 markdown 作为博客文章,所以我逐渐就使用起来了 markdown。 在公司里,虽然 markdown 写起来非常简单,但是公司内部的文档是有格式要求的,排版被提上了日程,而排版恰恰是 markdown 的硬伤,markdown 可以说几乎没有排版的功能,它是轻量的标记语言,没有人给它提供复杂的排版,所以我们的目标变成了需要一种方便进行文本 diff 又不是 word 这类软件,最终我们的目标落在了 latex 上。 在 20 世纪,计算机教授高德纳(Donald Ervin Knuth)编写了一个排版软件,它可以处理非常复杂的数学公式,后来发展出了非常多的语言排版,它在学术界特别是数学、物理学和计算机科学界十分流行。tex 被普遍认为是一个优秀的排版工具,尤其是对于复杂数学公式的处理。利用 latex 等终端软件,tex 就能够排版出精美的文本以帮助人们辨认和查找。 latex 是一种基于 tex 的排版系统,利用这种格式系统的处理,即使用户没有排版和程序设计的知识也可以充分发挥由 tex 所提供的强大功能,不必一一亲自去设计或校对,能在几天,甚至几小时内生成很多具有书籍质量的印刷品。 目前来看,我们使用 latex 写文档,进行 git 提交,在使用的时候编译成 pdf,发送给其他人使用,其他人更新文档我们也可以非常方便进行 review。 所以我就需要在本地配置一下 latex 的环境,以及使用 vscode 进行 latex 文档的编写。 安装由于我在使用 macOS,所以我需要进行的步骤要比使用 deepin 多一些。 在 deepin 上,只需要安装 texlive-lang-cjk 和 texlive-xetex 两个包就可以了。 sudo apt install texlive-lang-cjk texlive-xetex 在 macOS 上,需要使用 homebrew 安装 mactex,这是专门针对 mac 系统优化的 tex 发行版。 brew install mactex 安装完 mactex 以后,需要打开 TeX Live Utility,然后选择更新,将所有包更新到最新。如果觉得网速慢,可以使用国内的镜像加速下载。 阅读 https://mirrors.bfsu.edu.cn/help/CTAN/ 使用镜像。 以上安装工作就结束了。 配置模板为了方便每个人写的文档都满足公司的要求,所以我们准备了一份公司的 latex 模板,latex 的模板规定了一些目录,只要放在模板目录即可使用。 Linux: /usr/share/texlive/texmf-dist/tex/latex/ macOS: ~/Library/texmf/tex/latex/ vscode以上准备工作做好以后,打开 vscode,安装 LaTeX Workshop 插件,其他插件可以不用安装,这个插件会帮助我们自动补全语法、高亮和格式化。 准备完毕以后,就可以愉快写 latex 了,本篇文章并不会教大家如何写 latex,只是介绍一下 latex,以及我使用的配置。 首先给 vscode 配置以下的工作区配置: { "latex-workshop.latex.outDir": "%DIR%/build", "latex-workshop.view.pdf.viewer": "tab", "latex-workshop.latex.autoBuild.cleanAndRetry.enabled": true, "latex-workshop.latex.autoClean.run": "onFailed", "latex-workshop.latex.clean.subfolder.enabled": true, "latex-workshop.latex.clean.fileTypes": [ "*.aux" ], "latex-workshop.latex.tools": [ { "name": "xelatex", "command": "xelatex", "args": [ "-synctex=1", "-interaction=nonstopmode", "-file-line-error", "--shell-escape", "-pdf", "--output-directory=%OUTDIR%", "%DOCFILE%" ] }, { "name": "pdflatex", "command": "pdflatex", "args": [ "-synctex=1", "-interaction=nonstopmode", "-file-line-error", "--output-directory=%OUTDIR%", "%DOCFILE%" ] }, { "name": "bibtex", "command": "bibtex", "args": [ "%OUTDIR%/%DOCFILE%" ], "env": { "TEXMFOUTPUT": "%OUTDIR%" } } ], "latex-workshop.latex.recipes": [ { "name": "xelatex", "tools": [ "xelatex" ], }, { "name": "pdflatex", "tools": [ "pdflatex" ] }, { "name": "xe->bib->xe->xe", "tools": [ "xelatex", "bibtex", "xelatex", "xelatex" ] }, { "name": "pdf->bib->pdf->pdf", "tools": [ "pdflatex", "bibtex", "pdflatex", "pdflatex" ] } ],} 在配置中,这段的作用是控制输出目录、自动构建和清理。 { "latex-workshop.latex.outDir": "%DIR%/build", "latex-workshop.view.pdf.viewer": "tab", "latex-workshop.latex.autoBuild.cleanAndRetry.enabled": true, "latex-workshop.latex.autoClean.run": "onFailed", "latex-workshop.latex.clean.subfolder.enabled": true, "latex-workshop.latex.clean.fileTypes": [ "*.aux" ],} 接下来的步骤是控制编译的,为什么需要写这么长呢,因为 latex 其实不支持中文的,支持中文的是 xelatex,我们需要重写编译参数,否则输出的 pdf 是不会显示中文的。 在 latex-workshop.latex.tools 中,我们增加了三个新的构建指令,用于处理我们的 latex 文档。 在 latex-workshop.latex.recipes 中,第一个 recipe 为默认的编译工具,所以默认我们会使用 xelatex 进行编译,从而使用我们自定义的编译命令。 保存工作区配置到 .vscode/settings.json 中,就可以愉快的写 latex 啦。 放几张演示图片:","categories":[],"tags":[{"name":"latex","slug":"latex","permalink":"https://blog.justforlxz.com/tags/latex/"}]},{"title":"QDir 和 std::filesystem 的简单对比","slug":"qdir-stdfilesystem","date":"2022-03-04T02:11:45.000Z","updated":"2024-04-15T05:09:55.027Z","comments":true,"path":"2022/03/04/qdir-stdfilesystem/","permalink":"https://blog.justforlxz.com/2022/03/04/qdir-stdfilesystem/","excerpt":"","text":"作为一名使用 Qt 的开发人员,Qt 为我提供了大量好用的基础设施,例如广受好评的 QString、QNetwork之类的,这是 Qt 平台为我提供的帮助,我只需要在这个平台上开发就足够了。 同样作为一名 C++ 开发人员,C++ 标准库也是我需要用的基础设施,但是标准库提供的功能就不如 Qt 了,最令人诟病的就是 C++ 的 std::string,业内充斥着对 std::string 的不屑与谩骂。 但是这一情况将会在 C++ 20 标准后改善,这里不具体展开,将来我会准备写一份 C++ 20 的功能介绍。 今天在开发项目的时候,为了节省资源,我仅使用标准库完成开发,缺少了 Qt 平台为我提供的有利帮助,项目开发的难度瞬间增加,在此期间,我发现了 C++ 的目录操作似乎能比的上 Qt 提供的 QDir封装,我打算在本篇文章中介绍一下这两者的不同。 QDirQt 的基本思路是继承大于组合,所以 Qt 为我们提供的都是各种继承的类,在 Qt 中,我们使用 QDir 类进行目录操作。 QDir 类使用相对或绝对文件路径来指向一个文件或目录。如果总是使用 “/” 作为目录分隔符,Qt 将会把你的路径转化为符合底层的操作系统的。 QDir 类由于不涉及 IO 的具体操作,所以没有继承自 QObject 或者其他 QIO 的类。 QDir 提供了非常多的方法,可以方便的获取目录的名称、绝对路径、相对路径、设置目录过滤器等。 一个基本的用法如下: QDir dir("/tmp");if (!dir.exists()) { return}for (const QFileInfo& item : dir.entryInfoList()) { if (item.isDir()) { // ... }}dir.setFilter(QDir::Files | QDir::Hidden | QDir::NoSymLinks);dir.setSorting(QDir::Size | QDir::Reversed);const QFileInfoList& list = dir.entryInfoList();for (const QFileInfo& item : list) { // ...} 在上面的示例代码中可以看到,QDir 整体是非常符合面向对象的,我们使用 QDir 对象,对目录执行各种操作,涉及到具体的文件(目录是特殊的文件),我们可以使用 QFileInfo 类获取具体文件的信息。 除了以上演示涉及到的方法,QDir 还有很多其他方法,使用 count() 方法统计目录内文件和目录的数量,使用 remove() 方法删除指定的目录,这里就不一一列举了。 QDir 支持设置多种过滤,过滤(Filter) 和 排序(Sorting) 都会影响到 entryInfoList 方法返回的内容。 QDir 接受的过滤器枚举: 枚举 枚举值 描述 QDir::Dirs 0x001 列出与过滤器匹配的目录。 QDir::AllDirs 0x400 列出所有目录;即不要将过滤器应用于目录名称。 QDir::Files 0x002 列出文件。 QDir::Drives 0x004 列出磁盘驱动器(在Unix下忽略)。 QDir::NoSymLinks 0x008 不要列出符号链接(不支持符号链接的操作系统忽略)。 QDir::NoDotAndDotDot NoDot NoDotDot QDir::NoDot 0x2000 不要列出特殊条目“.”。 QDir::NoDotDot 0x4000 不要列出特殊条目“..”。 QDir::AllEntries Dirs Files QDir::Readable 0x010 列出应用程序具有读取访问权限的文件。可读值需要与Dirs或Files合并。 QDir::Writable 0x020 列出应用程序具有写入访问权限的文件。可写值需要与Dirs或Files合并。 QDir::Executable 0x040 列出应用程序具有执行访问权限的文件。可执行值需要与Dirs或Files合并。 QDir::Modified 0x080 仅列出已修改的文件(在Unix上忽略)。 QDir::Hidden 0x100 列出隐藏的文件(在Unix上,以“.”开头的文件)。 QDir::System 0x200 列出系统文件(包括Unix、FIFO、套接字和设备文件;在Windows上,包括.lnk文件) QDir::CaseSensitive 0x800 过滤器应该区分大小写。 QDir 接受的排序枚举: 枚举 枚举值 描述 QDir::Name 0x00 按名称排序。 QDir::Time 0x01 按时间(修改时间)排序。 QDir::Size 0x02 按文件大小排序。 QDir::Type 0x80 按文件类型(扩展名)排序。 QDir::Unsorted 0x03 不要排序。 QDir::NoSort -1 默认情况下未排序。 QDir::DirsFirst 0x04 先放目录,然后放文件。 QDir::DirsLast 0x20 先放文件,然后放目录。 QDir::Reversed 0x08 颠倒排序顺序。 QDir::IgnoreCase 0x10 不区分大小写的排序。 QDir::LocaleAware 0x40 使用当前区域设置对项目进行适当排序。 std::filesystem上面介绍了 Qt 的设计风格,而标准库的设计风格是组合大于继承,标准库提供各种非常具体的类或者函数,将一个系列的操作拆分为各个子项,最终完成任务。 这里使用一个小例子来说明一下: std::filesystem::path tmp{"/tmp"};if (!std::filesystem::exists(tmp)) { std::cout << "目录不存在" << std::endl; return;}const bool result = std::filesystem::create_directories(tmp);if (!result) { std::cout << "目录创建失败,也许是已经存在。" << std::endl;}for (auto const& dir_entry : std::filesystem::directory_iterator(tmp)) { if (dir_entry.is_directory()) { // ... }}if (std::filesystem::is_directory(tmp)) { // ...} 可以看到,C++ 标准库使用多个类共同完成了对目录的检查和遍历,这种基于组合的方式可以带来更多的灵活性,如果需要对某个部分进行修改,只需要继承特定类就可以完成,如果是 Qt 的 QDir,则不是很轻松。 在使用标准库的时候需要注意的是,标准库通常不会约束使用者,使用当前的例子举例,QDir 提供了过滤器枚举,可以帮助开发者简单的实现文件过滤功能,但是 std::filesystem 则不提供这种接口,标准库提供了机制,但是不提供策略,开发者需要使用标准库提供的各种接口,组合 出自己的业务,所以如果想要使用标准库的时候也能实现过滤和排序,就只能自己提供相应的操作。 std::filesystem::path tmp{ "/tmp" };std::vector<std::filesystem::directory_entry> list;std::vector<std::string> filter{ ".jpg", ".txt" };std::filesystem::directory_iterator iter{ tmp };std::copy_if(iter.begin(), iter.end(), std::back_inserter(list), [filter](std::filesystem::directory_entry entry) { return !entry.is_directory() && std::find_if(filter.begin(), filter.end(), [entry](std::string s) { return entry.path().extension() == s; } ); }); 可以看到,代码变得异常丑陋(逃 标准库提供了一些比较方便的函数,例如 std::copy_if、 std::back_inserter 和 std::find_if 等,还有一个较为常用的 std::transform。标准库提供了迭代器抽象,这样我们可以使用迭代器对象和迭代器算法,方便的进行各种遍历、复制和转换。 从上面的例子可以看出,Qt 确实为开发者提供了很好的帮助,这是 Qt 作为一个平台力所能及的工作,当然,即使我们使用 Qt,也还是可以写出上面一样的代码。 总结以上就是简单的 QDir 和 std::filesystem 的不同,综合来看,Qt 库和标准库其实各有优缺,他们的目标和面向的开发者是不同的,Qt 最近一直在尝试使用标准库的内容来代替自己的一部分组件,最新的Qt6 就已经升级到了 C++ 17 标准,将 qSort 宏改为了使用 std::sort,也许在不久的将来,我们再也不用为使用 Qt 还是标准库而争论或者站队。 引用资料std::filesystem https://en.cppreference.com/w/cpp/filesystemQDir https://doc.qt.io/qt-5/qdir.html","categories":[],"tags":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"},{"name":"Qt","slug":"Qt","permalink":"https://blog.justforlxz.com/tags/Qt/"}]},{"title":"How to use cuda with deepin","slug":"How-to-use-cuda-with-deepin","date":"2022-02-25T02:06:05.000Z","updated":"2024-04-15T05:09:54.915Z","comments":true,"path":"2022/02/25/How-to-use-cuda-with-deepin/","permalink":"https://blog.justforlxz.com/2022/02/25/How-to-use-cuda-with-deepin/","excerpt":"","text":"CUDA(Compute Unified Device Architecture,统一计算架构)是由NVIDIA所推出的一种集成技术,是该公司对于GPGPU的正式名称。通过这个技术,用户可利用NVIDIA的GeForce 8以后的GPU和较新的Quadro GPU进行计算。亦是首次可以利用GPU作为C-编译器的开发环境。NVIDIA营销的时候,往往将编译器与架构混合推广,造成混乱。实际上,CUDA可以兼容OpenCL或者自家的C-编译器。无论是CUDA C-语言或是OpenCL,指令最终都会被驱动程序转换成PTX代码,交由显示核心计算。 在论坛上看到有些用户希望在 deepin 下使用 CUDA,但是他们采取的做法往往是手动下载nvidia的二进制文件,直接进行安装。 但是这样会破坏一部分的 glx 链接,导致卸载的时候无法彻底恢复,结果就是系统因为卸载nvidia驱动而废掉,所以我强烈推荐使用包管理器的方式安装 nvidia 驱动和 CUDA 相关的东西,尽量不要手动修改。 在其他发行版,例如 arch,安装 NVIDIA 包会提供一个配置文件自动加载对应的内核模块,而 deepin 的包是来自 debian,debian 并没有做这个事情,这就导致在 deepin 上安装 NVIDIA 驱动后,显示相关的内核模块会被 X 加载,而 CUDA 相关的内核模块并不会被加载,所以我们对包进行了修改,添加了自动加载内核模块的配置文件。 安装依赖如果想要直接使用 CUDA 的开发头文件,那么需要安装以下的包,不过会依赖很多nvidia的库,总量还是有一些的。 sudo apt install nvidia-cuda-dev nvidia-cuda-toolkit 现在,重启一下系统就可以正常使用了。如果不想重启系统,可以手动执行命令加载内核模块。 sudo modprobe nvidia_uvm CUDA 小例子这里有个小栗子,可以用来测试 CUDA 是否能够成功编译和运行 将以下代码保存为 main.cu #include <stdio.h>__global__ void vector_add(const int *a, const int *b, int *c) { *c = *a + *b;}int main(void) { const int a = 2, b = 5; int c = 0; int *dev_a, *dev_b, *dev_c; cudaMalloc((void **)&dev_a, sizeof(int)); cudaMalloc((void **)&dev_b, sizeof(int)); cudaMalloc((void **)&dev_c, sizeof(int)); cudaMemcpy(dev_a, &a, sizeof(int), cudaMemcpyHostToDevice); cudaMemcpy(dev_b, &b, sizeof(int), cudaMemcpyHostToDevice); vector_add<<<1, 1>>>(dev_a, dev_b, dev_c); cudaMemcpy(&c, dev_c, sizeof(int), cudaMemcpyDeviceToHost); printf("%d + %d = %d, Is that right?\\n", a, b, c); cudaFree(dev_a); cudaFree(dev_b); cudaFree(dev_c); return 0;} 编译: nvcc main.cu 运行: ./a.out 如果一切顺利,在编译的时候就不会有报错,不过在我的环境下nvcc会有架构被弃用的警告,本着只要不error就算没事的原则,我们无视这条警告即可。 输出结果: 2 + 5 = 0, Is that right? Machine Learning 测试既然都用上 Nvidia 显卡和 CUDA 驱动了,那肯定是要炼一个丹呀。 先安装 Anaconda,这是一个 python 的发行版,提供了一个完整的科学计算环境,包括 NumPy、SciPy 等常用科学计算库。当然,你有权选择自己喜欢的 Python 环境。 可以根据 anaconda 官方教程来安装 https://docs.conda.io/en/latest/miniconda.html#linux-installers 创建一个存放 tensorflow demo 的目录。 mkdir tensorflow 使用 conda 创建一个新的环境。 cd tensorflowconda create --name tf2 python=3.7 # “tf2”是你建立的conda虚拟环境的名字conda activate tf2 # 进入名为“tf2”的conda虚拟环境 这时候我们就会看到shell中会提示一个 (tf2),说明当前 shell 使用的是 conda venv 环境,我们可以在当前环境中安装各种依赖包。 为了能正常使用 tensorflow,我们还需要安装 cudnn,可以通过 anaconda 来安装。 (tf2) $ conda install -c anaconda cudnn tensorflow-gpu 当环境准备就绪,我们创建一个 tf.py 文件,将测试代码写入: #!/usr/bin/env python3import tensorflow as tfmnist = tf.keras.datasets.mnist(x_train, y_train),(x_test, y_test) = mnist.load_data()x_train, x_test = x_train / 255.0, x_test / 255.0model = tf.keras.models.Sequential([ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(10, activation='softmax')])model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])model.fit(x_train, y_train, epochs=5)model.evaluate(x_test, y_test) 执行这个 py 文件,就可以看到开始自动炼丹。 (tf2) ~/tensorflow$ python tf.py2022-01-06 22:12:31.752249: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcuda.so.12022-01-06 22:12:31.787298: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.787645: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1561] Found device 0 with properties:pciBusID: 0000:01:00.0 name: NVIDIA GeForce RTX 3070 computeCapability: 8.6coreClock: 1.815GHz coreCount: 46 deviceMemorySize: 7.79GiB deviceMemoryBandwidth: 417.29GiB/s2022-01-06 22:12:31.788616: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.12022-01-06 22:12:31.802163: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.102022-01-06 22:12:31.811230: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcufft.so.102022-01-06 22:12:31.813348: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcurand.so.102022-01-06 22:12:31.832304: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusolver.so.102022-01-06 22:12:31.837966: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusparse.so.102022-01-06 22:12:31.874633: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.72022-01-06 22:12:31.874864: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.875707: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.876401: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1703] Adding visible gpu devices: 02022-01-06 22:12:31.877075: I tensorflow/core/platform/cpu_feature_guard.cc:143] Your CPU supports instructions that this TensorFlow binary was not compiled to use: SSE4.1 SSE4.2 AVX AVX2 FMA2022-01-06 22:12:31.903636: I tensorflow/core/platform/profile_utils/cpu_utils.cc:102] CPU Frequency: 3609600000 Hz2022-01-06 22:12:31.906524: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x559ed2e8b830 initialized for platform Host (this does not guarantee that XLA will be used). Devices:2022-01-06 22:12:31.906581: I tensorflow/compiler/xla/service/service.cc:176] StreamExecutor device (0): Host, Default Version2022-01-06 22:12:31.907492: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.908582: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1561] Found device 0 with properties:pciBusID: 0000:01:00.0 name: NVIDIA GeForce RTX 3070 computeCapability: 8.6coreClock: 1.815GHz coreCount: 46 deviceMemorySize: 7.79GiB deviceMemoryBandwidth: 417.29GiB/s2022-01-06 22:12:31.908632: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.12022-01-06 22:12:31.908655: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.102022-01-06 22:12:31.908674: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcufft.so.102022-01-06 22:12:31.908695: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcurand.so.102022-01-06 22:12:31.908714: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusolver.so.102022-01-06 22:12:31.908732: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcusparse.so.102022-01-06 22:12:31.908751: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudnn.so.72022-01-06 22:12:31.908855: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.909548: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.910177: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1703] Adding visible gpu devices: 02022-01-06 22:12:31.910430: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcudart.so.10.12022-01-06 22:12:31.964445: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1102] Device interconnect StreamExecutor with strength 1 edge matrix:2022-01-06 22:12:31.964465: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1108] 02022-01-06 22:12:31.964468: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1121] 0: N2022-01-06 22:12:31.964586: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.964866: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.965060: I tensorflow/stream_executor/cuda/cuda_gpu_executor.cc:981] successful NUMA node read from SysFS had negative value (-1), but there must be at least one NUMA node, so returning NUMA node zero2022-01-06 22:12:31.965367: I tensorflow/core/common_runtime/gpu/gpu_device.cc:1247] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 4626 MB memory) -> physical GPU (device: 0, name: NVIDIA GeForce RTX 3070, pci bus id: 0000:01:00.0, compute capability: 8.6)2022-01-06 22:12:31.966801: I tensorflow/compiler/xla/service/service.cc:168] XLA service 0x559ed4798980 initialized for platform CUDA (this does not guarantee that XLA will be used). Devices:2022-01-06 22:12:31.966811: I tensorflow/compiler/xla/service/service.cc:176] StreamExecutor device (0): NVIDIA GeForce RTX 3070, Compute Capability 8.62022-01-06 22:15:23.414471: W tensorflow/core/framework/cpu_allocator_impl.cc:81] Allocation of 188160000 exceeds 10% of free system memory.Epoch 1/52022-01-06 22:15:23.672182: I tensorflow/stream_executor/platform/default/dso_loader.cc:44] Successfully opened dynamic library libcublas.so.101875/1875 [==============================] - 3s 2ms/step - loss: 0.2967 - accuracy: 0.9131Epoch 2/51875/1875 [==============================] - 3s 2ms/step - loss: 0.1440 - accuracy: 0.9578Epoch 3/51875/1875 [==============================] - 3s 2ms/step - loss: 0.1097 - accuracy: 0.9670Epoch 4/51875/1875 [==============================] - 2s 1ms/step - loss: 0.0881 - accuracy: 0.9732Epoch 5/51875/1875 [==============================] - 2s 966us/step - loss: 0.0751 - accuracy: 0.97682022-01-06 22:16:32.382922: W tensorflow/core/framework/cpu_allocator_impl.cc:81] Allocation of 31360000 exceeds 10% of free system memory.313/313 [==============================] - 1s 2ms/step - loss: 0.0716 - accuracy: 0.9781","categories":[{"name":"Solution","slug":"Solution","permalink":"https://blog.justforlxz.com/categories/Solution/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"fix h5py build failed on m1","slug":"fix-h5py-build-failed-on-m1","date":"2022-01-05T03:12:36.000Z","updated":"2024-04-15T05:09:54.966Z","comments":true,"path":"2022/01/05/fix-h5py-build-failed-on-m1/","permalink":"https://blog.justforlxz.com/2022/01/05/fix-h5py-build-failed-on-m1/","excerpt":"","text":"今天在 m1 mbp 上安装 tensorflow-metal 遇到了依赖无法安装的问题,错误的原因是 h5py 这个包无法编译。 在 h5py 的项目里看到了已经解决了,但是仍然需要从源码构建。 https://github.com/h5py/h5py/issues/1810 $ brew install hdf5$ export HDF5_DIR=/opt/homebrew/Cellar/hdf5/$ pip install --no-binary=h5py h5py 测试代码: #!/usr/bin/env python3import tensorflow as tfmnist = tf.keras.datasets.mnist(x_train, y_train),(x_test, y_test) = mnist.load_data()x_train, x_test = x_train / 255.0, x_test / 255.0model = tf.keras.models.Sequential([ tf.keras.layers.Flatten(input_shape=(28, 28)), tf.keras.layers.Dense(128, activation='relu'), tf.keras.layers.Dropout(0.2), tf.keras.layers.Dense(10, activation='softmax')])model.compile(optimizer='adam', loss='sparse_categorical_crossentropy', metrics=['accuracy'])model.fit(x_train, y_train, epochs=5)model.evaluate(x_test, y_test) 运行结果: $ python demo.pyDownloading data from https://storage.googleapis.com/tensorflow/tf-keras-datasets/mnist.npz11493376/11490434 [==============================] - 7s 1us/step11501568/11490434 [==============================] - 7s 1us/stepMetal device set to: Apple M1systemMemory: 16.00 GBmaxCacheSize: 5.33 GB2022-01-05 11:20:21.920404: I tensorflow/core/common_runtime/pluggable_device/pluggable_device_factory.cc:305] Could not identify NUMA node of platform GPU ID 0, defaulting to 0. Your kernel may not have been built with NUMA support.2022-01-05 11:20:21.920545: I tensorflow/core/common_runtime/pluggable_device/pluggable_device_factory.cc:271] Created TensorFlow device (/job:localhost/replica:0/task:0/device:GPU:0 with 0 MB memory) -> physical PluggableDevice (device: 0, name: METAL, pci bus id: <undefined>)2022-01-05 11:20:22.484838: W tensorflow/core/platform/profile_utils/cpu_utils.cc:128] Failed to get CPU frequency: 0 HzEpoch 1/52022-01-05 11:20:22.682840: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:112] Plugin optimizer for device_type GPU is enabled.1875/1875 [==============================] - 11s 5ms/step - loss: 0.2896 - accuracy: 0.9160Epoch 2/51875/1875 [==============================] - 9s 5ms/step - loss: 0.1391 - accuracy: 0.9585Epoch 3/51875/1875 [==============================] - 9s 5ms/step - loss: 0.1036 - accuracy: 0.9684Epoch 4/51875/1875 [==============================] - 8s 4ms/step - loss: 0.0844 - accuracy: 0.9739Epoch 5/51875/1875 [==============================] - 8s 4ms/step - loss: 0.0715 - accuracy: 0.97762022-01-05 11:21:07.206209: I tensorflow/core/grappler/optimizers/custom_graph_optimizer_registry.cc:112] Plugin optimizer for device_type GPU is enabled.313/313 [==============================] - 2s 5ms/step - loss: 0.0738 - accuracy: 0.9784 Apple 给的适配 tensorflow 的 metal 插件安装方案https://developer.apple.com/metal/tensorflow-plugin/","categories":[],"tags":[]},{"title":"Starting sessions with systemd part 2","slug":"Starting-sessions-with-systemd-part-2","date":"2021-12-29T13:09:07.000Z","updated":"2024-04-15T05:09:54.952Z","comments":true,"path":"2021/12/29/Starting-sessions-with-systemd-part-2/","permalink":"https://blog.justforlxz.com/2021/12/29/Starting-sessions-with-systemd-part-2/","excerpt":"","text":"接着上一篇文章 Starting sessions with systemd,这篇文章主要讲解一下具体的实现。 https://github.com/linuxdeepin/dde-session 已经包含了所有的文件和提交。 上面文章说过,为了让 dde 使用 systemd –user 来关系服务,我提供了一组服务: dde-session-initialized.targetdde-session-manager.servicedde-session-manager.targetdde-session-pre.targetdde-session-restart-dbus.servicedde-session-shutdown.servicedde-session-shutdown.targetdde-session.targetdde-session-x11-services-ready.targetdde-session-x11-services.targetdde-session-x11.targetorg.deepin.Session.service lightdm 在认证通过以后,会启动 dde-session 进程,dde-session 会通过 systemd 的 dbus 启动 org.deepin.Session.service。 在 org.deepin.Session.service 中会执行 dde-session-ctl --systemd-service,启动 dde-session-x11.target. dde-session-x11.target 关联了所有初始化的服务,systemd 会帮助自动运行这些服务,并且最终会运行到 dde-session-manager.service 上。 dde-session-manager.service [Service]Type=simpleExecStart=/usr/bin/startddeExecStopPost=/usr/lib/libexec/dde-session-ctl --logout 在 org.deepin.Session.service 中会启动一个特殊的服务,这个服务会监听 dde-session 的退出,因为总要有一个服务去监听 session 的状态,要确保 session 在服务在,session 退服务退,服务退 session 退,共存亡,这也是所有操作中最麻烦的一步。 org.deepin.Session.service [Service]Type=dbusBusName=org.deepin.SessionExecStart=/usr/bin/dde-session --systemd-serviceExecStop=/usr/lib/libexec/dde-session-ctl --shutdown 在 dde-session-ctl 中是这么实现 shutdown 的: if (isShutdown) { // kill startdde-session or call login1 QDBusInterface systemd("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager"); qInfo() << systemd.call("StartUnit", "dde-session-shutdown.target", "replace-irreversibly"); return 0;} 在 dde-session-ctl 中是这么实现 logout 的: if (parser.isSet(logout)) { org::deepin::Session session("org.deepin.Session", "/org/deepin/Session", QDBusConnection::sessionBus()); session.Logout(); return 0;} dde-session main.cpp // 添加启动参数,表示这是通过 systemd 启动的。QCommandLineOption systemd(QStringList{"d", "systemd-service", "wait for systemd services"});parser.addOption(systemd);parser.process(app);if (parser.isSet(systemd)) { return -1;}QDBusServiceWatcher *watcher = new QDBusServiceWatcher("org.deepin.Session", QDBusConnection::sessionBus(), QDBusServiceWatcher::WatchForUnregistration); watcher->connect(watcher, &QDBusServiceWatcher::serviceUnregistered, [=] { QDBusInterface systemdDBus("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager"); qInfo() << systemdDBus.call("StartUnit", "dde-session-shutdown.service", "replace"); qApp->quit(); }); qInfo() << systemdDBus.call("StartUnit", "dde-session-x11.target", "replace");} dde-session-shutdown.service 只负责执行 dde-session-ctl --shutdown,用于启动 dde-session-shutdown.target。 dde-session-shutdown.service [Service]Type=oneshotExecStart=/usr/lib/libexec/dde-session-ctl --logout 通过 systemd 的接口启动了 dde-session-shutdown.target,从而启动了清理流程。 dde-session-shutdown.target 没有执行任何内容,仅仅是关联了一组服务,但是是冲突这些服务,因为这是启动的时候 在 dde-session-shutdown.target 中还关联了最重要的一个服务: dde-session-restart-dbus.service,这个服务是用来关闭 dbus.service 服务的,这样就可以保证 session 结束的时候,不会有 dbus 服务逃逸出去。 dde-session-restart-dbus.service [Service]Type=notifyExecStart=/usr/lib/libexec/dde-session-ctl --restart-dbus 在 dde-session-ctl 中是这么实现 restart-dbus 的: if (isRestartDBus) { QDBusInterface systemd("org.freedesktop.systemd1", "/org/freedesktop/systemd1", "org.freedesktop.systemd1.Manager"); qInfo() << systemd.call("StopUnit", "dbus.service", "replace-irreversibly"); return 0;} 复盘一下整个流程,在登录界面输入密码登录以后,lightdm 会启动 dde-session 作为 session 的入口,dde-session 会注册一个 org.deepin.Session 的 dbus 服务,并且会启动关联的 systemd 服务,启动一个 dde-session --systemd-service,在这个进程里,会启动 dde-session-x11.target,从而最终启动了 dde-session-manager.service,将原本的会话入口 startdde 启动了。 在 dde-session --systemd-service 会监听 org.deepin.Session 服务的存活,当服务不存在时,就会开始执行退出流程,从而启动了 dde-session-ctl --shutdown,在 dde-session-ctl 的帮助下,启动了 dde-session-shutdown.target,将所有启动的 dde 核心服务都冲突掉,从而完成了服务关闭,在最后阶段再将 dbus.service 服务停止,完成最终的清理。 还有一种退出模式,手动启动了 dde-session-shutdown.service 服务,也会触发完成的退出流程,dde-session-shutdown.service 会利用 dde-session-ctl --logout 将会话注销,从而触发上面的执行流程,完成会话与服务的关闭。 目前还有一些问题没有解决,没有创建独立的 systemd.slice 和 systemd.scope,这可以帮助 dde 细致的划分和管理自己的子 services,退出 dbus.service 的手段非常黑,而且 dbus 服务的程序由于图形服务也已经结束了,导致成片的 X11 Error。这些都是要解决的。 下一步准备写几个 service,用来代替 startdde 的组件启动,比如 kwin、dde-desktop、dde-dock 和 xdg-autostart 等。 就目前的情况来看,任重而道远。","categories":[],"tags":[{"name":"systemd","slug":"systemd","permalink":"https://blog.justforlxz.com/tags/systemd/"}],"author":"justforlxz"},{"title":"Starting sessions with systemd","slug":"Starting-sessions-with-systemd","date":"2021-12-25T14:00:53.000Z","updated":"2024-04-15T05:09:54.953Z","comments":true,"path":"2021/12/25/Starting-sessions-with-systemd/","permalink":"https://blog.justforlxz.com/2021/12/25/Starting-sessions-with-systemd/","excerpt":"","text":"DDE 现在正在做 Wayland 的支持,所以我们需要对目前的桌面环境结构进行调整,考虑到 GNOME 和 KDE 都已经使用 systemd 来管理 session,我认为 deepin 团队也可以考虑这一步了。 为什么需要 systemd?目前 systemd 作为事实上胜利的 init 进程,它现在负责的功能已经越来越多了,支持但不限于:udev、sleep/hibernate/suspend、dbus、services 等。现在越来越多的程序也开始使用 systemd 管理后台服务。 不使用 systemd 来管理 session 会有什么问题吗?存在一个进程逃逸问题,不过这个问题与是否采用 systemd 管理 session 没有太大关系。 我先来简单介绍一下目前 DDE 的工作模型: 首先,DDE 使用 LightDM 作为显示服务器(Display Manager),DDE 提供了 lightdm-deepin-greeter 作为登录界面,greeter 可以通过调用 LightDM 的 api 进行 linux-pam 的认证。 当认证通过后,LightDM 会启动一个会话(Session),之后运行的程序都将是以登录的用户身份启动,然后 systemd --user 进程会被启动,同时 dbus-daemon 也会启动,之后 LightDM 会启动 startdde。 此时就开始执行到 DDE 的初始化阶段,startdde 会启动 dde-session-daemon 和 DDE 的核心组件,之后就并行启动 autostart 中的 desktop 文件。 startdde 目前的流程本身没有问题,问题出在 systemd 接管了 dbus,并且从 systemd 226 版本开始,/etc/pam.d/system-login 默认配置中的 pam_systemd 模块会在用户首次登录的时候, 自动运行一个 systemd --user 实例。 只要用户还有会话存在,这个进程就不会退出;用户所有会话退出时,进程将会被销毁。当#随系统自动启动 systemd 用户实例启用时, 这个用户实例将在系统启动时加载,并且不会被销毁。 systemd --user 实例是针对每个用户处理的,而不是针对会话。这样做的原理是用户服务处理的大部分资源,像 socket 或状态文件是针对每个用户的(存活于用户的主目录下)而不是会话。这意味着所有的用户服务是独立于会话之外运行的。最终,我们得出结论:基于会话运行的程序可能会导致用户服务中断。systemd 处理用户会话的方式是非常生硬的(pretty much in flux)。 那么问题就来了,上面说了,systemd 接管了 dbus 服务,通过 dbus 启动的程序会在 dbus.service 下面,成为它的子进程,而 systemd --user 在有用户其它登录的会话存在时,并不会停止 systemd --user,所以 dbus.service 也不会停止。但是 dde 的会话已经完成注销了,但是通过 dde 启动的程序却没有被退出,从而导致了程序 逃逸。 解决方案?如果只是想解决逃逸问题,那么想一个办法,在 session 停止的时候,主动把 dbus.service 服务停掉其实就可以解决了,但是我们想要利用 systemd 的自动依赖解决,这样就不用重复造一点点小轮子了。 那么需要怎么修改才能符合桌面环境的要求? GNOME 和 KDE 现在已经完成了 systemd session 的工作,我们可以在这两个老大哥身上学习。 提供了一个新的会话入口点 gnome-session-systemd。 这需要一个参数,即开始会话的单元(通常是 .target 单元)。 它将执行一些清理功能,初始化 systemd 环境,然后启动单元并等待它停止。会话通过修改它们的 Exec 行并删除 RequiredComponents 来选择以这种方式启动。 XDG autostart 现在 systemd 提供了一个 wrapper,可以自动生成出 service 单元,所以功能可以复用。 在 systemd 管理的系统上,每个用户都被分配了一个 user-x.slice (systemd.slice(5)),并且用户的会话将在 session-Y.scope (systemd.scope(5)) 中运行。您还可以在主机上看到一些其它用户特定的单元,包括 [email protected],它是用户的 systemd 实例。这是用户的一个单独的 systemd 进程,如果用户不再登录,它将再次关闭。 随着 systemd 的移动,不仅 DBus 激活的应用程序和服务,您的整个会话现在都使用用户的 systemd 实例启动。 这有一些副作用,起初可能看起来很奇怪。 例如,之前提到的 session-Y.scope 曾经包含 200 多个进程,现在减少到仅 4 个进程。 另一个副作用是更难理解进程属于哪个会话(这与许多服务相关)或者 ps 将不再显示 tty。 但是根据 GNOME blog 的文章说明,这些副作用已经被处理了。GNOME会话仍然始终绑定到session-Y.scope(例如,使用 loginctl kill-session 继续可靠地工作)。 查看了 GNOME 提供的 dbus 服务单元文件,最终明白了 GNOME 是怎么做到防止 dbus 程序逃逸的,其实操作非常简单,就是提供了一个 gnome-session-shutdown.target,在这个 target 里关联了 gnome-sessino-restart-dbus.service,里面执行的是 gnome-session-ctl --restart-dbus,其实就是通过 DBus 调用了 systemd --user 的 dbus 方法,将 dbus.service 给停掉了。(- - |) 改造方案既然了解了前因后果,那么我们可以着手改造 DDE 了。 还需要补充一个知识,会话启动以后,会有一个进程作为会话的入口,会话所有的程序都是从这个入口开始启动的,这个入口就是 startdde。 我们既然希望利用 systemd 的服务来启动,并且帮助我们自动解决依赖,但是拆分项目目前压力比较大,我们还计划重写一部分后端功能,可以在重写时进行调整。 启动入口首先我们需要创建一系列的 service 和 target 文件。 dde-session-initialized.targetdde-session-manager.servicedde-session-manager.targetdde-session-pre.targetdde-session-restart-dbus.servicedde-session-shutdown.servicedde-session-shutdown.targetdde-session.targetdde-session-x11-services-ready.targetdde-session-x11-services.targetdde-session-x11.targetorg.deepin.Session.service 看起来非常的多,不用担心,他们都有各自的作用,你可以认为这是将生命周期在 systemd 实现了。 由于 systemd 会帮助我们自动完成依赖解析,那么我们只需要保留一个入口服务,其它服务都禁止手动启动即可。 我选择使用 dde-session-x11.target 作为入口,因为未来 deepin 还要支持 wayland,那么在这里就区分开会比较方便一些,因为桌面环境的后续启动与使用什么图形服务没有太大关系。 根据文件名称就可以方便的了解到这些文件是在什么阶段被执行的。 退出时清理创建的 dde-session-shutdown.target 用来关联所有退出时需要执行的 services。 清理 dbus.service提供了一个 dde-session-restart-dbus.service 用来注销以后关闭 dbus.service,不要问,问就是没办法~。 在 dde-session-shutdown.target 中会关联这个服务,当用户的会话注销或者桌面环境服务出现问题时,就可以退出所有的 dbus.service。 当发现会话注销时,dde-session-manager.service 会执行退出,在服务关闭时启动 dde-session-shutdown.target,并且使用 replace-irreversibly 标记为不可撤销。 dde-session-shutdown.target 中又会清理 dbus.service 下的所有程序,这样就避免了服务可以通过 dbus 逃逸出会话。 架构模型 最终效果可以看到 startdde 和它的进程树挂在 systemd 下面。 原本的位置现在只有一个占位的程序。 引用资料 https://blogs.gnome.org/benzea/2019/10/01/gnome-3-34-is-now-managed-using-systemd/","categories":[],"tags":[{"name":"systemd","slug":"systemd","permalink":"https://blog.justforlxz.com/tags/systemd/"}],"author":"justforlxz"},{"title":"introduction-to-the-qml-cmake-api(中文翻译)","slug":"introduction-to-the-qml-cmake-api","date":"2021-09-14T00:35:33.000Z","updated":"2024-04-15T05:09:55.010Z","comments":true,"path":"2021/09/14/introduction-to-the-qml-cmake-api/","permalink":"https://blog.justforlxz.com/2021/09/14/introduction-to-the-qml-cmake-api/","excerpt":"","text":"原文: https://www.qt.io/blog/introduction-to-the-qml-cmake-api 当 Qt 6 迁移到 CMake 时,我们希望为 QML 项目提供更好的体验。在最初的Qt 6.0发布版中,我们只提供了一些技术预览API,这并没有比 qmake 自 Qt 5.15 以来提供的 API 做得更多。 现在,随着即将发布的 Qt 6.2,我们已经完成了 CMake API。在这篇博客文章中,我们将说明为什么需要为 QML 添加构建系统支持,并展示如何实现两个常用用例。 为什么我们需要CMake的支持?在讨论创建 QML 应用程序和库的新方法之前,我们首先应该了解到目前为止它们是如何完成的: 只有自定义 c++ 被放到实际的QML名称空间中;无论是通过 5.15 以来的声明式类型注册,还是仍然使用手动 qml*Register 调用。 QML 文件通常简单地放在单个文件夹中。如果它们相互引用,人们通常依赖于隐式导入( QML 文档可以在其他 QML 文档中使用类型,只要它们在同一个文件夹中)。大多数人都忽略了 qmldir 文件,除非他们有很强的理由使用它们(很可能是因为他们需要一个 QML 单例)。然后,应用程序只需通过 QQmlApplicationEngine 或 QQuickWindow 加载主 QML 文件。 图像、着色器或字体等附加资源通常被放入资源系统中。然后,QML 文件将通过qrc 路径引用它们。因此,您不能再使用 QML(场景)中的 QML 文件或 QtCreator 中的 QML 设计器。 要使用 qmlcachegen,需要创建一个包含所有 QML 文件的 QRC 文件,然后通过指定 QT += qtquickcompiler.prf 启用 qmlcachegen 这只会在您从资源文件系统加载相应文件时生效,但是,从物理文件系统加载文件时,它们仍将在运行时编译。 上面已经列出了一些由迄今为止使用的相当特别的方法引起的问题。 通常,我们的工具很难对 QML 项目进行推理。 输入 CMake 和 qt6_add_qml_module。 有了这个新引入的函数,构建系统就知道 QML 项目的所有部分,并且可以根据需要将其传递给我们的工具。 让我们看几个例子! 一个基本的 QML 应用程序……我们从一个小小的“Hello, World!”开始 例子。 我们在同一目录中有一个 QML 文件 main.qml import QtQuickWindow { id: root visible: true Text { text: "Hello, world!" anchors.centerIn: parent }} 和一个实例化它的 main.cpp 文件: #include <QGuiApplication>#include <QQmlApplicationEngine>int main(int argc, char *argv[]){ QGuiApplication app(argc, argv); QQmlApplicationEngine engine; const QUrl url(u"qrc:/hello/main.qml"_qs); engine.load(url); return app.exec();} 除了 URL 中的“hello”外,大部分都很简单。我们一会儿再回到这个问题上。但首先,让我们看看构建项目所需的CMakeLists.txt文件: 首先,我们做一些通用的设置。 cmake_minimum_required(VERSION 3.18)project(hello VERSION 1.0 LANGUAGES CXX)set(CMAKE_AUTOMOC ON)set(CMAKE_CXX_STANDARD 17)set(CMAKE_CXX_STANDARD_REQUIRED ON)find_package(Qt6 6.2 COMPONENTS Quick Gui REQUIRED)qt_add_executable(myapp main.cpp)target_link_libraries(myapp PRIVATE Qt6::Gui Qt6::Quick) 我们添加了 Quick 模块,并告诉 CMake 为我们的可执行文件创建一个目标,接下来会发生有趣的部分: qt_add_qml_module(myapp URI hello VERSION 1.0 QML_FILES main.qml) 在这里,我们终于看到了 qt_add_qml_module 的作用。 我们将可执行文件的目标、URI、模块版本和 QML 文件列表传递给它。 反过来,它确保 myapp 成为 QML 模块。 除此之外,这会将 QML 文件放入资源文件系统中的 qrc:/${URI} 中。 这现在可能看起来很奇怪,实际上在这篇博文的结尾看起来仍然很奇怪:您必须等待即将发布的关于 QML 模块的博文,我们将详细解释为什么需要这样做。 在这一点上,您可能会想知道这种奇怪是否真的值得。 毕竟在 Qt 5 中使用 cmake 已经可以将文件放入资源系统,而无需太多仪式! 请放心,我们已经获得了两件事:第一,qt_add_qml_module 确保 qmlcachegen 运行。 其次,它创建一个 myapp_qmllint 目标,该目标对 QML_FILES 中的文件运行 qmlint。 阅读我们关于工具的博客文章,了解为什么要运行 qmllint。 变得更加先进让我们扩展这个示例,使其更有趣。我们添加了一个新的 QML 文件,FramedImage.qml 和 img 子目录中的图像。 import QtQuickRectangle { border.width: 2 border.color: "black" Image { source: Qt.resolvedUrl("img/world.png") anchors.centerIn: parent sourceSize.width: parent.width sourceSize.height: parent.height }} 在 main.qml 中使用: import QtQuickWindow { id: root visible: true Text { text: "Hello, world!" anchors.centerIn: parent color: "white" z: 2 } FramedImage { anchors.fill: parent }} 更新我们的 CMakeList.txt: qt_add_qml_module(myapp URI hello VERSION 1.0 QML_FILES main.qml FramedImage.qml RESOURCES img/world.png) 您可能已经预料到 QML_FILES 现在也会列出 FramedImage.qml。 更有趣的变化是 RESOURCES 条目。 通过在那里添加引用的资源,它们会自动添加到与 QML 文件相同的根路径下的应用程序 - 也在资源文件系统中。 无需重复 qt_add_resources 中的路径。 并且通过保持资源系统中的路径与 source 和 build 目录中的路径一致,我们确保始终找到图像: 由于它是相对于 FramedImage.qml 解析的,因此它指的是资源文件系统中的图像 如果我们从那里加载 main,或者如果我们使用 qml 工具检查它,则加载到实际文件系统中的那个。 创建一个 qml 库现在,让我们开始第二个例子,我们创建了一个向 QML 公开 C++ 类型的库。 我们这个例子的目录结构看起来像: ├── CMakeLists.txt└── example └── mylib ├── CMakeLists.txt ├── mytype.cpp └── mytype.h mytype.h 声明一个类并使用 Qt 5.15 中引入的声明性注册宏将其公开给引擎: #include <QObject>#include <QtQml/qqmlregistration.h>class MyType : public QObject{ Q_OBJECT QML_ELEMENT Q_PROPERTY(int answer READ answer CONSTANT) public: int answer() const;}; 顶层 CMakeLists.txt 文件进行一些基本设置,然后使用 add_subdirectory 将其包含在 mylib 中。 我们使用与 QML 模块的 URI 相对应的子目录结构,但将点替换为斜杠 - 这与引擎在导入路径中搜索模块时使用的逻辑相同。 通过遵循该约定,我们有助于工具。 qt6_add_qml_module(mylib URI example.mylib VERSION 1.0 SOURCES mytype.h mytype.cpp) 首先,这次我们要添加 C++ 类型。 为此,我们使用 SOURCES 参数指定它们。 此外,这次我们还没有为 mylib 创建任何目标。 如果 qt6_add_qml_module 的 target 参数不存在,它会自动创建一个库目标,这就是我们在这种情况下想要的。 如果您构建项目,您会注意到我们不仅构建了库,还构建了一个 QML 插件——即使我们没有编写自定义 QQmlEngineExtensionPlugin。 那么插件有什么作用呢? 就其本身而言,不是很多。 mylib 库本身已经包含向引擎注册类型的代码。 但是,这仅在我们可以链接库的情况下才有用。 为了使模块在 qml 工具加载的 QML 文件中可用,我们需要一些可以加载的插件。 然后插件负责实际链接库,并确保类型得到注册。 应该注意的是,自动插件生成只有在模块除了注册类型之外不做任何事情时才可能。 如果它需要做一些更高级的事情,比如在 initializeEngine 中注册一个图像提供者,你仍然需要手动编写插件。 qt6_add_qml_module 通过 NO_GENERATE_PLUGIN_SOURCE 对此提供支持。 我们将在稍后的博客文章中更详细地介绍这一点。 另外,您还记得我们提到遵循目录布局约定有助于工具吗? 该布局反映在我们的构建目录中,插件就位。 这意味着您可以将构建目录的路径传递给 qml 工具(通过 -I 标志),它会找到插件。 反过来,这可以帮助您测试纯 C++ 库模块上的更改,因为您不需要执行任何进一步的安装步骤。 在结束之前,让我们向模块添加一个 QML 文件:在 lib 子文件夹中,我们添加一个 Mistake.qml 文件: import example.mylibMyType{ answer: 43} 并调整 qt6_add_qml_module 调用: qt6_add_qml_module(mylib URI example.mylib VERSION 1.0 SOURCES mytype.h mytype.cpp QML_FILES Mistake.qml) 顾名思义(Mistake),我们在这里犯了一个错误: answer 实际上是一个只读属性。 我们为什么要这样做? 当然,为了炫耀承诺的 qmllint 集成:CMake 将为我们创建一个 qmllint 目标,一旦我们运行它,qmllint 就会警告我们这个问题: (只有当您使用了dev分支最近的qmllint时) $> cmake --build . --target mylib_qmllint...Warning: Mistake.qml:4:13: Cannot assign to read-only property answer answer: 43 展望这是关于新 CMake API 的系列文章中的第一篇博文。 我们介绍了 qt6_add_qml_module,并展示了它如何处理一些基本用例。 我们已经看到 CMake 通过处理重复性任务来帮助你,比如手动编写 QML 插件,以及它如何帮助我们的工具故事。 在本博客系列的下一部分中,我们将深入研究 QML 模块,并查看一些更高级的用例:包含多个 QML 库的应用程序和安装 QML 模块库。 如果您想了解有关它提供的所有选项的更多信息,还可以查看 qt6_add_qml_module 的参考文档。","categories":[],"tags":[{"name":"Qt","slug":"Qt","permalink":"https://blog.justforlxz.com/tags/Qt/"}]},{"title":"Vuex基本使用","slug":"Vuex基本使用","date":"2021-08-10T05:56:30.000Z","updated":"2024-04-15T05:09:54.954Z","comments":true,"path":"2021/08/10/Vuex基本使用/","permalink":"https://blog.justforlxz.com/2021/08/10/Vuex%E5%9F%BA%E6%9C%AC%E4%BD%BF%E7%94%A8/","excerpt":"","text":"最近在写 deepincn 的首页,看着 elementary os 的首页样式挺好看的,就打算山寨一下。 本来打算用 Angular 开发的,但是看到 Vue3 已经十分稳定了,就打算重拾 Vue3,用 Vue3 来写新的首页。 为了方便以后更新数据,需要使用集中的数据管理,Vue 下比较出名和好用的就是 Vuex 了,而 Vuex 也已经适配了 Vue3,可以放心使用了。 本篇文章会见简单介绍一下 Vuex,然后讲一下基本语法,我会再开( 水 )一篇文章来专门山寨一个 Vuex,通过实现一个简易的 Vuex 来深入了解核心。 1. 简介Vue 最重要的一个功能点就是 数据驱动。 所谓数据驱动,是指视图是由数据驱动生成的,我们对视图的修改,不会直接操作 DOM,而是通过修改数据。它相比我们传统的前端开发,如使用 jQuery 等前端库直接修改 DOM,大大简化了代码量。 特别是当交互复杂的时候,只关心数据的修改会让代码的逻辑变的非常清晰,因为 DOM 变成了数据的映射,我们所有的逻辑都是对数据的修改,而不用碰触 DOM,这样的代码非常利于维护。 Vue 还有一个重要的功能点是 组件化,每个组件就是最小的功能单位,每个组件都有自己的 data,template 和 methods。我们通过修改 data 中的数据来更新 template 中视图。在单个组件中我们更新视图是非常方便的,但是实际开发中,我们的项目不止一个组件,非常多的组件整合在一起的时候,维护同一个状态就会非常困难,特别是嵌入非常深的时候,传递参数就会变得非常麻烦(这里我想起来了 Deepin 的控制中心,组件里面有非常深的嵌套,最里面的组件想要获取数据,需要沿着嵌套层层传递,非常麻烦)。 为了解决这个问题,我们需要引入 Vuex 来进行状态管理,负责组件中的通信。 Vuex 解决的问题 多个视图依赖于同一状态。 来自不同视图的行为需要变更同一状态。 2. 基础使用首先先在项目中安装 Vuex,Vuex4 是为 Vue3 开发的版本,我们通过以下命令安装: npm npm install vuex@next yarn yarn add vuex@next 每一个 Vuex 应用的核心就是 store(仓库)。“store”基本上就是一个容器,它包含着你的应用中大部分的状态 (state)。Vuex 和单纯的全局对象有以下两点不同: Vuex 的状态存储是响应式的。当 Vue 组件从 store 中读取状态的时候,若 store 中的状态发生变化,那么相应的组件也会相应地得到高效更新。 你不能直接改变 store 中的状态。改变 store 中的状态的唯一途径就是显式地提交 (commit) mutation。这样使得我们可以方便地跟踪每一个状态的变化,从而让我们能够实现一些工具帮助我们更好地了解我们的应用。 现在已经安装好 Vuex 了,我们可以开始写一个简单的 store。创建一个 store.ts 文件。 import { createStore } from 'vuex'const store = createStore({ state: { count: 0 }, mutations: { increment (state) { state.count++ } },});export { store }; 在 main.ts 中,将 store 注册到 Vue 的 app 实例中。 import { store } from './store'...app.use(store);app.mount('#app'); // 添加在挂载之前 以上就是一个最简单的 store 的例子了,是不是有些摸不着头脑? store.ts 中我们导出了使用 createStore 函数返回的对象,在 store 中,我们定义了两个属性:state 和 mutations。 我们在 state 中定义状态,在 mutations 中定义操作方法,在 mutations 中定义的方法,必须通过 store.commit() 方法调用,而不能直接访问。 现在我们在组件中使用 store 吧。 <template> <div> <p>{{ count }}</p> </div></template><script lang="ts">import { defineComponent } from 'vue';export default defineComponent({ setup() { return { count: this.$store.state.count }; }, methods: { increment() { this.$store.commit('increment') } }});</script> 在组件的例子中,setup 函数是 Vue3 引入的 组合式 API(composition API),这里不详细介绍 setup 的使用,setup 的出现,我们可以将相同功能的代码放在接近的位置,只需要在函数最后返回视图(template)所需的变量即可。 在例子中,setup 向视图提供了 count 变量,这里使用了解构赋值,模板使用 count 变量时,实际上访问的还是 store 中的 count,为什么呢? 这是因为 Vuex 的状态存储是响应式的,具体请看 《Vue 响应式原理》。 在例子中,组件提供了一个方法,用于修改 store 中的 count,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。Vuex 中的 mutation 非常类似于事件:每个 mutation 都有一个字符串的事件类型 (type)和一个回调函数 (handler)。这个回调函数就是我们实际进行状态更改的地方,并且它会接受 state 作为第一个参数。 你不能直接调用一个 mutation 处理函数。这个选项更像是事件注册:“当触发一个类型为 increment 的 mutation 时,调用此函数。”。 3. 核心概念Vuex 一共有五个核心概念: State、Getter、Mutation、Action 和 Module。 StateVuex 使用单一状态树,用一个对象就包含了全部的应用层级状态。至此它便作为一个“唯一数据源 (SSOT)”而存在。这也意味着,每个应用将仅仅包含一个 store 实例。单一状态树让我们能够直接地定位任一特定的状态片段,在调试的过程中也能轻易地取得整个当前应用状态的快照。 在组件中,我们可以通过 this.$store.state 来获取注册到的 state。每当 store.state.count 变化的时候, 都会重新求取计算属性,并且触发更新相关联的 DOM。 Vuex 通过 Vue 的插件系统将 store 实例从根组件中“注入”到所有的子组件里。且子组件能通过 this.$store 访问到。 template 可以直接使用 this.$store.state.[属性] ,this 可以省略。 <template> <div id="app"> {{ this.$store.state.name }} {{ this.$store.state.age }} </div></template> Getter有时候我们需要从 store 中的 state 中派生出一些状态,例如对列表进行过滤并计数: doneTodosCount () { return this.$store.state.todos.filter(todo => todo.done).length} 如果有多个组件需要用到此属性,我们要么复制这个函数,或者抽取到一个共享函数然后在多处导入它——无论哪种方式都不是很理想。 Vuex 允许我们在 store 中定义“getter”(可以认为是 store 的计算属性)。 Getter 接受 state 作为其第一个参数: const store = createStore({ state: { todos: [ { id: 1, text: '...', done: true }, { id: 2, text: '...', done: false } ] }, getters: { doneTodos (state) => { return state.todos.filter(todo => todo.done) } }}) Getter 会暴露为 store.getters 对象,你可以以属性的形式访问这些值: store.getters.doneTodos // -> [{ id: 1, text: '...', done: true }] Mutation在上面已经说过了,更改 Vuex 的 store 中的状态的唯一方法是提交 mutation。 const store = createStore({ state: { count: 1 }, mutations: { increment (state) { // 变更状态 state.count++ } }}) 需要使用 commit 方法来提交一个调用: store.commit('increment') 也可以提交多个参数,commit 函数会展开所有参数: // ...mutations: { increment (state, n) { state.count += n }} store.commit('increment', 10) ActionAction 类似于 mutation,不同在于: Action 提交的是 mutation,而不是直接变更状态。 Action 可以包含任意异步操作。 const store = createStore({ state: { count: 0 }, mutations: { increment (state) { state.count++ } }, actions: { increment (context) { context.commit('increment') } }}) Action 函数接受一个与 store 实例具有相同方法和属性的 context 对象,因此你可以调用 context.commit 提交一个 mutation,或者通过 context.state 和 context.getters 来获取 state 和 getters。 Action 通过 store.dispatch 方法触发: store.dispatch('increment') 乍一眼看上去感觉多此一举,我们直接分发 mutation 岂不更方便?实际上并非如此,还记得 mutation 必须同步执行这个限制么?Action 就不受约束!我们可以在 action 内部执行异步操作: actions: { incrementAsync ({ commit }) { setTimeout(() => { commit('increment') }, 1000) }} Module由于使用单一状态树,应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时,store 对象就有可能变得相当臃肿。 为了解决以上问题,Vuex 允许我们将 store 分割成模块(module)。每个模块拥有自己的 state、mutation、action、getter、甚至是嵌套子模块——从上至下进行同样方式的分割: const moduleA = { state: () => ({ ... }), mutations: { ... }, actions: { ... }, getters: { ... }}const moduleB = { state: () => ({ ... }), mutations: { ... }, actions: { ... }}const store = createStore({ modules: { a: moduleA, b: moduleB }})store.state.a // -> moduleA 的状态store.state.b // -> moduleB 的状态 对于模块内部的 mutation 和 getter,接收的第一个参数是模块的局部状态对象。 const moduleA = { state: () => ({ count: 0 }), mutations: { increment (state) { // 这里的 `state` 对象是模块的局部状态 state.count++ } }, getters: { doubleCount (state) { return state.count * 2 } }} 同样,对于模块内部的 action,局部状态通过 context.state 暴露出来,根节点状态则为 context.rootState: const moduleA = { // ... actions: { incrementIfOddOnRootSum ({ state, commit, rootState }) { if ((state.count + rootState.count) % 2 === 1) { commit('increment') } } }} 默认情况下,模块内部的 action 和 mutation 仍然是注册在全局命名空间的——这样使得多个模块能够对同一个 action 或 mutation 作出响应。Getter 同样也默认注册在全局命名空间,但是目前这并非出于功能上的目的(仅仅是维持现状来避免非兼容性变更)。必须注意,不要在不同的、无命名空间的模块中定义两个不同的 getter 从而导致错误。 如果希望你的模块具有更高的封装度和复用性,你可以通过添加 namespaced: true 的方式使其成为带命名空间的模块。当模块被注册后,它的所有 getter、action 及 mutation 都会自动根据模块注册的路径调整命名。例如: const store = createStore({ modules: { account: { namespaced: true, // 模块内容(module assets) state: () => ({ ... }), // 模块内的状态已经是嵌套的了,使用 `namespaced` 属性不会对其产生影响 getters: { isAdmin () { ... } // -> getters['account/isAdmin'] }, actions: { login () { ... } // -> dispatch('account/login') }, mutations: { login () { ... } // -> commit('account/login') }, // 嵌套模块 modules: { // 继承父模块的命名空间 myPage: { state: () => ({ ... }), getters: { profile () { ... } // -> getters['account/profile'] } }, // 进一步嵌套命名空间 posts: { namespaced: true, state: () => ({ ... }), getters: { popular () { ... } // -> getters['account/posts/popular'] } } } } }}) 启用了命名空间的 getter 和 action 会收到局部化的 getter,dispatch 和 commit。换言之,你在使用模块内容(module assets)时不需要在同一模块内额外添加空间名前缀。更改 namespaced 属性后不需要修改模块内的代码。 4. 配合 TypeScriptVuex4 使用 TypeScript 还是有一定的麻烦,Vue 提供了一个 InjectionKey 接口,该接口是扩展 Symbol 的泛型类型。它可用于在提供者和消费者之间同步 inject 值的类型。 import { createStore, Store } from 'vuex'import { InjectionKey } from 'vue'import { Menu } from './settings';export interface Menu { icon?: string; text: string; url: string; id: string;}interface MenuState { menus: Menu[];}const MenuKey: InjectionKey<Store<MenuState>> = Symbol()const menuStore = createStore<MenuState>({ state: { menus: [] }, mutations: { add(state: MenuState, menu: Menu) { state.menus.push(menu); } }, actions: { add(context, menu: Menu) { context.commit('add', menu); } }, getters: { menus(state) { return state.menus; } }})export { Menu, MenuKey, menuStore }; 在 main.ts 中注入依赖: import { MenuKey, menuStore } from './store'// ...app.use(menuStore, MenuKey);// ...","categories":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/categories/Web/"},{"name":"Vue","slug":"Web/Vue","permalink":"https://blog.justforlxz.com/categories/Web/Vue/"},{"name":"Vuex","slug":"Web/Vue/Vuex","permalink":"https://blog.justforlxz.com/categories/Web/Vue/Vuex/"}],"tags":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/tags/Web/"}]},{"title":"CSS浮动笔记","slug":"CSS浮动笔记","date":"2021-08-07T03:02:20.000Z","updated":"2024-04-15T05:09:54.912Z","comments":true,"path":"2021/08/07/CSS浮动笔记/","permalink":"https://blog.justforlxz.com/2021/08/07/CSS%E6%B5%AE%E5%8A%A8%E7%AC%94%E8%AE%B0/","excerpt":"","text":"DOM文档排版的特点块级元素只能垂直排版,行内元素只能水平排版。 块级元素可以设置宽高属性,行内元素没有宽高属性,只能被内部元素撑起来,水平放置不下时会换行继续排版。 CSS 浮动浮动就是让文档脱离文档流,在父元素内部可以移动到指定位置。 浮动的元素可以设置宽高,像块级元素一样,又向行内元素一样可以水平排版。 <!doctype html><html><head><meta charset="utf-8"><title>元素的浮动</title><style type="text/css">/*定义父元素的样式*/.father { background: #ccc; border: 1px dashed #999;}/*定义 box01、 box02、 box03 三个盒子的样式*/.box01, .box02, .box03 { height: 50px; width: 50px; background: #FF9; border: 1px solid #F33; margin: 15px; padding: 0px 10px;}/*定义段落文本的样式*/p { background: #FCF; border: 1px dashed #F33; padding: 0px 10px;}</style><style>.box01, .box02, .box03 { float: left;}</style></head><body> <div class="father"> <div class="box01">box01</div> <div class="box02">box02</div> <div class="box03">box03</div> </div> <p>12</p><!--不定义float属性,float属性值都为其默认值 none--></body></html> 运行后,box01、box02、box03 及段落文本从上到下一一罗列。 可见如果不对元素设置浮动,则该元素及其内部的子元素将按照标准文档流的样式显示,即块元素占据页面整行。 接下来以 box01 为设置对象,对其应用左浮动样式,在 head 的 style 标签下添加新的 style 标签,具体CSS代码如下: .box01 { float: left;} 可以看到 box01 已经脱离了文档流,接下来为 box02 和 box03 设置左浮动: .box02 { float: left;}.box03 { float: left;} 上述代码运行后,box01、 box02、 box03 三个盒子排列在同一行,同时,周围的段落文本将环绕盒子,出现了图文混排的网页效果。 CSS 浮动清除由于浮动元素不占用原文档流的位置,使用浮动时会影响后面相邻的固定元素。 CSS 提供了 clear 属性,可以清除掉浮动元素。 clear 有三个属性值: left :不允许左侧有浮动元素(清除左侧浮动的影响) right :不允许右侧有浮动元素(清除右侧浮动的影响) both :同时清除左右两侧浮动的影响 浮动清除存在一个问题,浮动清除本质上处理的是左右元素,如果父元素内所有的元素都浮动了,此时因为文档流中没有其他元素可以在内部撑起父元素的高度,父元素此时会坍缩为 0 高度。 方法一:使用空标记清除浮动在浮动元素之后添加空标记,并对该标记应用 clear:both 样式,可清除元素浮动所产生的影响,这个空标记可以为 <div> 、 <p> 、 <hr /> 等任何标记。 需要注意的是,上述方法虽然可以清除浮动, 但是在无形中增加了毫无意义的结构元素(空标记),因此在实际工作中不建议使用。 (空标记)因此在实际工作中不建议使用。 <!doctype html><html><head><meta charset="utf-8"><title>空标记清除浮动</title><style type="text/css">/*没有给父元素定义高度*/.father { background: #ccc; border: lpx dashed #999;}/*定义box01、box02、box03三个盒子左浮动*/.box01, .box02, .box03 { height: 50px; line-height: 50px; background: #f9c; border:1px dashed 999; margin: 15px; padding: 0px 10px; float: left;}/*对空标记应用clear:both;*/.box04 { clear: both;}</style></head><body> <div class="father"> <div class="box01">box01</div> <div class="box02">box02</div> <div class="box03">box03</div> <div class="box04"></div> <!--在浮动元素后添加空标记--> </div></body></html> 方法二:使用 overflow 属性清除浮动对元素应用 overflow: hidden; 也可以清除浮动对该元素的影响,该方法弥补了空标记清除浮动的不足。 对父元素应用 overflow: hidden; 样式来清除子元素浮动对父元素的影响。 父元素又被其子元素撑开了,即子元素浮动对父元素的影响已经不存在。 <!doctype html><html><head><meta charset="utf-8"><title>overflow属性清除浮动</title><style type="text/css">/*没有给父元素定义高度*/.father { background: #ccc; border: 1px dashed #999; /*对父元素应用overflow:hidden;*/ overflow: hidden;}/*定义box01、box02、box03三个盒子左浮动*/.box01, .box02, .box03 { height: 50px; line-height: 50px; background: #f9c; border: 1px dashed #999; margin: 15px; padding: 0px 10px; float: left;}</style></head><body> <div class="father"> <div class="box01">box01</div> <div class="box02">box02</div> <div class="box03">box03</div> </div></body></html> 方法三: 使用 after 伪对象清除浮动使用 after 伪对象也可以清除浮动,但是该方法只适用于 IE8 及以上版本浏览器和其他非 IE 浏览器。 另外,使用 after 伪对象清除浮动时需要注意: (1) 必须为需要清除浮动的元素伪对象设置 height: (); 样式,否则该元素会比其实际高度高出若干像素。 (2) 必须在伪对象中设置 content 属性,属性值可以为空,如 content:" "; 。 <!doctype html><html><head><meta charset="utf-8"><title>使用after伪对象清除浮动</title><style type="text/css">/*没有给父元素定义高度*/.father {background: #ccc;border: 1px dashed #999;}/*对父元素应用after伪对象样式*/.father:after { display: block; clear: both; content:" "; visibility: hidden; height: 0;}/*定义box01、box02、box03三个盒子左浮动*/.box01, .box02, .box03 { height: 50px; line-height: 50px; background: #f9c; border: 1px dashed #999; margin: 15px; padding: 0px 10px; float: left;}</style></head><body> <div class="father"> <div class="box01">box01</div> <div class="box02">box02</div> <div class="box03">box03</div> </div></body></html>","categories":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/categories/Web/"}],"tags":[{"name":"CSS","slug":"CSS","permalink":"https://blog.justforlxz.com/tags/CSS/"}]},{"title":"JavaScript任务执行笔记","slug":"JavaScript任务执行笔记","date":"2021-08-07T02:37:18.000Z","updated":"2024-04-15T05:09:54.915Z","comments":true,"path":"2021/08/07/JavaScript任务执行笔记/","permalink":"https://blog.justforlxz.com/2021/08/07/JavaScript%E4%BB%BB%E5%8A%A1%E6%89%A7%E8%A1%8C%E7%AC%94%E8%AE%B0/","excerpt":"","text":"浏览器中的 JavaScript 的执行流程和 Node.js 中的执行流程都是基于 事件循环 的 事件循环事件循环 的概念非常简单,事件就是一个动作,界面点击会触发一个动作,生成点击事件,get 请求会触发一个动作,也会产生事件。 事件循环就是提供一个先入先出的队列,事件产生后加入到事件队列中,当队列头的事件被处理完以后,弹出头部的事件,执行下一个事件,不断循环。 在上述模型中,可以看出事件循环是在单一线程中执行的,那么如果此时有 IO 任务,线程岂不是会阻塞,在界面上的体现就是卡死、无法响应。 亦或者有些代码执行了大量计算,比方说在前端暴力破解密码之类的鬼操作,这就会导致后续代码一直在等待,页面处于假死状态,因为前边的代码并没有执行完。 所以如果全部代码都是同步执行的,这会引发很严重的问题,比方说我们要从远端获取一些数据,难道要一直循环代码去判断是否拿到了返回结果么? 于是有了异步事件的概念,注册一个回调函数,比如说发一个网络请求,我们告诉主程序等到接收到数据后通知我,然后我们就可以去做其他的事情了。 然后在异步完成后,会通知到我们,但是此时可能程序正在做其他的事情,所以即使异步完成了也需要在一旁等待,等到程序空闲下来才有时间去看哪些异步已经完成了,可以去执行。 宏任务和微任务为什么要有宏任务和微任务的区别? 这里举一个经常看到的例子,假如 A 去银行办理业务,排了半天队伍,终于到 A了,在 A 的业务办理的过程中,A 想要除了存钱以外,还想要修改银行卡密码,可是总不能让 A 重新排队吧,这样太浪费时间了,所以 A 可以在当前业务办理完成后,立即执行修改密码的操作,而不用重新到队伍里排队。 A 办理业务的过程就是宏任务,而在办理业务中产生的新的业务,就是微任务。 在 JavaScript 中,宏任务是指普通的任务,例如渲染、请求、脚本、延时、io等。而微任务是指 promise 回调、proxy、监听DOM、nextTick等。 执行流程是宏任务队列在执行一个任务时,任务可能会产生新的任务,新的任务会根据类型来决定是放置在宏任务队列,还是开辟一个新的任务队列,在当前任务执行完毕后执行这个队列。 这是一个执行过程: <script>setTimeout(function () { console.log('setTimeout') }, 0) new Promise(function (resolve) { console.log('promise1') for( let i = 0; i < 1000; i++ ) { i === 999 && resolve() } console.log('promise2') }).then(function () { console.log('promise3') }) console.log('script')</script> 输出结果: promise1 → promise2 → script → promise3 → setTimeout async / awaitasync / await 是 ES7 引入的重大改进,它本质上是语法糖,可以以同步的方式写异步代码,不会产生回调地狱。 async :异步执行和返回 Promise await :返回 Promise 对象 在没有 Promise 之前,我们写的异步代码必须用 setTimeout 来执行一个异步函数,很容易就会产生回调地狱,setTimeout 里嵌套 setTimeout。当 ES6 引入了 Promise,我们终于有了专门执行异步代码的结构,但是 Promise 仍然会产生回调地狱,所以在 ES7,引入了 async / await 语法糖,可以很方便的写出同步方式的异步代码。 看代码 async function do2() { return 2;}async function do1() { console.log(1); let a = await do2(); console.log(a); console.log(3);}console.log(4);do1();console.log(5); 输出结果: 4 1 5 2 3 在代码中可以看到,函数增加一个 async 关键字,async 的意思是该函数会以异步的方式执行,async 函数是 AsyncFunction 构造函数的实例,并且其中允许使用 await 关键字。async 和 await 关键字让我们可以用一种更简洁的方式写出基于 Promise 的异步行为,而无需刻意地链式调用 Promise async 函数一定会返回一个 Promise 对象。如果一个 async 函数的返回值看起来不是 Promise,那么它将会被隐式地包装在一个 Promise 中。","categories":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/categories/Web/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.justforlxz.com/tags/JavaScript/"}]},{"title":"Wayland 协议(中文)—— 第三章. Wayland的架构","slug":"wayland-architecture","date":"2021-07-24T04:08:20.000Z","updated":"2024-04-15T05:09:55.092Z","comments":true,"path":"2021/07/24/wayland-architecture/","permalink":"https://blog.justforlxz.com/2021/07/24/wayland-architecture/","excerpt":"","text":"第三章. Wayland 体系结构X 和 Wayland 的体系结构对比了解 Wayland 架构以及它与 X 有何不同的好方法是关注从输入设备到它影响的更改出现在屏幕上的事件。 这是 X 目前的情况: 图 3.1 X 架构图 内核从输入设备获取事件,并通过 evdev 输入驱动程序将其发送到 X。内核通过驱动设备并将不同的设备特定事件协议转换为 linux evdev 标准输入事件,在这里完成所有艰苦的工作。 X 服务器确定事件影响哪个窗口,并将其发送给已选择用于该窗口上有关事件的客户端。X 服务器实际上不知道如何正确地做到这一点,因为屏幕上的窗口位置由合成器控制,并且可能会以 X 服务器无法理解的多种方式进行转换(缩小、旋转、摆动等)。 客户端查看事件并决定该怎么做。通常,UI 必须根据事件进行更改,也许点击了复选框或显示的按钮。因此,客户端将渲染请求发送回 X 服务器。 当 X 服务器收到渲染请求时,它会将其发送给驱动程序,以便对硬件进行编程以进行渲染。X 服务器还计算渲染的边界区域,并将其作为损坏事件发送给合成器。 损坏事件告诉合成器窗口中发生了变化,它必须重新合成窗口可见的那部分屏幕。合成器基于它的场景和X窗口的内容负责渲染整个屏幕的内容。然而,它必须通过X服务器来显示它。 X 服务器接收来自合成器的渲染请求,然后将合成器的后缓冲区复制到前缓冲区或进行页面翻转。在一般情况下,X 服务器必须进行此步骤,以便它可以解释重叠的窗口,这可能需要剪切并确定是否可以翻页。然而,对于一个合成器,它总是全屏,这是另一个不必要的上下文切换。 如上所述,这种方法存在一些问题。X 服务器没有信息来决定哪个窗口应该接收事件,也不能将屏幕坐标转换为窗口本地坐标。尽管 X 已将屏幕最终绘画的责任交给了合成器,但 X 仍控制着前缓冲和模式化。用于处理的 X 服务器的大部分复杂性现在可在内核或自包含库中提供(KMS、evdev、mesa、字体配置、自由型、cairo、Qt 等)。一般来说,X 服务器现在只是一个中间人,在应用程序和合成器之间引入额外的步骤,在合成器和硬件之间引入额外的步骤。 在 Wayland ,合成器是显示服务器。我们将 KMS 和 evdev 的控制权移交给合成器。Wayland 协议允许合成器将输入事件直接发送给客户端,并允许客户端将损坏事件直接发送给合成器: 图 3.2 Wayland 架构图 内核获取事件并将其发送给合成器。这与 X 案例类似,这太棒了,因为我们可以重复使用内核中的所有输入驱动程序。 合成器查看其场景图,以确定哪个窗口应该接收该事件。场景图对应于屏幕上的内容,合成器了解它可能应用于场景图中元素的转换。因此,合成器可以通过应用反向转换来选择正确的窗口并将屏幕坐标转换为窗口本地坐标。可应用于窗口的转换类型仅限于合成器可以执行的转换类型,只要它可以反向转换计算输入的事件。 与 X 案例一样,当客户端收到事件时,它会根据响应更新 UI。但在 Wayland 案例中,渲染发生在客户端中,客户只需向撰写商发送请求,以指示已更新的区域。 合成器从客户端收集损坏请求,然后重新组合屏幕。然后,合成器可以直接发出一个 ioctl 来安排与 KMS 的翻页。 Wayland 渲染我在上面概述中遗漏的一个细节是客户在 Wayland 下的实际渲染方式。通过从图片中删除 X 服务器,我们还删除了 X 客户端通常呈现的机制。但还有另一种机制,我们已经使用与 DRI2 下 X:直接渲染。通过直接渲染,客户端和服务器共享视频内存缓冲区。客户端链接到渲染库(如 OpenGL),该库知道如何对硬件进行编程并直接渲染到缓冲区。合成器反过来可以取缓冲器,并在它复合桌面时将其用作纹理。初始设置后,客户端只需要告诉合成器使用哪个缓冲器,以及它何时何地向其中呈现了新内容。 这给应用程序留下了两种更新窗口内容的方法: 将新内容渲染为新的缓冲区,并告诉合成器使用该内容而不是旧缓冲区。应用程序可以在每次需要更新窗口内容时分配一个新的缓冲区,或者它可以在它们之间保持两个(或多个)缓冲区并循环。缓冲区管理完全处于应用控制之下。 将新内容渲染到缓冲区中,该缓冲区以前曾告诉合成器使用。虽然可以直接渲染到与合成器共享的缓冲区中,但这可能会与合成器竞争。可能发生的情况是,重新粉刷窗口内容可能会被重新粉刷桌面的合成器中断。如果应用程序在清除窗口后被中断,但在渲染内容之前,合成器将从空白缓冲区进行纹理。结果是应用窗口将在空白窗口或半渲染内容之间闪烁。避免这种情况的传统方法是将新内容渲染为后缓冲区,然后从中复制到合成器表面。后缓冲区可以即时分配,并且足够大,可以容纳新内容,或者应用程序可以保持缓冲区。同样,这处于应用控制之下。 在这两种情况下,应用程序必须告诉合成器表面的哪个区域具有新内容。当应用程序直接呈现到共享缓冲区时,需要注意合成器是否有新内容。但是,在交换缓冲时,合成器不会假设任何更改,并且需要应用程序的请求才能重新粉刷桌面。即使应用程序将新的缓冲区传递给合成器,缓冲区的一小部分也可能不同,如闪烁的光标或微调器。、 为Wayland启用硬件通常,硬件启用包括模式安装/显示和 EGL/GLES2。除此之外,Wayland 还需要一种方法来在流程之间高效共享缓冲区。这有两个方面,客户端和服务器端。 在客户端,我们定义了一个Wayland EGL 平台。在 EGL 模型中,该模型包括原生类型(EGLNative 显示类型、EGLNATIVE 窗口类型和 EGLNativePixmapType)以及创建这些类型的方法。换句话说,它是将 EGL 堆栈及其缓冲共享机制与通用 Wayland API 绑定起来的胶水代码。EGL 堆栈预计将提供 Wayland EGL 平台的实施。完整的 API 位于 wayland-egl.h 头文件中。mesa EGL 堆栈中的开源实现处于 wayland-egl.c 和 platform_wayland.c 状态。 在引擎盖下,EGL 堆栈预计将定义供应商特定的协议扩展,允许客户端 EGL 堆栈与合成器通信缓冲详细信息,以便共享缓冲区。Wayland-egl.h API 的要点是抽象掉它,让客户端为 Wayland 表面创建一个 EGL 表面并开始渲染。开源堆栈使用 drm Wayland 扩展,允许客户端发现 drm 设备用于使用和验证,然后与合成器共享 drm (GEM) 缓冲区。 Wayland 的服务器端是垂直的合成器和核心 UX,通常将任务切换器、应用启动器、锁屏界面集成在一个单一应用程序中。服务器运行在调制 API(内核模式,OpenWF 显示器或类似)的顶部,并使用 EGL/GLES2 合成器和硬件叠加(如果可用)的组合组合将最终的 UI 进行复合。启用模式设置、EGL/GLES2 和叠加应该是标准硬件带来的一部分。Wayland 启用的额外要求是 EGL_WL_bind_wayland_display 扩展,允许合成器从通用 Wayland 共享缓冲区创建 EGLImage。它类似于从 X 像素图创建 EGLIM 的 EGL_KHR_image_pixmap 扩展。 扩展具有设置步骤,您必须将 EGL 显示器绑定到 Wayland 显示屏。然后,当合成器从客户端接收通用 Wayland 缓冲器(通常当客户致电 eglSwapBuffers 时),它将能够通过结构 wl_buffer 指头将图像 KHR 作为 EGLClientBuffer 参数,并以 EGL_WAYLAND_BUFFER_WL 为目标。这将创建一个 EGLImage,然后由合成器用作纹理或传递到调制解码以用作覆盖平面。同样,这由供应商特定的协议扩展实现,服务器端将接收有关共享缓冲区的驱动程序特定详细信息,并在用户调用 eglCreateImageKHR 时将其转换为 EGL 图像。","categories":[{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/categories/Wayland/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/tags/Wayland/"}]},{"title":"Wayland 协议(中文)—— 第二章. 合成器的类型","slug":"wayland-types","date":"2021-07-24T04:03:21.000Z","updated":"2024-04-15T05:09:55.093Z","comments":true,"path":"2021/07/24/wayland-types/","permalink":"https://blog.justforlxz.com/2021/07/24/wayland-types/","excerpt":"","text":"第二章. 合成的类型合成器有不同的类型,取决于它们在操作系统的整体架构中扮演的角色。例如,一个系统合成器可以用于引导系统、处理多个用户切换、一个可能的控制台终端模拟器等等。一个不同的合成器、会话合成器将提供实际的桌面环境。有许多方法可以让不同的合成器共存。 在本节中,我们将介绍三种依赖于 libwayland-server 的 Wayland 合成器 系统合成器系统合成器可以从早期启动一直运行到关机。它可以有效地代替内核 vt 系统,并且可以与系统图形引导设置和多座位支持相结合。 一个系统合成器可以承载不同类型的会话合成器,并且让我们在多个会话(快速用户切换,或者安全/私人桌面切换)之间切换。 系统合成器的 linux 实现通常使用 libudev、egl、kms、evdev 和 cairo。对于全屏客户端,系统合成器可以重新编程视频扫描地址直接从客户端提供的缓冲区读取。 会话合成器一个会话合成器负责单一的用户会话。如果存在系统合成器,会话合成器将嵌套在系统合成器下运行。嵌套是可行的,因为协议是异步的;当涉及到嵌套时,往返的开销会很大。如果没有系统合成器,会话合成器可以直接在硬件上运行。 X 应用程序可以通过按需激活的无 Root X 服务器继续在会话合成器下工作。 会话合成器的实例包括: gnome-shell moblin kwin kmscon rdp session Weston 与 X11 或 Wayland 后端是嵌套在另一个会话合成。 在 Wayland 下的全屏 X 会话 嵌套合成器X11 允许客户端从其他客户端嵌入窗口,或允许客户端将另一个客户端呈现的 pixmap 内容复制到其窗口中。这通常用于面板、浏览器插件或类似内容的小程序。wayland 不允许直接这么做,但客户端可以在协议外传达 GEM 缓冲区的名称,例如,使用 D-Bus,或者在面板启动小程序时传递命令行参数。另一种选择是使用嵌套的 Wayland 实例。为此,Wayland 服务器必须是主机应用程序链接到的库。然后主机应用程序把 Wayland 服务器插座名称传递到嵌入式应用程序,并且需要实现 Wayland 合成器接口。主机应用程序将客户端的材质组合为其窗口的一部分,即在网页或者面板中。嵌套 Wayland 服务器的好处是,它提供了嵌入式客户端需要的请求,以便将缓冲区更新通知主机,并提供了从主机应用程序转发输入事件的机制。 这种设置的一个例子是 Firefox 嵌入 flash 播放器作为一种特殊用途的合成器。","categories":[{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/categories/Wayland/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/tags/Wayland/"}]},{"title":"Wayland 协议(中文)—— 第一章. 介绍","slug":"wayland-introduction","date":"2021-07-24T03:54:49.000Z","updated":"2024-04-15T05:09:55.092Z","comments":true,"path":"2021/07/24/wayland-introduction/","permalink":"https://blog.justforlxz.com/2021/07/24/wayland-introduction/","excerpt":"","text":"第一章. 介绍动机大多数基于 Linux 和 Unix 的系统都依赖于 X 窗口系统(或者简单的 X)作为构建位图图形界面的低级协议。在这些系统上,X 系统已经发展到包含客户端库、帮助库或主机操作系统内核中可能属于的功能。PCI 资源管理、显示配置管理、直接渲染和内存管理等内容的支持都已经集成到 X 系统中,对独立应用程序的支持有限、其他项目(如 Linux fb 或者 DirectFB 项目)的重复已经组合多个选组的系统(例如 fb 驱动程序和 X 驱动程序之间的 Radeon 内存地图处理或 VT 切换)的复杂性水平很高。 此外,X 系统已经逐渐融入了现代功能,如屏幕外渲染和场景构图,但受限于 X 架构的限制。例如组合的 X 实现上增加了额外的上下文交换,使输入重定向等内容变得困难。 上图说明了 X 服务器和 Compositor 在操作中的核心作用,以及将内容显示到屏幕上所需的步骤。 随着时间的推移,X 开发人员逐渐理解了这种方法的缺点,并努力进行拆分。在过去的几年中,许多功能已经从 X 服务器转移到了客户端库或内核驱动程序中。第一个被移除的组件是字体渲染,freetype 和 fontconfig 提供了核心 X 字体的替代方案。作为客户端库中的图形驱动程序的直接渲染 OpenGL 经过了一些迭代,最终变成了 DRI2,它从客户端代码中抽象出了大部分的直接渲染缓冲区管理。后来,cairo 提供了一个独立于 X 的现代 2D 渲染库,合成管理器接管了桌面渲染的控制权,像 GTK+这样的工具包和 Qt 不再使用 X api 进行渲染。最近,内存和显示管理已经转移到 Linux 内核,进一步缩小了 X 及其驱动程序堆栈的范围。最终的结果是一个高度模块化的图形堆栈。 作为显示服务器的合成管理器Wayland 是一个新的显示服务器和合成协议,而 Weston 是该协议的实现,它构建在上述所有组件之上。我们试图从 X 服务器中提取出现代 Linux 桌面仍在使用的功能。这并不是全部。应用程序可以使用硬件加速库(如 libGL)或高质量的软件实现(如 Cairo 中的那些),分配自己的屏幕外缓冲区并直接呈现窗口内容。最后,我们需要的是一种显示结果窗口表面的方法,以及在多个客户端之间接收和仲裁输入的方法。这就是 Wayland 所提供的,通过将生态系统中已经存在的组件以略微不同的方式拼凑在一起。 X 将永远是相关的,就像 Fortran 编译器和 VRML 浏览器一样,但现在我们应该考虑将它从关键路径中移除,并将其作为遗留应用程序的可选组件提供。 总的来说,Wayland 的理念是为客户提供一种管理窗口及其内容显示方式的方法。把渲染留给客户端,系统范围内的内存管理接口用于在客户端和合成管理器之间传递缓冲区句柄。 上图说明了 Wayland 客户端如何与 Wayland 服务器交互。请注意,窗口管理和组合完全在服务器中处理,这大大降低了复杂性,同时通过减少上下文切换略微提高了性能。生成的系统比类似的 X 系统更容易构建和扩展,因为通常只需在一个地方进行更改。 或者在协议扩展的情况下,还需要更新窗口管理和/或组合处理的 XICS 中的两个(而不是 3 或 4 的情况)。","categories":[{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/categories/Wayland/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/tags/Wayland/"}]},{"title":"Wayland 协议(中文)—— 序言","slug":"wayland-preface","date":"2021-07-24T03:28:10.000Z","updated":"2024-04-15T05:09:55.093Z","comments":true,"path":"2021/07/24/wayland-preface/","permalink":"https://blog.justforlxz.com/2021/07/24/wayland-preface/","excerpt":"","text":"Kristian Høgsberg Intel Corporation krh@bitplanet.net 版权所有 © 2012 Kristian Høgsberg,Intel 公司。 特此免费授予任何获得本软件及相关文档文件(“软件”)副本的人,以不受限制地经营本软件,包括但不限于使用、复制、修改、合并、出版、分发、再许可的权利,和/或销售软件的副本,并允许软件提供的人这样做,但根据以下条件: 上述版权声明和本许可声明(包括下一段)应包含在软件的所有副本或实质性部分中。 本软件是“按现状”提供的,没有任何明示或暗示的保证,包括但不限于适销性、适用于特定目的和不侵权的保证。在任何情况下,作者或版权持有人都不对任何索赔、损害赔偿或其他责任负责,无论是在合同、侵权行为或其他行为中,由软件或使用或其他交易引起的,或与软件有关的,或与软件有关的。 摘要 Wayland 是一个用于合成程序与客户端通信的协议,也是该协议的C库实现。合成器可以是运行在 Linux 内核 modesetting 和 evdev 输入设备上的独立显示服务器,一个 X 应用程序或 Wayland 客户端本身。客户机可以是传统应用程序、X 服务器(无 Root 窗口或全屏)或其他显示服务器。 前言 本文描述了 Wayland 的体系、Wayland 的操作模型和 Wayland 的 API,另外,Wayland 协议规范再附录中提供。本文档主要针对 Wayland 开发者和那些想要用它编程的人;它不包括应用程序开发。 本文档有许多贡献者,由于这只是第一版,预计会发现许多错误,我们非常感谢修正。 你们的Wayland 开源社区2012 年 11 月","categories":[{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/categories/Wayland/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/tags/Wayland/"}]},{"title":"leetcode 11. 盛最多水的容器","slug":"container-with-most-water","date":"2021-07-06T09:40:18.000Z","updated":"2024-04-15T05:09:54.958Z","comments":true,"path":"2021/07/06/container-with-most-water/","permalink":"https://blog.justforlxz.com/2021/07/06/container-with-most-water/","excerpt":"","text":"盛最多水的容器给你 n 个非负整数 a1,a2,...,an,每个数代表坐标中的一个点 (i, ai) 。在坐标内画 n 条垂直线,垂直线 i 的两个端点分别为 (i, ai) 和 (i, 0) 。找出其中的两条线,使得它们与 x 轴共同构成的容器可以容纳最多的水。 说明:你不能倾斜容器。 示例 1: 输入:[1,8,6,2,5,4,8,3,7]输出:49解释:图中垂直线代表输入数组 [1,8,6,2,5,4,8,3,7]。在此情况下,容器能够容纳水(表示为蓝色部分)的最大值为 49。 示例 2: 输入:height = [1,1]输出:1 示例 3: 输入:height = [4,3,2,1,4]输出:16 示例 4: 输入:height = [1,2,1]输出:2 提示: n = height.length 2 <= n <= 3 * 104 0 <= height[i] <= 3 * 104 算法1解题思路: 使用双指针进行解题,本题的核心点在于,容纳的水量为 两个指针指向的数字中较小值 ∗ 指针之间的距离 所以,使用双指针标记区间范围,缓存面积的值,每次计算都求出当前区间范围的面积,并求出缓存的面积值和当前的面积值的最大值,更新缓存。之后再从最小值开始移动指针,求下一次的面积。 int maxArea(const std::vector<int> &height) { size_t lp = 0; size_t rp = height.size() - 1; size_t ans = 0; while (lp < rp) { size_t area = std::min(height[lp], height[rp]) * (rp - lp); ans = std::max(ans, area); if (height[lp] < height[rp]) { ++lp; } else { --rp; } } return ans;}","categories":[{"name":"leetcode算法","slug":"leetcode算法","permalink":"https://blog.justforlxz.com/categories/leetcode%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"数组","slug":"数组","permalink":"https://blog.justforlxz.com/tags/%E6%95%B0%E7%BB%84/"},{"name":"算法","slug":"算法","permalink":"https://blog.justforlxz.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"简单","slug":"简单","permalink":"https://blog.justforlxz.com/tags/%E7%AE%80%E5%8D%95/"}]},{"title":"leetcode 1. 两数之和","slug":"two-sum","date":"2021-07-06T08:50:11.000Z","updated":"2024-04-15T05:09:55.039Z","comments":true,"path":"2021/07/06/two-sum/","permalink":"https://blog.justforlxz.com/2021/07/06/two-sum/","excerpt":"","text":"1. 两数之和给定一个整数数组 nums 和一个整数目标值 target,请你在该数组中找出 和为目标值 target 的那 两个 整数,并返回它们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素在答案里不能重复出现。 你可以按任意顺序返回答案。 示例 1: 输入:nums = [2,7,11,15], target = 9输出:[0,1]解释:因为 nums[0] + nums[1] == 9 ,返回 [0, 1] 。 示例 2: 输入:nums = [3,2,4], target = 6输出:[1,2] 示例 3: 输入:nums = [3,3], target = 6输出:[0,1] 提示: 2 <= nums.length <= 104 -109 <= nums[i] <= 109 -109 <= target <= 109 只会存在一个有效答案 进阶: 你可以想出一个时间复杂度小于 O(n2) 的算法吗? 算法 1解题思路: 这道题应该是大家的leetcode入门题了,做法非常的简单,核心在于使用 unordered map 保存 target 和 遍历值的差,没有在 map 中找到差就将结果保存到 map 中。 std::vector<int> twoSum(std::vector<int>& nums, int target) { std::unordered_map<int, int> hashtable; for (int i = 0; i < nums.size(); ++i) { auto it = hashtable.find(target - nums[i]); if (it != hashtable.end()) { return {it->second, i}; } hashtable[nums[i]] = i; } return {};}","categories":[{"name":"leetcode算法","slug":"leetcode算法","permalink":"https://blog.justforlxz.com/categories/leetcode%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"数组","slug":"数组","permalink":"https://blog.justforlxz.com/tags/%E6%95%B0%E7%BB%84/"},{"name":"算法","slug":"算法","permalink":"https://blog.justforlxz.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"简单","slug":"简单","permalink":"https://blog.justforlxz.com/tags/%E7%AE%80%E5%8D%95/"}]},{"title":"leetcode 704. 二分查找","slug":"binary-search","date":"2021-07-06T08:35:19.000Z","updated":"2024-04-15T05:09:54.955Z","comments":true,"path":"2021/07/06/binary-search/","permalink":"https://blog.justforlxz.com/2021/07/06/binary-search/","excerpt":"","text":"704. 二分查找给定一个 n 个元素有序的(升序)整型数组 nums 和一个目标值 target ,写一个函数搜索 nums 中的 target,如果目标值存在返回下标,否则返回 -1。 示例 1: 输入: nums = [-1,0,3,5,9,12], target = 9输出: 4解释: 9 出现在 nums 中并且下标为 4 示例 2: 输入: nums = [-1,0,3,5,9,12], target = 2输出: -1解释: 2 不存在 nums 中因此返回 -1 提示: 你可以假设 nums 中的所有元素是不重复的。n 将在 [1, 10000]之间。nums 的每个元素都将在 [-9999, 9999]之间。 解题思路使用双指针实现二分查找。 初始化 left 为0, right 为 nums的长度减一,在每次遍历的时候,计算 mid 为中间值,计算方法如下: mid = left + (right - left) / 2; 当 nums[mid] == target 时,即查找到目标值,返回 mid。当 nums[mid] < target 时,调整 right 为 mid - 1。当 nums[mid] > target 时,调整 left 为 mid + 1。 循环条件为 left <= right,当超过中间值仍然没有查找到目标值,left 会超过 right 的位置,从而跳出循环,返回 -1。 详细实现: int search(const std::vector<int>& nums, int target) { if (nums.size() < 2) { return nums[0] == target ? 0 : -1; } int mid = 0; int left = 0; int right = nums.size() -1; while (left <= right) { mid = left + (right - left) / 2; if (nums[mid] == target) { return mid; } if (target < nums[mid]) { right = mid - 1; } else { left = mid + 1; } } return -1;}","categories":[{"name":"leetcode算法","slug":"leetcode算法","permalink":"https://blog.justforlxz.com/categories/leetcode%E7%AE%97%E6%B3%95/"}],"tags":[{"name":"数组","slug":"数组","permalink":"https://blog.justforlxz.com/tags/%E6%95%B0%E7%BB%84/"},{"name":"算法","slug":"算法","permalink":"https://blog.justforlxz.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"简单","slug":"简单","permalink":"https://blog.justforlxz.com/tags/%E7%AE%80%E5%8D%95/"}]},{"title":"如何给Arch打一个包","slug":"package-manager-for-arch","date":"2021-03-04T03:23:42.000Z","updated":"2024-04-15T05:09:55.027Z","comments":true,"path":"2021/03/04/package-manager-for-arch/","permalink":"https://blog.justforlxz.com/2021/03/04/package-manager-for-arch/","excerpt":"","text":"Arch 使用的是 pacman 包管理器,包格式是 tar.zst。Arch 提供了一些工具用于创建 tar.zst 包,首先需要安装 base-devel 包和 devtools 包。 pacman -S base-devel devtools Arch 的打包流程是这样的,要先写一个 PKGBUILD 文件,这个文件描述了构建一个包所需的全部信息,如从哪里下载源码,依赖有哪些,构建的版本是多少,如何进行构建等。 PKGBUILD一个基本的 PKGBUILD 格式如下: # Maintainer: justforlxz <[email protected]>pkgname=dtkcore-gitpkgver=5.4.0.r1.ge7e7a99pkgrel=1pkgdesc='DTK core modules'arch=('x86_64')url="https://github.com/linuxdeepin/dtkcore"license=('LGPL3')depends=('dconf' 'deepin-desktop-base-git' 'python' 'gsettings-qt' 'lshw')makedepends=('git' 'qt5-tools' 'gtest')conflicts=('dtkcore')provides=('dtkcore')groups=('deepin-git')source=("$pkgname::git://github.com/linuxdeepin/dtkcore.git")sha512sums=('SKIP')pkgver() { cd $pkgname git describe --long --tags | sed 's/\\([^-]*-g\\)/r\\1/;s/-/./g'}build() { cd $pkgname qmake-qt5 PREFIX=/usr DTK_VERSION=$pkgver LIB_INSTALL_DIR=/usr/lib make}package() { cd $pkgname make INSTALL_ROOT="$pkgdir" install} 以 dtkcore 为例,整个配置文件十分清晰明了,一看就知道构建工具是如何一步步制作出一个包的。 将配置文件保存到目录里,以 PKGBUILD 命名,然后执行 makepkg。 makepkg 是用于执行 PKGBUILD 文件的工具,它根据文件中的描述,进行包的构建。 如何提交一个包到 aur当你希望自己打的包可以被别人使用的时候,一般都会请求上传到官方仓库,但是进入官方仓库的流程异常繁琐,而且对贡献者的要求会比较高。Arch 提供了一个用户仓库,供用户自由分享配置文件,只需要下载配置文件,在本机打包就可以了。 根据 aur 的 贡献指南,我们可以很轻松的上传自己的配置文件,并借用yay等aur工具自动下载和构建软件包。 用户可以通过 Arch User Repository 分享 PKGBUILD。AUR 中不包含任何二进制包,仅包含用户上传的 PKGBUILD,供其他用户下载使用。所有软件包都是非官方的,使用风险自担。 提交aur之前,需要先了解一下 aur 的一些要求,Rules of submission 提供了一些规则,只要我们按照规则来,就可以上传和维护我们自己的包。 首先需要去 aur官网 注册一个账号,并上传自己的 ssh key 我们通过克隆一个新的仓库来作为上传的方式。 git clone ssh://[email protected]/<pkgname>.git 以dtkcore为例,克隆的时候把pkgname改成dtkcore。 git clone ssh://[email protected]/dtkcore.git 如果是第一次创建,会提示你克隆的一个空仓库。 把 PKGBUILD 文件复制到克隆的目录,然后执行 makepkg --printsrcinfo > .SRCINFO 创建一个软件包信息,将 .SRCINFO 和 PKGBUILD 文件都添加到 git 中,然后提交 commit 信息,推送到服务器。 注意: 如果您忘记在首次提交中包含 .SRCINFO,您可以使用 rebasing with –root 或是 filtering the tree 使得 AUR 接受您的第一次推送 提示: 为了保持工作目录和提交尽可能的干净,可以创建 gitignore 文件来排除所有文件,然后再按需添加文件。 参考资料: https://wiki.archlinux.org/index.php/PKGBUILD","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"timemachine","slug":"timemachine","date":"2021-02-27T03:35:29.000Z","updated":"2024-04-15T05:09:55.033Z","comments":true,"path":"2021/02/27/timemachine/","permalink":"https://blog.justforlxz.com/2021/02/27/timemachine/","excerpt":"","text":"接着上篇的 samba,我给家里配置了 samba 服务器,为家里提供共享存储服务。 我在我笔记本上安装了黑苹果,所以也想顺便试试传说中的 Time Machine 自动备份,说不定还能整一套给 deepin 使用呢。 话不多说开干。 安装 avahiAvahi 是一种免费的零配置网络实现,包括用于多播 DNS / DNS-SD服务发现的系统。它可以帮助我们广播 samba 服务器,这样网络内其他机器就可以查找到 samba 服务器了。 在 arch 上安装 avahi。 pacman -S avahi 我们并不需要配置什么,直接开启服务就行了。 systemctl enable --now avahi-daemon 配置 samba我们还需要稍微配置一下 samba 服务,专门开辟一个用于备份的目录。 修改 /etc/samba/smb.conf,添加一个 timemachine 的条目。 [timemachine] comment = macos time machine backup path = /home/lxz/TimeMachine valid users = lxz browseable = yes writeable = yes writelist = lxz create mask = 0777 # 加载模块以支持AAPL拓展,注意顺序很重要! vfs objects = catia fruit streams_xattr # 支持aapl fruit:aapl = yes # 存储os x的元数据 fruit:metadata = stream # 设置服务器在finder中的图标 fruit:model = MacPro # 支持time machine,非常重要! fruit:time machine = yes # 文件清理的一些配置 fruit:posix_rename = yes fruit:veto_appledouble = no fruit:wipe_intentionally_left_blank_rfork = yes fruit:delete_empty_adfiles = yes 还需要设置 smb 协议的版本和开启苹果的支持,在配置文件中查找或者添加新的,注意,这里的配置文件必须是在上面条目的上方,我推荐是放在 dns proxy 的下方。 min protocol = SMB2ea support = yes 然后我们重启一下 samba 和 avahi的服务。 此时 TimeMachine 和 Finder 都会显示 samba 服务器了。 开启 time machine打开设置,进入时间机器,打开选择一个硬盘。 选择共享的timemachine,点击使用硬盘,然后会弹出来认证的对话框,输入用户名和密码即可。 配置就算完成了,只需要等待自动备份,或者选择下面的在菜单栏中显示时间机器,在菜单栏里选择立即备份。","categories":[],"tags":[]},{"title":"samba","slug":"samba","date":"2021-02-27T02:42:53.000Z","updated":"2024-04-15T05:09:55.028Z","comments":true,"path":"2021/02/27/samba/","permalink":"https://blog.justforlxz.com/2021/02/27/samba/","excerpt":"","text":"samba 是一个linux下开源的SMB/CIFS协议的免费软件,最初 SMB 协议是 Microsoft 开发的网络通讯协议,后来微软又把 SMB 改名为 CIFS(Common Internet File System),即公共 Internet 文件系统,并且加入了许多新的功能,这样一来,使得Samba具有了更强大的功能。 samba目前最大的作用是文件共享,服务器只需要提供账号和密码,就可以由 samba 客户端访问,并挂载到本地系统上。 我在家用了一个 macmini 当做家庭 nas,并且使用 archlinux 当做服务器系统,我在上面部署了 samba 服务,seafile 服务,nextcloud 服务,plex 服务,arai2c 下载机。 除了 samba 服务,其他服务都是运行在 docker 中,所以 samba 服务需要我手动配置。 安装archlinux 安装 samba 服务很简单,只需要使用 pacman 安装 samba 包即可。 pacman -S samba 安装完成以后我们就可以创建用户和配置共享目录了。 配置由于 samba 不提供配置文件,所以我们要从网上下载基本的 配置文件。 创建用户我们需要先在系统创建对应的用户,以我的用户名 lxz 为例。 useradd -m -g users -G wheel lxz 禁用用户登录由于该用户只是为了权限相关的操作,所以我并没有计划使用该账户,如果需要在系统中使用该账户,则不需要进行接下来的禁用操作。 禁用本地登录 usermod --shell /usr/bin/nologin --lock lxz 禁用ssh登录 修改 /etc/ssh/sshd_config ,修改 AllowUsers。 创建 samba 用户我们需要区分的是,尽管 samba 的用户和 Linux 系统本身的用户是同一个,但是密码则是分开存放的,我们需要使用 samba 提供的命令修改用户的密码。 smbpasswd -a lxz 修改密码则不使用 -a 参数: smbpasswd lxz 测试到此,我们已经完成了基本的 samba 配置,配置文件里默认开启了用户的家目录访问,所以我们使用 smb 客户端进行登录的时候,可以直接访问家目录。 开启服务systemctl enable smbsystemctl start smb 使用 smbclient 可以验证本机服务。 smbclient -L localhost -U% [root@LXZ-MacMini ~]# smbclient -L localhost -U% Sharename Type Comment --------- ---- ------- timemachine Disk macos time machine backup IPC$ IPC IPC Service (Samba Server)SMB1 disabled -- no workgroup available 添加其他的共享目录除了用户家目录,我们还可以共享特定的目录。 例如我创建一个 data 目录,允许任何人只读访问。 修改 /etc/samba/smb.conf 文件,在末尾处添加配置。 [Guests] comment = Allow all users to read path = /data public = yes only guest = yes writable = no printable = no 然后重启 samba 服务,使用 smbclient 验证。 [root@LXZ-MacMini ~]# systemctl restart smb[root@LXZ-MacMini ~]# smbclient -L localhost -U% Sharename Type Comment --------- ---- ------- timemachine Disk macos time machine backup Guests Disk Allow all users to read IPC$ IPC IPC Service (Samba Server)SMB1 disabled -- no workgroup available","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"2020 Review","slug":"2020review","date":"2021-02-26T14:51:45.000Z","updated":"2024-04-15T05:09:54.905Z","comments":true,"path":"2021/02/26/2020review/","permalink":"https://blog.justforlxz.com/2021/02/26/2020review/","excerpt":"","text":"2020拖了这么久才更新博客,想必大家都在 2020 年过的挺不好的,年初武汉的疫情打了全国一个措不及防,甚至已经一整年了,全球疫情还是十分紧张。截止到 2021 年 2 月 26 日,全球每日新增仍然有30 多万,而且全球累计死亡已有 250 万人之多。 疫情生命是如此的脆弱。有些时候和朋友们一起吃饭,会说起一月份时大家是如何 逃离 武汉的,在家隔离又多了哪些事情,我们活着是多么的不容易。虽然人类已经可以生产 5 纳米的芯片了,但是就如同我们对这个宇宙的认知一样,几百亿光年外任我们看,可太阳系内有什么我们都不知道,甚至在地球上还有很多我们未知的东西。 计划失误2020 年我的计划几乎没有一件事是完成的,我计划应该一整年掌握 TypeScript 和 web 开发,并开发自己的blog系统,但是方案被我推到重来了三次,我就没耐心继续开发了,只完成了基本的 md 文件编译和服务器渲染就暂停了。计划阅读 《1984》 、 《TensorFlow》 和 《TypeScript实战》 ,然而只有 《TypeScript实战》阅读完了,剩余两本书我则是只看了一丢丢,看来只能放到 2021 年读完了。 她在 2019Review 的文章里,我提到了我遇到了生命中的那个她,而 2020 年,我和她 结婚 啦。 在家隔离的日子里,有她陪伴着我,虽然老家的人都挺敌视从武汉回来的我,但是大门一关我谁也看不到,眼不见心不烦,在家一块搓麻将挺好的,还一起练 (zao) 习 (ta)了厨艺,朋友圈做饭比赛第一名。 学习虽然我读书拉后了,但是我的英语 坚持 了下来,我使用多邻国进行英语和日语的学习,已经坚持快6个月了,需要继续加油努力,争取早日把学校里拉下的英语 补 回来。 工作2020 年最大的收益可能是创建了 deepin-git 的仓库,提供了 dde 所有组件在 arch 上的git版,并且还成功申请到了 archlinuxcn 的贡献者。 文章2020 年一共水了18篇文章。 12-23 ccls11-17 Qt多线程09-17 deepin-wine中文乱码09-08 使用rEFInd来安全启动系统09-06 deepin git version09-03 使用VSCode远程开发DDE08-06 在ArchLinux上开发startdde07-27 use github action to check dde-launcher07-21 使用perf工具分析程序性能06-16 CTest & QTest/GTest06-15 CPP项目的一些坑06-15 使用inquirer提供交互式git commit06-01 vue-router路由复用后页面没有刷新05-31 vue3升级遇到的坑02-01 JavaScript建造者模式01-31 浅谈Javascript构造器模式01-01 2019 Review01-01 使用伪元素创建一个圆点 研究了 vscode 的插件,开发了一个 qmake 的插件原型,可以打开 dtk 等 qt pro 的项目,利用 bear 拦截 make 的编译信息,生成 ccls 需要的数据库,这样就可以为项目提供自动补全和语法检查等功能了。 写了一些关于远程开发的文章,也是利用 vscode 的远程功能,不得不说 vscode 真的可以说是宇宙第一编辑器了,让老牌的 vim 和 emacs 知道了什么叫易用性的力量。 展望2021 年,不能松懈,今年要继续努力学习,努力提升自己。 在 2021 年里,我将会负责 deepin 的社区,由于前两年公司需要快速扩张,导致忽略了社区,现在我们的首要任务就是恢复社区,并让社区良性的自由发展,社区是我们的根本。 挖了很多坑,今年争取都填一下。 其他又搬家了,找了一个一室一厅,开启了二人的小日子,过了年以后把狗子也带来了,现在天天早上起来打狗,晚上回来打狗。 我又给 肝疼3 氪了一个月卡,这次只忘了一天没登录,不然 30 块大洋又要白花了。 祝大家幸福安康,新年快乐!(^▽^)","categories":[],"tags":[]},{"title":"ccls","slug":"ccls","date":"2020-12-23T08:27:39.000Z","updated":"2024-04-15T05:09:54.955Z","comments":true,"path":"2020/12/23/ccls/","permalink":"https://blog.justforlxz.com/2020/12/23/ccls/","excerpt":"","text":"LSP(Language Server Protocol) 语言服务协议,此协议定义了在编辑器或IDE与语言服务器之间使用的协议,该语言服务器提供了例如自动补全,转到定义,查找所有引用等的功能;语言服务器索引格式的目标是支持在开发工具中进行丰富的代码导航或者一个无需本地源码副本的WebUI。 而ccls就是为c/c++开发的一个的lsp服务,可以为vim、emacs和vscode等工具提供自动补全,定义跳转等功能。 根据本文的指导,你可以将vim配置成一个不错的c++开发工具。 准备工作由于公司部分项目并不是CMake开发的,所以vscode无法完成胜任开发工作,所以我开始使用vim。系统我使用的是ArchLinux,我会尽量使用仓库中的包,而不是通过pip等工具安装组件。 安装安装neovim、ccls和bear,bear目前只在archlinuxcn仓库,需要注意。 sudo pacman -S neovim ccls bear python node 下载SpaceVim,由于我使用的是neovim,所以下载配置时选择了neovim. curl -sLf https://spacevim.org/cn/install.sh | bash -s -- --install neovim 创建自己的配置文件目录 mkdir ~/.SpaceVim.d/ 配置首先要先创建基本的SpaceVim配置文件,配置文件参考了https://github.com/Martins3/My-Linux-config。 整个配置都在~/.SpaceVim.d目录下。 .SpaceVim.d├── autoload│ └── myspacevim.vim├── coc-settings.json├── init.toml└── plugin ├── coc.nvim └── defx.nvim2 directories, 5 files 在.SpaceVim.d目录下创建init.toml # 所有的 SpaceVim 选项都列在 [options] 之下[options] # 设置 SpaceVim 主题及背景,默认的主题是 gruvbox,如果你需要使用更 # 多的主题,你可以载入 colorscheme 模块 colorscheme = "gruvbox" # 背景可以取值 "dark" 或 "light" colorscheme_bg = "dark" # 启用/禁用终端真色,在目前大多数终端下都是支持真色的,当然也有 # 一小部分终端不支持真色,如果你的 SpaceVim 颜色看上去比较怪异 # 可以禁用终端真色,将下面的值设为 false enable_guicolors = true # 设置状态栏上分割符号形状,如果字体安装失败,可以将值设为 "nil" 以 # 禁用分割符号,默认为箭头 "arrow" statusline_separator = "nil" statusline_iseparator = "bar" # 设置顶部标签列表序号类型,有以下五种类型,分别是 0 - 4 # 0: 1 ➛ ➊ # 1: 1 ➛ ➀ # 2: 1 ➛ ⓵ # 3: 1 ➛ ¹ # 4: 1 ➛ 1 buffer_index_type = 4 # 显示/隐藏顶部标签栏上的文件类型图标,这一图标需要安装 nerd fonts, # 如果未能成功安装这一字体,可以隐藏图标 enable_tabline_filetype_icon = true # 是否在状态栏上显示当前模式,默认情况下,不显示 Normal/Insert 等 # 字样,只以颜色区分当前模式 enable_statusline_mode = false filemanager = "defx" autocomplete_method = "coc" bootstrap_before = "myspacevim#before" bootstrap_after = "myspacevim#after"# SpaceVim 模块设置,主要包括启用/禁用模块# 计算器 日历 书签等小工具[[layers]] name = 'tools'[[layers]] name = "checkers" enable = false# 版本控制[[layers]] name = "VersionControl" enable-gtm-status = true[[layers]] name = "git" git-plugin = "fugitive"[[layers]] name = "fzf"# coc.nvim 核心配置,不要删除[[custom_plugins]] name = 'neoclide/coc.nvim' merge = 0# 主要用于快速搜索 文件, buffer 和 函数[[custom_plugins]] name = "Yggdroot/LeaderF" build = "./install.sh"# 更加美观的 tagbar[[custom_plugins]] name = 'liuchengxu/vista.vim'[[custom_plugins]] name = 'junegunn/fzf.vim'# 更加方便的调节窗口的大小[[custom_plugins]] name = 'simeji/winresizer'# 基于lsp的高亮插件[[custom_plugins]] name = 'jackguo380/vim-lsp-cxx-highlight'# 从 http://cplusplus.com/ 和 http://cppreference.com/ 获取文档[[custom_plugins]] name = 'skywind3000/vim-cppman'# 利用 git blame 显示当前行的 commit message[[custom_plugins]] name = 'rhysd/git-messenger.vim' lazy = 1 on_cmd = 'GitMessenger'# 以悬浮窗口的形式打开终端[[custom_plugins]] name = 'voldikss/vim-floaterm'# 显示搜索的标号[[custom_plugins]] name = 'google/vim-searchindex.git'[[custom_plugins]] name = 'kenn7/vim-arsync'[[custom_plugins]] name = 'fatih/vim-go'# 禁用 shell 模块,禁用模块时,需要加入 enable = false[[layers]] name = "shell" enable = false# 添加自定义插件[[custom_plugins]] repo = "lilydjwg/colorizer" merged = false[[layers]] name = "format" 创建plugin目录,新建两个文件。coc.nvim " coc.nvim 的配置, 来自于 https://github.com/neoclide/coc.nvim" Use <c-space> for trigger completion.inoremap <silent><expr> <c-space> coc#refresh()set hidden" Some servers have issues with backup files, see #649set nobackupset nowritebackup" 使用 Microsoft Python Language Server 不然 coc.nvim 会警告call coc#config("python.jediEnabled", v:false)call coc#config("smartf.wordJump", v:false)call coc#config("smartf.jumpOnTrigger", v:false)" https://rust-analyzer.github.io/manual.html#rust-analyzer-language-server-binarycall coc#config("rust-analyzer.serverPath", "~/.cargo/bin/rust-analyzer")call coc#config('coc.preferences', { \\ "autoTrigger": "always", \\ "maxCompleteItemCount": 10, \\ "codeLens.enable": 1, \\ "diagnostic.virtualText": 1, \\})" coc.nvim 插件,用于支持 python java 等语言let s:coc_extensions = [ \\ 'coc-python', \\ 'coc-java', \\ 'coc-json', \\ 'coc-css', \\ 'coc-html', \\ 'coc-word', \\ 'coc-cmake', \\ 'coc-dictionary', \\ 'coc-rust-analyzer', \\ 'coc-vimlsp', \\ 'coc-ci', \\ 'coc-snippets', \\ 'coc-vimtex', \\ 'coc-smartf', \\]for extension in s:coc_extensions call coc#add_extension(extension)endforinoremap <silent><expr> <TAB> \\ pumvisible() ? "\\<C-n>" : \\ <SID>check_back_space() ? "\\<TAB>" : \\ coc#refresh()inoremap <expr><S-TAB> pumvisible() ? "\\<C-p>" : "\\<C-h>"function! s:check_back_space() abort let col = col('.') - 1 return !col || getline('.')[col - 1] =~# '\\s'endfunction" 方便在中文中间使用 w 和 b 移动nmap <silent> w <Plug>(coc-ci-w)nmap <silent> b <Plug>(coc-ci-b)" Use <cr> for confirm completion, `<C-g>u` means break undo chain at current position." Coc only does snippet and additional edit on confirm.inoremap <expr> <cr> pumvisible() ? "\\<C-y>" : "\\<C-g>u\\<CR>"" Remap keys for gotosnmap <silent> gd <Plug>(coc-definition)nmap <silent> gy <Plug>(coc-type-definition)nmap <silent> gi <Plug>(coc-implementation)nmap <silent> gr <Plug>(coc-references)" Use K for show documentation in preview windownnoremap <silent> K :call <SID>show_documentation()<CR>function! s:show_documentation() if &filetype == 'vim' execute 'h '.expand('<cword>') else call CocAction('doHover') endifendfunction" Highlight symbol under cursor on CursorHoldset updatetime=300autocmd CursorHold * silent call CocActionAsync('highlight')autocmd CursorHoldI * sil call CocActionAsync('showSignatureHelp')" Remap for rename current wordnmap <leader>rn <Plug>(coc-rename)" 注释掉,一般使用 `Space` `r` `f` 直接格式化整个文件" Remap for format selected region" vmap <leader>f <Plug>(coc-format-selected)" nmap <leader>f <Plug>(coc-format-selected)augroup mygroup autocmd! " Setup formatexpr specified filetype(s). autocmd FileType typescript,json setl formatexpr=CocAction('formatSelected') " Update signature help on jump placeholder autocmd User CocJumpPlaceholder call CocActionAsync('showSignatureHelp')augroup end" Remap for do codeAction of selected region, ex: `<leader>aap` for current paragraphvmap <leader>a <Plug>(coc-codeaction-selected)nmap <leader>a <Plug>(coc-codeaction-selected)" Remap for do codeAction of current linenmap <leader>ac <Plug>(coc-codeaction)" Fix autofix problem of current linenmap <leader>qf <Plug>(coc-fix-current)" Use `:Format` for format current buffer" command! -nargs=0 Format :call CocAction('format')call SpaceVim#custom#SPC('nnoremap', ['r', 'f'], "call CocAction('format')", 'format file with coc.nvim', 1)" Use `:Fold` for fold current buffercommand! -nargs=? Fold :call CocAction('fold', <f-args>)" auto import for go on saveautocmd BufWritePre *.go :call CocAction('runCommand', 'editor.action.organizeImport')" 这个和 SpaceVim 的 statusline/tabline 冲突了" Add diagnostic info for https://github.com/itchyny/lightline.vim" let g:lightline = {" \\ 'colorscheme': 'wombat'," \\ 'active': {" \\ 'left': [ [ 'mode', 'paste' ]," \\ [ 'cocstatus', 'readonly', 'filename', 'modified' ] ]" \\ }," \\ 'component_function': {" \\ 'cocstatus': 'coc#status'" \\ }," \\ }" Using CocList" Show all diagnosticsnnoremap <silent> <leader>d :<C-u>CocList diagnostics<cr>" 下面是 ccls 提供的 LSP Extension" https://github.com/MaskRay/ccls/wiki/coc.nvim" Manage extensions" nnoremap <silent> <space>e :<C-u>CocList extensions<cr>" Show commands" nnoremap <silent> <space>c :<C-u>CocList commands<cr>" Find symbol of current document" nnoremap <silent> <space>o :<C-u>CocList outline<cr>" Search workspace symbols" nnoremap <silent> <space>s :<C-u>CocList -I symbols<cr>" Do default action for next item." nnoremap <silent> <space>j :<C-u>CocNext<CR>" Do default action for previous item." nnoremap <silent> <space>k :<C-u>CocPrev<CR>" Resume latest coc list" nnoremap <silent> <space>p :<C-u>CocListResume<CR>nn <silent> xl :call CocLocations('ccls','$ccls/navigate',{'direction':'D'})<cr>nn <silent> xk :call CocLocations('ccls','$ccls/navigate',{'direction':'L'})<cr>nn <silent> xj :call CocLocations('ccls','$ccls/navigate',{'direction':'R'})<cr>nn <silent> xh :call CocLocations('ccls','$ccls/navigate',{'direction':'U'})<cr>noremap x <Nop>nn <silent> xb :call CocLocations('ccls','$ccls/inheritance')<cr>" bases of up to 3 levelsnn <silent> xb :call CocLocations('ccls','$ccls/inheritance',{'levels':3})<cr>" derivednn <silent> xd :call CocLocations('ccls','$ccls/inheritance',{'derived':v:true})<cr>" derived of up to 3 levelsnn <silent> xD :call CocLocations('ccls','$ccls/inheritance',{'derived':v:true,'levels':3})<cr>" callernn <silent> xc :call CocLocations('ccls','$ccls/call')<cr>" calleenn <silent> xC :call CocLocations('ccls','$ccls/call',{'callee':v:true})<cr>" $ccls/member" member variables / variables in a namespacenn <silent> xm :call CocLocations('ccls','$ccls/member')<cr>" member functions / functions in a namespacenn <silent> xf :call CocLocations('ccls','$ccls/member',{'kind':3})<cr>" nested classes / types in a namespacenn <silent> xs :call CocLocations('ccls','$ccls/member',{'kind':2})<cr>nmap <silent> xt <Plug>(coc-type-definition)<cr>nn <silent> xv :call CocLocations('ccls','$ccls/vars')<cr>nn <silent> xV :call CocLocations('ccls','$ccls/vars',{'kind':1})<cr>nn xx x defx.nvim " defx 将会自动忽略如下的文件call defx#custom#option('_', { \\ 'ignored_files': ".*,*.class,*.out,*.o,*.bc,*.a,compile_commands.json,*.d,*.mod*,*.cmd,.tmp_versions/,modules.order,Module.symvers,Mkfile.old,dkms.conf,*.ko,*.elf,*.img", \\ }) 创建autoload目录,新建myspacevim.vim func! myspacevim#before() abort let g:coc_config_home = '~/.SpaceVim.d/' " 焦点消失的时候自动保存 au FocusLost * :wa au FocusGained,BufEnter * :checktime " 当文件被其他编辑器修改时,自动加载 set autowrite set autoread " 设置按照syntax高亮进行折叠 set foldmethod=syntax set nofoldenable " 重新映射 leader 键 let g:mapleader = ',' " 重新映射 window 键位 let g:spacevim_windows_leader = 'c' call SpaceVim#custom#SPC('nnoremap', ['s', 'f'], 'Vista finder coc', 'search simbols', 1) call SpaceVim#custom#SPC('nnoremap', ['s', 'F'], 'LeaderfFunction!', 'list functions', 1) let g:Lf_ShortcutF = "<leader>s" " 让 leaderf 可以搜索 git 的 submodule,否则是自动忽略的 let g:Lf_RecurseSubmodules = 1 let g:spacevim_snippet_engine = 'ultisnips' let g:table_mode_corner='|' " 调节 window 大小 let g:winresizer_start_key = '<space>wa' " If you cancel and quit window resize mode by `q` (keycode 113) let g:winresizer_keycode_cancel = 113 " 让file tree 显示文件图标,需要 terminal 安装 nerd font let g:spacevim_enable_vimfiler_filetypeicon = 1 " 让 filetree 显示 git 的状态,会变得很卡,所以关掉 let g:spacevim_enable_vimfiler_gitstatus = 1 " 默认 markdown preview 在切换到其他的 buffer 或者 vim " 失去焦点的时候会自动关闭 preview,让 let g:mkdp_auto_close = 0 " 书签选中之后自动关闭 quickfix window let g:bookmark_auto_close = 1 " vim-lsp-cxx-highlight 和这个选项存在冲突 " let g:rainbow_active = 1 " ctrl + ] 查询 cppman " 如果想让该快捷键自动查询 man,将Cppman 替换为 Cppman! autocmd FileType c,cpp noremap <C-]> <Esc>:execute "Cppman " . expand("<cword>")<CR> " 让光标自动进入到popup window 中间 let g:git_messenger_always_into_popup = v:true " 设置映射规则,和 spacevim 保持一致 call SpaceVim#custom#SPC('nnoremap', ['g', 'm'], 'GitMessenger', 'show commit message in popup window', 1) call SpaceVim#custom#SPC('nnoremap', ['g', 'l'], 'FloatermNew lazygit', 'open lazygit in floaterm', 1) " 设置默认的pdf阅览工具 let g:vimtex_view_method = 'zathura' " 关闭所有隐藏设置 let g:tex_conceal = "" " 实现一键运行各种文件,适合非交互式的,少量的代码,比如 leetcode func! QuickRun() exec "w" let ext = expand("%:e") let file = expand("%") if ext ==# "sh" exec "!sh %" elseif ext ==# "cpp" exec "!clang++ % -Wall -O3 -g -std=c++17 -o %<.out && ./%<.out" elseif ext ==# "c" exec "!clang % -Wall -g -std=c11 -o %<.out && ./%<.out" elseif ext ==# "java" let classPath = expand('%:h') let className = expand('%:p:t:r') " echo classPath " echo className exec "!javac %" exec "!java -classpath " . classPath . " " . className elseif ext ==# "go" exec "!go run %" elseif ext ==# "js" exec "!node %" elseif ext ==# "bin" exec "!readelf -h %" elseif ext ==# "py" exec "!python3 %" elseif ext ==# "vim" exec "so %" elseif ext ==# "html" exec "!google-chrome-stable %" elseif ext ==# "rs" call CargoRun() else echo "Check file type !" endif echo 'done' endf " 一键运行 rust 工程,不断向上查找直到遇到 Cargo.toml,然后执行 cargo run func! CargoRun() let cargo_run_path = fnamemodify(resolve(expand('%:p')), ':h') while cargo_run_path != "/" if filereadable(cargo_run_path . "/Cargo.toml") echo cargo_run_path exec "cd " . cargo_run_path exec "!cargo run" exec "cd -" return endif let cargo_run_path = fnamemodify(cargo_run_path, ':h') endwhile echo "Cargo.toml not found !" endfendffunc! myspacevim#after() abort " <F3> 打开文件树 nnoremap <F4> :call QuickRun()<CR> " <F5> floaterm toggle " <F7> 打开历史记录 tnoremap <Esc> <C-\\><C-n> map <Tab> :wincmd w<CR> " press <esc> to cancel. nmap f <Plug>(coc-smartf-forward) nmap F <Plug>(coc-smartf-backward) nmap ; <Plug>(coc-smartf-repeat) nmap , <Plug>(coc-smartf-repeat-opposite) augroup Smartf autocmd User SmartfEnter :hi Conceal ctermfg=220 guifg=pink autocmd User SmartfLeave :hi Conceal ctermfg=239 guifg=#504945 augroup end inoremap <silent> <C-n> :FloatermNew<CR> nnoremap <silent> <C-n> :FloatermNew<CR> tnoremap <silent> <C-n> <C-\\><C-n>:FloatermNew<CR> inoremap <silent> <C-h> :FloatermPrev<CR> nnoremap <silent> <C-h> :FloatermPrev<CR> tnoremap <silent> <C-h> <C-\\><C-n>:FloatermPrev<CR> inoremap <silent> <C-l> :FloatermNext<CR> nnoremap <silent> <C-l> :FloatermNext<CR> tnoremap <silent> <C-l> <C-\\><C-n>:FloatermNext<CR> " 保证在插入模式<F5>可以 toggle floaterm " 来,提出一个自己的第一个 pull request ! inoremap <silent> <F5> :FloatermToggle!<CR> nnoremap <silent> <F5> :FloatermToggle!<CR> tnoremap <silent> <F5> <C-\\><C-n>:FloatermToggle!<CR> " go highlight " https://github.com/neoclide/coc.nvim/issues/472 let g:go_list_type="quickfix" let g:go_fmt_command="goimports" let g:go_highlight_types=1 let g:go_highlight_fields=1 let g:go_highlight_functions=1 let g:go_highlight_function_calls=1 let g:go_fmt_fail_silently=1endf 创建coc的默认配置文件,原本coc的配置文件应该是在~/.SpaceVim下的,但是在autoload的配置文件中,设置了coc_config_home,修改为.SpaceVim.d下,方便控制。 { "languageserver": { "ccls": { "command": "ccls", "filetypes": ["c", "cpp"], "rootPatterns": ["compile_commands.json", ".git/"], "index": { "threads": 8 }, "initializationOptions": { "cache": { "directory": "build/.ccls-cache" }, "highlight": { "lsRanges": true }, "compilationDatabaseDirectory": "build" }, "client": { "snippetSupport": true } } }} 使用启动nvim的时候,就会开始安装所有的插件,等带最终配置完成。 现在进去一个C++的项目里,由于ccls需要知道项目都使用了哪些文件,所以需要我们为它创建配置文件,如果项目是CMake的,可以直接执行命令生成compile_commands.json文件,如果不是CMake的项目,则需要使用bear对Makefile进行编译,bear会拦截编译信息,最终生成compile_commands.json。 CMake: cmake -DCMAKE_EXPORT_COMPILE_COMMANDS=ON Makefile: bear -- make 等文件生成完毕后,使用ln命令将文件软链回项目根目录。 ln -sf build/compile_commands.json compile_commands.json 此时已经配置完成一半了,我们还需要在根目录创建.ccls文件,并写入一下配置: %compile_commands.json 此时ccls就可以读取compile_commands.json的信息,为我们提供自动补全等功能了。 问题如果出现了找不到vimproc动态库在nvim中执行 :VimProcInstall进行重新编译。","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"vim","slug":"vim","permalink":"https://blog.justforlxz.com/tags/vim/"}]},{"title":"Qt多线程","slug":"qt-multi-thread","date":"2020-11-17T07:53:37.000Z","updated":"2024-04-15T05:09:55.027Z","comments":true,"path":"2020/11/17/qt-multi-thread/","permalink":"https://blog.justforlxz.com/2020/11/17/qt-multi-thread/","excerpt":"类型注册 Qt 有三种多线程的方式,分别是继承 QThread、使用 QObject 的 moveToThread 函数和 Qtconcurrent 协程。 在很多文章中,大家都推荐继承 QThread 类,并重写 run 方法,在 run 中使用耗时操作代码。这种方式让我们觉得 QThread 是线程的实体。创建一个 QThread 对象就认为是开辟了一个新的线程。这种讨巧的方法似乎能帮助我们快速入门,但是只要深度了解多线程编程就会发现,这样子做会使代码脱离我们的控制,代码越写越复杂。最典型的问题就是明明将代码放入了新线程,但是仍然在旧线程中运行。","text":"类型注册 Qt 有三种多线程的方式,分别是继承 QThread、使用 QObject 的 moveToThread 函数和 Qtconcurrent 协程。 在很多文章中,大家都推荐继承 QThread 类,并重写 run 方法,在 run 中使用耗时操作代码。这种方式让我们觉得 QThread 是线程的实体。创建一个 QThread 对象就认为是开辟了一个新的线程。这种讨巧的方法似乎能帮助我们快速入门,但是只要深度了解多线程编程就会发现,这样子做会使代码脱离我们的控制,代码越写越复杂。最典型的问题就是明明将代码放入了新线程,但是仍然在旧线程中运行。 我们应该把耗时代码放在哪里? 暂时不考虑多线程的情况,我们一般都会将耗时代码封装到一个类中。在考虑多线程的情况下,难道我们要将代码剥离出来放到某个地方吗?其实不用这么麻烦。在 qt4 时代,我们需要使用继承 QThread 的方法,这样会破坏我们原有的代码结构,并且 run 方法只能运行一段代码,如果我们有成千上万个函数,我们总不能封装如此多的 QThread。 所以在 Qt5 中,Qt 库完善了线程的亲和性以及信号槽机制,我们有了更为优雅的使用线程的方式,即 QObject::moveToThread()。这也是官方推荐的做法。 我们准备两个类来介绍和解释一下工作流程。 controller.hpp #ifndef CONTROLLER_H#define CONTROLLER_H#include <QObject>#include <QDebug>class Controller : public QObject { Q_OBJECTpublic: explicit Controller(QObject *parent = nullptr) : QObject(parent) {}signals: void requestPing();public slots: void pong() { qDebug() << Q_FUNC_INFO << this->thread(); qDebug() << "pong"; }};#endif // CONTROLLER_H handler.hpp #ifndef HANDLEER_H#define HANDLEER_H#include <QObject>#include <QDebug>#include <QThread>class Handler : public QObject { Q_OBJECTpublic: explicit Handler(QObject *parent = nullptr) : QObject(parent) {}signals: void requestPong();public slots: void ping() { qDebug() << Q_FUNC_INFO << this->thread(); emit requestPong(); }};#endif // HANDLEER_H 在 main.cpp 中初始化对象,并连接信号和槽。 #include <QCoreApplication>#include <QThread>#include "controller.h"#include "handleer.h"int main(int argc, char *argv[]) { QCoreApplication a(argc, argv); QThread* handleThread = new QThread; Controller* controller = new Controller; Handler* handler = new Handler; // 移动对象到新的线程 handler->moveToThread(handleThread); handleThread->start(); // 将对象的信号的槽绑定,注意必须在 moveToThread 之后链接。 QObject::connect(controller, &Controller::requestPing, handler, &Handler::ping); QObject::connect(handler, &Handler::requestPong, controller, &Controller::pong); emit controller->requestPing(); return a.exec();} 看一下执行结果: void Handler::ping() QThread(0x14ee080)void Controller::pong() QThread(0x14e9e60)pong 可以看出来两个函数获取到的QThread对象并不是同一个了。使用 movetothread 将一个对象移动到新的线程,并通过信号调用目标函数,从而达到在新线程执行的目的。 使用这种方式,我们可以方便的通过操作QThread对象来控制线程的执行,例如设置线程的优先级别,暂停线程或者恢复线程。并且这种方式比继承QThread可以更加直观的感受到,QThread只是一个线程的管理类,而不是线程实体,如果采用继承的方式,则会认为QThread就是线程实体,从而造成一定的认知混乱。 还有一种多线程的方式,这种方案更加的灵活,不需要我们new新的QThread对象,是一个较高层次的API封装。QtConCurrent可根据计算机的 CPU 核数,自动调整运行的线程数目。 在使用Qtconcurrent之前需要添加对应的Qt模块concurrent。 在使用的时候,我们需要添加一个QFutureWatcher对象,用来控制和执行一个QFuture对象,并且通过finished信号接收QFuture对象的执行结果。 QFutureWatcher<bool>* watcher = new QFutureWatcher<bool>();QObject::connect(watcher, &QFutureWatcher<bool>::finished, watcher, [=] { const bool result = watcher->result(); qDebug() << result; watcher->deleteLater();});QFuture<bool> future = QtConcurrent::run([=]() -> bool { return true;});watcher->setFuture(future); 以上就是一个简单的例子,不难发现,Qt为我们提供了相当不错的解决方案,这种形式比较类似于QDbus对象使用QDbusPendingCallWatcher来异步获取结果的方式,使用起来非常容易上手。在使用多线程的时候,我们需要注意一些事情:互斥与同步同步,类型注册,在线程中开辟线程。 在多线程开发中,我们需要注意的地方就有点多了,最重要的就是线程同步,我们需要使用一些手段,让不同线程中的函数可以正确的访问的数据。 互斥:一个公共资源同一时刻只能被一个进程或线程使用,多个进程或线程不能同时使用公共资源。 同步:两个或两个以上的进程或线程在运行过程中协同步调,按预定的先后次序运行。 解决方法:互斥锁,条件变量,读写锁,自旋锁,信号量(互斥与同步)。 在Qt编程中,我们可以利用Qt的信号与槽机制实现两个对象的通信,无论两个对象是否在同一个线程,但是我们传递参数需要注册给Qt的元对象系统,否则Qt将无法完成数据传递。 在Qt中注册自定义类型有两种方式,一种是qRegisterMetaType()函数和Q_DECLARE_METATYPE(Type)宏。 这两种注册方式有不同的作用。使用qRegisterMetaType()函数可以让自定义类型在Qt的信号槽中传递,而Q_DECLARE_METATYPE(Type)宏则是可以让注册的自定义类型使用QVariant进行包装。 在多线程开发中我们还需要注意,Qt存在半自动内存管理,这个内存管理方式会影响着我们使用多线程开发。我们在创建新的QObject对象时,如果制定了parent,则该对象将与父对象进行线程绑定。如果两个对象在不同的线程中,Qt会警告我们父对象的线程和当前对象的线程不是同一个,他们将无法使用Qt的connect函数进行消息传递。","categories":[],"tags":[{"name":"Qt","slug":"Qt","permalink":"https://blog.justforlxz.com/tags/Qt/"}]},{"title":"deepin-wine中文乱码","slug":"deepin-wine-chinese-problem","date":"2020-09-17T08:32:12.000Z","updated":"2024-04-15T05:09:54.959Z","comments":true,"path":"2020/09/17/deepin-wine-chinese-problem/","permalink":"https://blog.justforlxz.com/2020/09/17/deepin-wine-chinese-problem/","excerpt":"","text":"众所周知,使用wine来运行windows下的一些软件是linux用户的常用操作,deepin为社区贡献了好几款中国用户必备的软件,例如QQ、微信、企业微信,以此来让更多的人无痛的切换到linux来。近年来value也一直在linux上布局,先后推出了steam主机和proton,前者是基于kvm和steam大屏幕模式的系统,而proton则是wine的分支,value提供了几组补丁用来提高性能和与Windows游戏的兼容性。 Arch上有非常方便的aur仓库,有很多用户都自己提交一些软件到aur上面,就有几个维护者将deepin打包的deepin wine和对应的软件包移植到Arch上面来,已经是很多人在Arch上面运行QQ和微信的首选方案。 但是使用的过程中我遇到了以下几个问题: 输入框中文乱码 在DDE桌面使用kwin的情况下最小化会卡死 KDE桌面环境无法使用deepin wine的程序 第二个问题比较特殊,因为dde在deepin和uos下运行的是fork版本的kwin,而Arch上运行的则是原版的kwin,一些操作代码并不具备,所以会出现一些奇葩的状况。 第三个问题则是xsettings的原因,在移植者的仓库有对应的issue讨论 《KDE环境完全无法使用wine-tim》 第一个问题解决起来也比较简单,是因为缺少了宋体文件,从而无法正确的渲染中文字体。而比较奇葩的是,把方块复制再粘贴,就可以正确的渲染了。 解决的方法也很简单,把windows的的字体复制过来就可以了。由于版权的问题,没办法直接提供文件,需要各位自己复制了。 引用资料 https://github.com/wszqkzqk/deepin-wine-ubuntu/issues/136","categories":[{"name":"技术","slug":"技术","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"使用rEFInd来安全启动系统","slug":"use-refind-to-boot-system","date":"2020-09-08T11:54:26.000Z","updated":"2024-04-15T05:09:55.047Z","comments":true,"path":"2020/09/08/use-refind-to-boot-system/","permalink":"https://blog.justforlxz.com/2020/09/08/use-refind-to-boot-system/","excerpt":"今年的七夕,我老婆给我买了一台surface laptop 2代,8G内存 + 256G存储版本,我也成功的用上了田牌的机器。","text":"今年的七夕,我老婆给我买了一台surface laptop 2代,8G内存 + 256G存储版本,我也成功的用上了田牌的机器。 2020/09/17更新: 不知道为啥,反正是开了对内核签名以后,哪怕是BIOS关闭了安全启动,仍然出现mkinitcpio会卡在autodetect上,无奈全部都删掉重来了,没有弄签名,希望各位看到本文章以后解决了这个问题能回复一下,谢谢。 surface默认是开启了安全启动(Microsoft签名)和bitlocker来保障设备和系统安全,我作为一个linux系统的开发者,当然是需要在surface上装一个linux了,但是前两年zccrs已经踩过坑了,linux不识别surface键盘,同样的触摸、网卡、声卡等设备也工作的不是很正常,本来以为我要开启远程开发的生活了,还特意写了一篇《使用VSCode远程开发DDE》,来给公司里有同样烦恼的人,让他们也感受一下远程开发的魅力。 苍天不负有心人,我成功的! 我成功的使用上了ArchLinux,并且工作的十分良好。这多亏了github上的一个组织linux-surface,有这么一些人,他们付出劳动来让手里的surface设备也用上linux,并且要和普通的x86兼容机一样工作,感谢他们的付出,也让我吃上了螃蟹,安装的过程我就不在这里详细说了,其实非常简单。 首先因为surface只有一个usb口,而键盘并不能工作,所以需要一个usb的扩展器。先按fn+f6进bios关闭掉安全启动,修改引导顺序为usb优先,之后就是正常的安装系统,但是不需要安装仓库里的内核,我们需要安装linux-surface提供的仓库里的内核。 这些都是非常正常的步骤,linux-surface提供了自己的仓库和内核,我们正常使用即可。 这里开始就是我研究了半天的内容, 开启安全模式! 首先我看了一下arch wiki上有关于安全启动的内容,写的挺详细的,就是看不懂。讲了各种的知识点,各种签名的方式,但是真正到我开始用的时候,我是一直失败的,失败的方式我就不说了,直接说我如何成功的。 首先我放弃了grub,一个原因是grub的安全启动我一直没有尝试成功,另外一个是我只有一个系统,没必要用grub。我换成了rEFInd来作为我的bootloader,首先安装rEFInd的引导。 yay -S refind shim-signed sbsigntools sudo refind-install --shim /usr/share/shim-signed/shimx64.efi --localkeys 来解释一下上面两条明令。第一个是安装必要的软件包,refind是bootloader本体,shim-signed是aur里面的用于安全启动的包,shim提供了一种并行的安全启动验证功能,我们使用它来启动refind的efi,再通过refind的efi启动内核,达到终极套娃启动。sbsigntools是用于给文件签名的工具,我们安装完refind以后,refind会帮助我们生成一份默认的key,我们需要使用这个key来为内核进行签名。 在执行第二条明令以后,会有几次询问,都选择Y回车就行。 然后使用sbsigntools来对内核进行签名。 sudo sbsign --key /etc/refind.d/keys/refind_local.key --cert /etc/refind.d/keys/refind_local.crt --output /boot/vmlinuz-linux-surface /boot/vmlinuz-linux-surface 准备工作已经进行一半了,我们只需要写一下refind的配置文件,就可以启动了。 refind的配置文件有两个地方,一个是boot分区下面的refind_linux.conf,还有一个是在efi分区里的EFI/refind/refind.conf,我们需要修改的是后者。 默认配置文件都是注释的,其实我们全部删了就可以了,有需要修改的地方去看原始文件或者文档就行了。 添加一个menuentry,就可以启动系统了。 also_scan_dirs +,ArchFS/bootdont_scan_dirs ESP:/EFI/boot,EFI/bootdont_scan_files shim.efi,MokManager.efi,fbx64.efi,mmx64.efi,shimx64.efiscan_all_linux_kernels falsemenuentry "Arch Linux" { icon /EFI/refind/icons/os_arch.png volume 8B131F77-62D7-4B4A-82D4-B60D7ACA2F6C loader /ArchFS/boot/vmlinuz-linux-surface initrd /ArchFS/boot/intel-ucode.img initrd /ArchFS/boot/initramfs-linux-surface.img options "root=UUID=9f8f9556-8ec1-4feb-9519-435beac8376f rw rootflags=subvol=ArchFS loglevel=3 quiet add_efi_memmap" submenuentry "Boot using fallback initramfs" { initrd /ArchFS/boot/initramfs-linux-surface-fallback.img } submenuentry "Boot to terminal" { add_options "systemd.unit=multi-user.target" }} 我用的是btrfs文件系统,所以配置文件有点罗嗦。解释一下上面的内容。 also_scan_dirs是指定扫描某个目录,因为我是btrfs文件系统,必须使用这个才能让refind扫描到内核文件,否则会无法启动。 dont_scan_dirs是跳过指定的目录,因为refind默认是会扫描所有的efi文件,我们自己提供了emnuentry,所以不需要让它扫描了。 dont_scan_files是跳过指定的文件,这里是防止其他目录出现对应的efi也被扫描到。 scan_all_linux_kernels是扫描所有linux内核,这样所有的内核就会出现在启动列表里,我们同样也是不需要的。 menuentry里面需要修改的地方有,volume是分区的partuuid,我因为这个uuid就测试了好几次,最后才反应过来不是filesystem uuid,要求的是partition uuid. 所有遇到ArchFS的地方都是不需要的,因为btrfs支持字卷,我的系统是在一个叫ArchFS的卷里面的,如果不是btrfs的文件系统,这个是不需要的,同样options里的rootflags选项也是不需要的,这是传递给内核的参数,让内核可以正确的加载根分区。 这样就算完工了,重启系统,然后进bios里把安全启动改成Microsoft & 3rd party CA,然后重新启动。 当第一次加载rEFInd的时候,因为我们的证书是才生成的,主板并没有存储对应的签名,rEFInd会启动mmx64.efi来让我们加载证书,证书的位置在/etc/refind.d/keys下,选择refind_local.cer导入,然后选择重启,重新进入系统就可以了。 导入证书这部分我其实不太确定,因为我除了使用shim方案,我还测试了preloader方案,那个方案会一开始就启动一个MOK的工具进行证书导入,我记不太清shim到底需不需要手动导入了,如果出现了,那就导入一下就行了,没出现的话就能正常的看到引导界面和进入系统了。 还有一个后续的动作需要处理,就是内核升级以后,我们需要对内核重新签名,否则会被bios拒绝启动。 编辑/etc/pacman.d/hooks/99-secureboot.hook,并写入以下配置: [Trigger]Operation = InstallOperation = UpgradeType = PackageTarget = linuxTarget = linux-surfaceTarget = systemd[Action]Description = Signing Kernel for SecureBootWhen = PostTransactionExec = /usr/bin/sh -c "/usr/bin/find /boot/ -type f \\( -name 'vmlinuz-*' -o -name 'systemd*' \\) -exec /usr/bin/sh -c 'if ! /usr/bin/sbverify --list {} 2>/dev/null | /usr/bin/grep -q \\"signature certificates\\"; then /usr/bin/sbsign --key /etc/refind.d/keys/refind_local.key --cert /etc/refind.d/keys/refind_local.crt --output {} {}; fi' \\;"Depends = sbsigntoolsDepends = findutilsDepends = grep 享受安全启动吧~","categories":[{"name":"技术","slug":"技术","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"deepin git version","slug":"deepin-git-version","date":"2020-09-06T04:51:08.000Z","updated":"2024-04-15T05:09:54.958Z","comments":true,"path":"2020/09/06/deepin-git-version/","permalink":"https://blog.justforlxz.com/2020/09/06/deepin-git-version/","excerpt":"This repository only provides the git version of deepin. You can replace the deepin group in the community by installing the deepin-git group.","text":"This repository only provides the git version of deepin. You can replace the deepin group in the community by installing the deepin-git group. The PKGBUILD for all packages are there https://github.com/justforlxz/deepin-git-repo, Each branch saves the corresponding software. Before adding this repository, you should first add the key used to sign the packages in it. You can do this by running the following commands: wget -qO - https://packages.justforlxz.com/deepingit.asc \\ | sudo pacman-key --add - It is recommended that you now fingerprint it by running sudo pacman-key --finger DCAF15B4605D5BEB and in a final step, you have to locally sign the key to trust it via sudo pacman-key --lsign-key DCAF15B4605D5BEB More infos on this process can be found at https://wiki.archlinux.org/index.php/Pacman/Package_signing#Adding_unofficial_keys. You can now add the repository by editing /etc/pacman.conf and adding [deepingit]Server = https://packages.justforlxz.com/ at the end of the file. See https://wiki.archlinux.org/index.php/Pacman#Repositories_and_mirrors for details. to install deepin git version: sudo pacman -Syy deepin-git If you don’t want to use the repository anymore, you can uninstall deepin git, or install the deepin group in Community. sudo pacman -Rscn deepin-git to install deepin group for community. sudo pacman -S deepin","categories":[{"name":"技术","slug":"技术","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"使用VSCode远程开发DDE","slug":"use-vscode-to-remotely-develop-dde","date":"2020-09-03T03:14:38.000Z","updated":"2024-04-15T05:09:55.058Z","comments":true,"path":"2020/09/03/use-vscode-to-remotely-develop-dde/","permalink":"https://blog.justforlxz.com/2020/09/03/use-vscode-to-remotely-develop-dde/","excerpt":"本文将介绍如何使用VSCode的远程开发套件连接到Deepin主机,进行DDE和其他软件的开发与调试.","text":"本文将介绍如何使用VSCode的远程开发套件连接到Deepin主机,进行DDE和其他软件的开发与调试. 介绍Visual Studio Code(简称VS Code)是一个由微软开发,同时支持Windows 、 Linux和macOS等操作系统的免费代码编辑器,它支持测试,并内置了Git 版本控制功能,同时也具有开发环境功能,例如代码补全(类似于 IntelliSense)、代码片段和代码重构等。该编辑器支持用户个性化配置,例如改变主题颜色、键盘快捷方式等各种属性和参数,同时还在编辑器中内置了扩展程序管理的功能。 在VSCode出来之前,Sublime曾经是前端开发者必备的软件,它使用python作为插件运行环境,并且也拥有不少的插件,但是很遗憾的是插件不能更改界面元素,可玩性不是很高。 再后来Atom作为GitHub的顶梁柱出现了,它基于使用Chromium和Node.js的跨平台应用框架Electron(最初名为Atom Shell),并使用CoffeeScript和Less撰写,并且支持js开发的插件,一时间拥有非常多的用户,并且从Sublime那里拉拢了非常多的前端开发者。 但是一切都在VSCode面世以后变了。VSCode同样也是基于Chromim和Electron开发,并且支持TypeScript开发插件,而且启动速度比Atom快很多,而且作为微软面向开源社区的主力产品,它和TypeScript一样,吸收了社区的很多意见和贡献,使得软件越来越好用。在语言支持方面,对 C#、JavaScript、和 TypeScript 等编程语言的原生支持最为完善。 安装VS Code官网提供的有vscode的安装包,windows用户下载stable版本的exe(System Installer)。 System Installer可以自动下载对应语言的环境包,推荐安装此版本。 安装完成后就可以安装插件了。 安装插件插件系统是一个编辑器的左膀右臂,emacs和vim作为终端下开发经常使用的编辑器,就拥有非常丰富的插件,几乎每个大佬使用的emacs和vim都不能互换使用。 dde的项目几乎都是cmake的项目,所以需要安装cmake插件和c++的插件,安装了这两个插件以后。vscode打开项目工程就会自动解析CMakeLists.txt,并且开启vscode的快速调试功能,还可以开始构建项目和调试项目了。 安装CMake、CMake Tools这两个插件就可以开发了。 安装C/C++和C++ Intellisense这两个插件可以对项目中的c++代码进行智能感知和代码补全,推荐安装。 安装Remote - SSH插件,可以让vscode通过ssh连接到目标机器,打开远程机器的目录和文件,并且在该模式下,部分插件可以自动切换成本地/远程模式,这样就可以在本机直接开发,但是操作的内容都是远程环境的。 安装完Remote - SSH插件以后,vscode的左下角就会有一个绿色的按钮,可以用来切换模式。 配置远程环境因为是要在Windows上进行远程开发,如果是直接在UOS或者Deepin上开发DDE,这一部分是可以不用看的,上面的插件安装完成以后就可以开发项目了。 点击左下角的绿色按钮,在弹出的面板选择Remote-SSH: Connect to Host。 会继续弹出一个面板,用来选择配置ssh的连接。 选择Add New SSH Host添加一个服务器。 输入ssh的命令,例如 ssh lxz@10.20.32.54。 然后选择一个保存配置的位置,一般默认选择用户家目录的.ssh目录即可。然后就提示添加成功,此时可以点击Connect按钮进行连接。 输入密码 登录以后会打开一个新的窗口,并提示正在连接。连接成功以后可以在左下角看到机器的信息。 然后打开命令面板,选择在SSH中安装本地扩展。 在打开的列表选择全选,然后安装。 等待全部安装成功。 开发和调试功能介绍CMake插件提供了编译、运行和调试的功能和命令,可以点击下方面板中的select target,选择要运行的目标程序,选择切换编译模式,可以选择Debug或者Release。还可以选择使用哪个编译器进行构建。 设置启动参数如果程序启动不需要提供参数,则可以直接点击下方面板的Debug按钮,或者打开命令面板选择CMake Debug Target,如果没有选择过Target,则会询问一次设置Target。 点击左侧的调试按钮,选择添加配置。 在弹出的面板选择GDB 此时vscode会创建出一个json文件,并生成了默认的配置文件。 我们需要进行一些调整,以便使用该配置文件进行调试。 program字段是程序二进制文件的位置,一般情况下我们是要手动写好路径,但是如果项目的二进制特别多,更换配置文件就会非常麻烦,而且配置文件里写死路径也不是很方便,我查阅了CMake插件的文档,发现CMake插件提供了两个很重要的变量,可以让我们方便的查找到路径。 "program": "${command:cmake.launchTargetPath}" CMake插件提供了launchTargetPath的变量,它对应的是CMake插件选择的默认target,启动调试之前需要我们先选择好Target。 "value": "$PATH:${command:cmake.launchTargetDirectory}" CMake插件还提供了launchTargetDirectory变量,用于获取程序启动所在的目录,一般需要我们指定到本次调试所需的环境变量中。 "environment": [ { "name": "PATH", "value": "$PATH:${command:cmake.launchTargetDirectory}" },] 然后我们就可以添加启动参数了。args字段保存了程序启动会传递的参数列表,例如这里会给fuse传递-d和/tmp/x。 "args": [ "-d", "/tmp/x"] 完整的配置如下: { "name": "(gdb) fuse", "type": "cppdbg", "request": "launch", "program": "${command:cmake.launchTargetPath}", "args": [ "-d", "/tmp/x" ], "stopAtEntry": false, "cwd": "${workspaceFolder}", "environment": [ { "name": "PATH", "value": "$PATH:${command:cmake.launchTargetDirectory}" }, ], "externalConsole": false, "MIMode": "gdb", "setupCommands": [ { "description": "为 gdb 启用整齐打印", "text": "-enable-pretty-printing", "ignoreFailures": true } ]} gdb调试此时我们就可以先通过CMake插件构建整个项目,再切换到运行面板,启动调试。 点击下方面板的Build按钮,构建项目。 再点击左侧的gdb fuse(deepin-turbo)按钮,因为配置文件里面我们起的名字是gdb fuse。我们在main函数添加一个断点,用来测试gdb是否工作正常。 一切都工作正常,在调试控制台可以使用-exec作为前缀来执行gdb的命令。 调试图形程序调试图形程序稍微有一些麻烦,因为是远程开发,图形程序又只能工作在目标机器,这里提供两个可行的方案。 synergy之类的键盘鼠标共享软件 在windows安装xserver 第一种方案是通过共享本机的键盘鼠标到远程机器,这样就可以在远程环境上面进行直接操作,好处是除了调试,也可以同时操作远程机器进行使用。 第二种方案是利用X11协议的网络透明,既图形程序和显示服务不一定在同一台机器上运行,我们只需要在Windows安装XServer程序,就可以让远程机器上的程序的画面显示到当前机器,并且可以操作。但是此方案有缺点,虽然设计上这种分离结构设计的很巧妙,但是因为远程OpenGL调用并不支持,所以图形无法调用3D程序渲染,并且和远程机器沟通需要大量的带宽,所以用起来体验并不好。 为了使用这两种方案,我们都需要在调试的launch.json中添加一个环境变量。 在运行面板点击齿轮按钮,可以编辑当前方案。 在打开的json文件中,找到environment字段,添加DISPLAY环境变量。 "environment": [ { "name": "DISPLAY", "value": ":0" }] 这样程序启动就有DISPLAY环境变量,我们就可以让程序在目标机器的屏幕上运行了。 还有一种自动化测试的方案,该方案是我个人认为所有开发都应该掌握的,通过自动化测试,我们就可以完全使用远程开发来完成开发任务,调试的时候只需要等待自动测试结果返回即可,设想一下,某个模块需要点击很多地方才可以重现一个问题,我们只需要设置好断点,让程序自动开始执行所有函数,并在最终出现问题的地方停下,我们就可以开始手动单步跟踪问题,完全不需要使用鼠标人工点击。(然而理想很美好,现实很残酷,我个人目前都没有掌握自动化测试的方式,现在也是只能通过鼠标点点点来重现问题。","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"},{"name":"技术","slug":"Linux/技术","permalink":"https://blog.justforlxz.com/categories/Linux/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"VS Code","slug":"VS-Code","permalink":"https://blog.justforlxz.com/tags/VS-Code/"},{"name":"Windows","slug":"Windows","permalink":"https://blog.justforlxz.com/tags/Windows/"}]},{"title":"在ArchLinux上开发startdde","slug":"develop-startdde-on-archlinux","date":"2020-08-06T08:14:26.000Z","updated":"2024-04-15T05:09:54.959Z","comments":true,"path":"2020/08/06/develop-startdde-on-archlinux/","permalink":"https://blog.justforlxz.com/2020/08/06/develop-startdde-on-archlinux/","excerpt":"dde 后端使用 go 作为主要的开发语言,使用 dbus 提供接口,主要使用 gsettings 来保存配置。 所以在进行后端开发前需要对以上内容有基本的了解,这里假定本文档的阅读者熟悉 dbus 和 gsettings,并有一定的开发经验。","text":"dde 后端使用 go 作为主要的开发语言,使用 dbus 提供接口,主要使用 gsettings 来保存配置。 所以在进行后端开发前需要对以上内容有基本的了解,这里假定本文档的阅读者熟悉 dbus 和 gsettings,并有一定的开发经验。 安装依赖虽然本项目是go语言开发的,但是我们并没有直接使用go的mod作为依赖管理方案,而是走系统包管理器的方式,所以要先安装startdde的编译依赖。 sudo pacman -Sy golang-github-linuxdeepin-go-dbus-factory golang-deepin-gir golang-deepin-lib golang-deepin-dde-api go git jq golang-golang-x-net golang-github-linuxdeepin-go-x11-client 这些包会被安装到系统的/usr/share/gocode目录下。还需要手动go get一个依赖到本地的GOPATH中。 go get -v github.com/cryptix/wav 设置GOPATH为了方便以后的开发,可以将GOPATH环境变量定义到~/.xprofile等文件中,或者shell的配置文件。例如我使用的zsh: export GOPATH=$HOME/Develop/Go:/usr/share/gocodeexport PATH=$HOME/Develop/Go/bin:$PATH 设置项目目录go要求项目目录必须在GOPATH中,所以要将startdde放到GOPATH的pkg.deepin.io/dde/目录下,但是GOPATH每次进入不方便,可以采用软链的形式将startdde的目录链接到GOPATH下。 cd ~/Develop/Deepingit clone https://github.com/linuxdeepin/startddemkdir -p ~/Develop/Go/src/pkg.deepin.io/dde/ln -sf ~/Develop/Deepin/startdde ~/Develop/Go/src/pkg.deepin.io/dde/startdde 这样就可以在一个方便的目录进行开发了。 vscode开发工具我个人推荐使用vscode当作开发工具,打开vscode安装go的插件,打开startdde目录,vscode会提示安装一些go的工具,选择全部安装即可。","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"}],"tags":[{"name":"dde","slug":"dde","permalink":"https://blog.justforlxz.com/tags/dde/"},{"name":"go","slug":"go","permalink":"https://blog.justforlxz.com/tags/go/"}]},{"title":"use github action to check dde-launcher","slug":"use-github-action-to-check-dde-launcher","date":"2020-07-27T10:14:21.000Z","updated":"2024-04-15T05:09:55.039Z","comments":true,"path":"2020/07/27/use-github-action-to-check-dde-launcher/","permalink":"https://blog.justforlxz.com/2020/07/27/use-github-action-to-check-dde-launcher/","excerpt":"本来打算7月份给dde添加github action验证,但是被各种事情耽误了,然后发现麒麟居然抢在我前面部署了全套的github action,这不能忍,赶紧把dde的github action也提上日程。并且打算听肥肥猫大佬的话,在aur给dde弄一套commit构建包,这样就可以在arch上使用比testing仓库更testing的dde了!","text":"本来打算7月份给dde添加github action验证,但是被各种事情耽误了,然后发现麒麟居然抢在我前面部署了全套的github action,这不能忍,赶紧把dde的github action也提上日程。并且打算听肥肥猫大佬的话,在aur给dde弄一套commit构建包,这样就可以在arch上使用比testing仓库更testing的dde了! github actions是github官方出的持续集成功能,以前大家在github上的项目都使用的第三方的Travis CI或者自建jenkins构建,但是github被微软收购以后,微软为了表现出给社区和用户的诚意,将大量github的付费功能免费公开给开发者使用,希望能将github打造成开发者中心,于是在2019年微软推出了免费的github actions,每个项目都可以免费使用官方提供的持续集成和持续部署功能,这对第三方业务无疑是个巨大的打击,虽然Travis CI和jenkins等方式仍然有一定的市场,但是对于中小项目的开源项目,使用官方提供的功能无疑是方便的。 github actions的配置十分简单,只需要几个简单的步骤就可以实现构建、执行和测试代码。并且可以使用Linux、Windows和MacOS环境,机器性能也十分强劲,编译速度非常的快。 这是给dde-launcher的一份基础配置,需要将配置文件放在.github/workflows/目录下,以build.yaml文件名保存。 name: CI Buildon: push: branches: - uos pull_request: branches: - uosjobs: archlinux: name: Archlinux Build Check runs-on: ubuntu-latest container: docker.io/library/archlinux:latest steps: - name: Checkout branch uses: actions/checkout@v2 - name: Refresh pacman repository run: pacman -Syy - name: Install build dependencies run: pacman -S --noconfirm base-devel cmake ninja qt5-tools deepin-qt-dbus-factory dtkwidget - name: CMake & Make run: | mkdir build cd build cmake ../ -G Ninja ninja 介绍一下配置文件吧,name是设置ci的名字,github允许有多个ci存在,可以做不同的事情,例如部署三个ci,一个做语法检查,一个做静态检查,一个做编译检查。name就是用来在界面上显示的。on是设置ci对哪些事件感兴趣,在这里我设置了push和pull_request,当发生push和pull request时,这个ci就会被启动,执行接下来的jobs的内容。jobs里是可以设置多个任务的,同样name字段也是用来展示本次动作的名称。runs-on是设置该job工作的环境,ubuntu-latest是linux环境,container是指使用哪个docker容器,github actions是可以使用docker的,也可以将自己的ci配置共享给其他人使用。run就是执行命令了,在配置文件中我手动运行了刷新仓库和编译项目所需的命令。job的steps可以理解成shell中一次动作的执行,uses是使用其他人封装好的命令,run则是执行本地命令。 可以看出github actions的配置是十分简单的,并且构建速度也非常的快,并且构建环境是使用的arch linux环境,为什么要选择arch作为ci的基础构建环境呢,原因当然不是因为和肥肥猫有py交易,arch上的dde更新速度很快,并且很多用户都使用arch+dde的方式使用linux,deepin自己维护的发行版因为基础仓库更新较慢,不适合一些用户,所以为了能让dde被更多的人接受和使用,在arch上及时更新dde是十分有必要的。所以才选择actions的环境为arch linux。","categories":[{"name":"技术","slug":"技术","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"使用perf工具分析程序性能","slug":"use-perf-to-analytics-program","date":"2020-07-21T01:15:11.000Z","updated":"2024-04-15T05:09:55.043Z","comments":true,"path":"2020/07/21/use-perf-to-analytics-program/","permalink":"https://blog.justforlxz.com/2020/07/21/use-perf-to-analytics-program/","excerpt":"最近在对DDE进行性能优化,所以补习了一下linux下的各种分析工具的使用方法。 这张图是来自Brendan Gregg大佬提供的linux分析工具的应用场景,可以看出几乎包含了系统每个地方应该用什么工具去分析。","text":"最近在对DDE进行性能优化,所以补习了一下linux下的各种分析工具的使用方法。 这张图是来自Brendan Gregg大佬提供的linux分析工具的应用场景,可以看出几乎包含了系统每个地方应该用什么工具去分析。 Linux Perf Tool允许系统进行分析为了能够正常分析,首先需要打开系统的调试功能,允许我们去对其他进程进行访问。 SysCtl较新的Linux内核具有sysfs可调参数/proc/sys/kernel/perf_event_paranoid,该参数允许用户调整perf_events非root用户的可用功能,数量越大则越安全(相应地提供较少的功能): Consider tweaking /proc/sys/kernel/perf_event_paranoid: -1 - Not paranoid at all 0 - Disallow raw tracepoint access for unpriv 1 - Disallow cpu events for unpriv 2 - Disallow kernel profiling for unpriv 默认值是不允许获取任何信息,所以我们需要修改为1或者0,允许我们访问CPU的事件信息。 临时修改 执行命令向内核接口直接写入值。 sudo tee /proc/sys/kernel/perf_event_paranoid <<< 1 永久修改 使用sysctl来配置其值,创建/etc/sysctl.d/50_perf_event_paranoid.conf文件,并写入kernel.perf_event_paranoid=1,执行sysctl -p来刷新系统配置。 perf 采样 性能优化相关的三种类型的工具,一种是sampling类型的,即采样,这种工具就是不停“询问”程序在做什么,perf在我们使用的这种模式下就是 sampling模式,如果是追踪某些event,就工作在trace模式,实际上就是第二种类型的工具,这种工具主要依靠事件或者hook,程序在运行的过程中不停主动告诉工具它自己在做什么,比如 strace;第三种是 instrument 类型的,这种主要就是依赖编译器进行插桩,精确知道代码行级别的执行情况(参考gcc instrumentation )。 by hualet on deepin 15.7 我们通过perf record命令才对程序进行采样记录。 perf record -g --call-graph=dwarf -F 99 /usr/bin/dde-shutdown 命令介绍: -g: 即采样全部信息--call-graph: 设置并启用调用图(堆栈链/回溯)记录,参数有fp(frame pointers)、dwarf(debug information)和lbr(Last Branch Record)。-F: 采样率 perf可以直接启动一个程序进行分析,也可以使用-p参数指定一个pid进行采样。 查看 perf 的采样结果当我们通过perf record完成采样以后,会在执行目录生成perf.data文件,此时我们就可以使用perf report命令对data文件进行数据分析了。 perf report --stdio perf report会自动打开当前目录下的perf.data文件,当然也可以在最后指定perf.data文件的路径。 perf report会根据–call-graph参数来生成不同的图,使用dwarf参数时会以函数调用栈的顺序来显示,使用这种方式可以方便的看出哪个函数执行的时间比较长,因为每次采样都能落到该函数上,也就意味着函数执行的时间非常长,再通过调用栈的深度来分析函数执行期间都在做什么事情。 hotspot火焰图在命令行下查看函数调用不是特别方便,所以就有图形化的工具用来方便的查看perf工具的生成结果,其中使用比较友好的是kde开发的hotspot工具,该工具可以直接打开perf.data文件,并生成对应的火焰图,火焰图是函数调用的另一种表现形式,火焰越高,也就意味着调用栈越深,火焰越广,也就意味着函数执行的时间很长。","categories":[{"name":"优化","slug":"优化","permalink":"https://blog.justforlxz.com/categories/%E4%BC%98%E5%8C%96/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"CTest & QTest/GTest","slug":"CTest & QTEST","date":"2020-06-16T07:11:40.000Z","updated":"2024-04-15T05:09:54.913Z","comments":true,"path":"2020/06/16/CTest & QTEST/","permalink":"https://blog.justforlxz.com/2020/06/16/CTest%20&%20QTEST/","excerpt":"本文会介绍一下QTest和GTest的一些功能和区别。","text":"本文会介绍一下QTest和GTest的一些功能和区别。 单元测试ctestctest是CMake提供的运行单元测试的工具,在使用CMakeLists.txt文件编译工程的时候,CTest会自动configure、build、test和展现测试结果。 ctest有两个模式: 模式一:使用CMake configure和build工程,在CMakeLists.txt,使用特殊的命令创建tests。使用CTest来执行那些测试。 模式二:使用CTest来执行一个script,这个script的语法必须和CMakeLists.txt相同。 使用方法: 在CMakeLists.txt使用include(CTest)和include(Dart)来导入CTest模块和开启ctest。使用add_test()来添加一个测试程序,测试程序是一个普通的二进制,只不过内部运行的是qtest或者gtest编写的测试用例。 include(CTest)include(Dart)add_executable(tests tests/test.cpp)add(NAME tests COMMAND $<TARGET_FILE:tests>) qt qtestqtest是Qt提供的单元测试框架,Qt Test是用于对基于Qt的应用程序和库进行单元测试的框架。Qt Test提供了单元测试框架中常见的所有功能以及用于测试图形用户界面的扩展。 Qt测试旨在简化基于Qt的应用程序和库的单元测试的编写: 特征 描述 轻量 Qt Test大约有6000行代码和60个导出符号组成 自成体系 Qt Test仅需要Qt Core模块中的几个符号即可进行非GUI测试 快速测试 Qt Test不需要特殊的测试运行程序,没有特殊的测试注册 数据驱动测试 可以使用不同的数据进行多次的测试 基本的GUI测试 Qt Test提供了用于鼠标和键盘的模拟功能 标杆管理 Qt Test支持基准测试,并提供多个测量后端 IDE友好 Qt Test输出可以由Qt Creator、Visual Studio等IDE解释的消息 线程安全 错误报告是线程安全和原子的 类型安全 模板的广泛使用可以防止隐式类型转换引起的错误 易于扩展 可以将自定义类型轻松添加到测试数据和测试输出中 断言QVERIFY() 用于验证数据是否正确。 QVERIFY(1 == 1);QVERIFY2(1 != 1, "1不等于1"); 循环QFETCH_GLOBAL() 该宏从全局数据表中的一行中获取类型类型为name的变量。 名称和类型必须与全局数据表中的列匹配。 这是断言,如果断言失败,则测试将中止。 QFETCH() 宏会在堆栈上创建一个类型为name的本地变量。 名称和类型必须与测试数据表中的列匹配。 这是断言,如果断言失败,则测试将中止。 QFETCH(QString, aString);QFETCH_GLOBAL(QLocale, locale); 比较QCOMPARE宏用于判断两个值是否相等,如果实际值和预期值匹配,将会继续运行,否则将失败记录在测试日至中,并且测试将被终止,不会尝试任何后续操作。 QCOMPARE(QString("hello").toUpper(), QString("HELLO")); 添加数据通过在包含_data()的函数中调用QTest::addColumn和QTest::newRow向测试用例增加数据,并通过QFETCH宏在测试用例中访问数据。 void TestQString::toInt_data(){ QTest::addColumn<QString>("aString"); QTest::addColumn<int>("expected"); QTest::newRow("positive value") << "42" << 42; QTest::newRow("negative value") << "-42" << -42; QTest::newRow("zero") << "0" << 0;}void TestQString::toInt(){ QFETCH(QString, aString); QFETCH(int, expected); QCOMPARE(aString.toInt(), expected);} 创建测试要创建测试,需要派生自QObject,并添加一个或多个专用槽函数。每个专用槽函数都是测试中的一个测试功能且必须为private。函数命名方法以casen_函数名或者以test结尾的方式。 使用QTest::qExec()可用于执行测试对象中的所有测试功能。 此外,还可以定义不用于测试功能的专用槽函数。如果存在,它们将由测试框架执行,并用于初始化和清除整个测试或当前的测试功能。 initTestCase() 将在第一个测试功能执行之前被调用 initTestCase_data() 将被调用以创建全局测试数据表 cleanupTestCase() 在最后一个测试函数执行后被调用 init() 将在每个测试功能执行之前被调用 cleanup() 将在每个测试函数之后调用 使用initTestCase()准备测试。每次测试都应使系统处于可用状态,因此可以重复运行。清理操作应在cleanupTestCase()中处理,因此即使测试失败也可以运行清理操作。 使用init()创建测试功能。每个测试功能都应使系统保持可用状态,以便可以重复运行。清理操作应在cleanup()中,即使测试功能失败并提前退出,清理动作也可以运行。 另外,可以使用RAII,并在析构函数中调用清除操作,以确保他们在测试函数返回且对象移出作用域时发生。 如果initTestCase()失败,将不执行任何测试功能。如果init()失败,则不执行以下测试功能,测试将继续进行下一个测试功能。 class Test : public QObject { Q_OBJECTprivate: bool verify() { return true; }private slots: void initTestCase() { qDebug() << "init test case"; } void firstTest() { QVERIFY(true); QCOMPARE(1, 1); } void secondTest() { QVERIFY(verify()); QVERIFY(1 != 2); } void cleanupTestCase() { qDebug() << "clean test case"; }}; 最后,如果测试类具有静态且公共的void initMain()方法,则在实例化QApplication对象之前,由QTEST_MAIN宏调用该方法。例如,这允许设置应用程序的属性,例如Qt::AA_DisableHighDpiScaling。这是在Qt5.14添加的。 使用CMake和CTest构建测试项目CMake还有其他优点。例如,几乎可以毫不费力地使用CDash将测试运行的结果发布到Web服务器上。 CTest可以扩展到非常不同的单元测试框架,并且可以与QTest一起使用。 project(mytest LANGUAGES CXX)include(CTest)include(Dart)find_package(Qt5 COMPONENTS Test REQUIRED)set(CMAKE_INCLUDE_CURRENT_DIR ON)set(CMAKE_AUTOMOC ON)add_executable(mytest tst_mytest.cpp)add_test(NAME mytest COMMAND mytest)target_link_libraries(mytest PRIVATE Qt5::Test) google testgoogle test(gtest)是google公司推出的c++单元测试框架,基于xUnit架构,并且支持Linux、Windows和mac,并且支持任何类型的测试和模拟,而不仅仅是单元测试。 基本概念当使用gtest时,通过编写断言来检查条件是否为真。断言的结果可能是成功、非致命失败或者致命失败。如果发生致命故障,将终止当前功能,否则将继续运行。 一个测试套件包含一个或者多个测试。当测试套件中的多个测试需要共享通用对象和子例程时,可以将他们放入一个测试桶中。 一个测试程序可以包含多个测试套件。 断言gtest断言类似于函数调用的宏,可以通过断言其行为来测试类或者函数。断言失败时,gtest会输出断言的源文件和行号位置以及失败消息。还可以提供自定义失败消息,该消息将会附加到gtest的消息之后。 断言成对出现,测试相同的事物,但是对当前函数有不同的影响。ASSERT_*版本失败时会产生致命错误,并终止当前功能。EXPECT_*会产生非致命错误,不会导致当前测试失败。通常EXPECT_*是首选,因为他们允许在测试中报告多个鼓掌,但是如果在断言失败时继续运行将没有意义时应当使用ASSERT_*。 由于ASSERT_*失败会从当前函数立即返回,可能会跳过其后的清理代码,导致内存泄漏。 基本断言基本断言可以进行基本的真/假条件测试 致命断言 非致命断言 验证 ASSERT_TRUE(condition); EXPECT_TRUE(condition); condition是真的 ASSERT_FALSE(condition); EXPECT_FLASE(condition); condition是假的 请记住,当它们失败时,将导致ASSERT_*致命故障并从当前函数返回,而当它们发生EXPECT_*非致命故障时,将允许该函数继续运行。无论哪种情况,断言失败都意味着其包含测试失败。 字符串比较该组中的断言比较两个C字符串。如果要比较两个string对象,请改用EXPECT_EQ,EXPECT_NE等。 致命断言 非致命断言 验证 ASSERT_STREQ(str1,str2); EXPECT_STREQ(str1,str2); 这两个C字符串的内容相同 ASSERT_STRNE(str1,str2); EXPECT_STRNE(str1,str2); 两个C字符串的内容不同 ASSERT_STRCASEEQ(str1,str2); EXPECT_STRCASEEQ(str1,str2); 两个C字符串的内容相同,忽略大小写 ASSERT_STRCASENE(str1,str2); EXPECT_STRCASENE(str1,str2); 两个C字符串的内容不同,忽略大小写 注意,断言名称中的“ CASE”表示忽略大小写。一个NULL 指针和一个空字符串被认为是不同的。 STREQ并STRNE接受宽C字符串(wchar_t*)。如果两个宽字符串的比较失败,则它们的值将打印为UTF-8窄字符串。 简单测试创建测试: 使用TEST()宏定义和命名测试功能。这些是没有返回值的普通C++函数。 在此函数,要与包含的所有有效C++语句一起使用各种gtest断言来检查。 测试结果由断言确定,如果测试中的任何声明失败(致命或非致命),或者测试崩溃,整个测试都会失败,否则测试应当成功。 TEST(TestSuiteName, TestName) { ...测试代码...} TEST()函数第一个参数是测试套件的名称,第二个参数是测试套件内的测试名称。这两个名称都必须是有效的C++标识符,并且不应包含任何下划线。来自不同测试套件的测试可以具有相同的名称。 参考资料:qtesthttps://doc.qt.io/qt-5/qtest.html gtesthttps://github.com/google/googletest/blob/master/googletest/docs/primer.md","categories":[{"name":"unit test","slug":"unit-test","permalink":"https://blog.justforlxz.com/categories/unit-test/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"},{"name":"Qt","slug":"Qt","permalink":"https://blog.justforlxz.com/tags/Qt/"},{"name":"CMake","slug":"CMake","permalink":"https://blog.justforlxz.com/tags/CMake/"},{"name":"GTest","slug":"GTest","permalink":"https://blog.justforlxz.com/tags/GTest/"},{"name":"CTest","slug":"CTest","permalink":"https://blog.justforlxz.com/tags/CTest/"}]},{"title":"CPP项目的一些坑","slug":"CPP项目的一些坑","date":"2020-06-15T03:11:40.000Z","updated":"2024-04-15T05:09:54.912Z","comments":true,"path":"2020/06/15/CPP项目的一些坑/","permalink":"https://blog.justforlxz.com/2020/06/15/CPP%E9%A1%B9%E7%9B%AE%E7%9A%84%E4%B8%80%E4%BA%9B%E5%9D%91/","excerpt":"本篇文章记录这几年项目中C++的一些问题和优化方法。需要注意的是,代码优化没有一本万利的方法,只能见招拆招,而且还要避免过早优化等问题,代码优化一定是要中后期才可以,而且不要为了优化而优化。","text":"本篇文章记录这几年项目中C++的一些问题和优化方法。需要注意的是,代码优化没有一本万利的方法,只能见招拆招,而且还要避免过早优化等问题,代码优化一定是要中后期才可以,而且不要为了优化而优化。 const和const &在接收一个返回值或者声明局部只读变量时没有使用const修饰。const的目的不仅仅是为了只读,更多的是编译器可以在此处提供优化。 QRect rect = m_displayInter->primaryRawRect();qreal scale = qApp->primaryScreen()->devicePixelRatio(); 在这两行例子中,react和scale都在当前函数内没有任何修改,而且不应该修改,需要添加const来修饰只读,并且QRect应该使用&来减少内存复制带来的额外影响。 类型强转在部分代码中,经常能看到C风格的代码强转,应当根据具体情况使用static_cast、dynamic_cast和reinterpret_cast。 static_cast是使用的比较多的cast,经常用于派生类和基类之间转换。dynamic_cast也用于派生类和基类的转换,如果类型T是指针类型,若转换失败,则返回T类型的空指针,如果时T是引用类型,则会抛出异常,返回std::bad_cast。reinterpret_cast并不会做实际的转换,只会在编译时进行检查,如果不能进行cast转换,则编译报错。 过多的嵌套过多的嵌套会严重影响代码阅读,经常出现只有if通过才会进入执行的情况,这种情况应该修改为不通过就不要继续执行,或者安排合理的if将条件限制在之前。 void BluetoothWorker::setAdapterPowered(const Adapter *adapter, const bool &powered){ QDBusObjectPath path(adapter->id()); //关闭蓝牙之前删除历史蓝牙设备列表,确保完全是删除后再设置开关 if (!powered) { QDBusPendingCall call = m_bluetoothInter->ClearUnpairedDevice(); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); connect(watcher, &QDBusPendingCallWatcher::finished, [ = ] { if (!call.isError()) { QDBusPendingCall adapterPoweredOffCall = m_bluetoothInter->SetAdapterPowered(path, false); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(adapterPoweredOffCall, this); connect(watcher, &QDBusPendingCallWatcher::finished, [this, adapterPoweredOffCall, adapter] { if (!adapterPoweredOffCall.isError()) { setAdapterDiscoverable(adapter->id()); } else { qWarning() << adapterPoweredOffCall.error().message(); } }); } else { qWarning() << call.error().message(); } }); } else { QDBusPendingCall adapterPoweredOnCall = m_bluetoothInter->SetAdapterPowered(path, true); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(adapterPoweredOnCall, this); connect(watcher, &QDBusPendingCallWatcher::finished, [this, adapterPoweredOnCall, adapter] { if (!adapterPoweredOnCall.isError()) { setAdapterDiscoverable(adapter->id()); } else { qWarning() << adapterPoweredOnCall.error().message(); } }); }} 这里的代码其实是可以优化的,我们可以通过三元表达式获取某个QDBusPendingCall,这样就可以使用一个QDBusPendingCallWatcher对象,然后将原本的lambda内容提取到其他函数内,在新的lambda中同样使用三元表达式运行对应的函数,这样拆分代码的好处是,阅读代码时的顺序会和执行顺序一致,分支判断对机器和人类都不是太友好,特别是判断体内有很长的代码段,找到else段是一件不容易的事情,通过降低if else块来提高代码可读性。同时应提取相同动作的代码到公共区域,以免将来修改时发现没有将所有的地方都做修改。 循环避免使用数组的方式来访问元素,使用迭代器的方式统一循环方式。 我注意到有些情况下,有人在for循环内直接定义静态变量,这种方式使用的时候需要注意,静态变量将会永远存在,但是大部分for循环内需要保存的数据都是成员变量,否则内存空间将永远不会释放,对内存有浪费。 而且经常遇到的问题就是foreach宏和for混用,在语法上就没有统一使用。 我推荐的方式是for+迭代器的方式,如果是简单遍历,使用原生的foreach语法即可。 std::list<int> list{ 1, 2, 3, 4};// 原生foreach语法,推荐只读遍历使用for (int item: list) { ...}// for+迭代器,只读遍历for (auto it = list.cbegin(); it != list.cend(); ++it) { // it是迭代器对象,需要解引用使用。 *it}// for+迭代器方式,推荐需要修改容器的长度使用for (auto it = list.begin(); it != list.end();) { // 注意,如果要移除某个元素,需要手动下一步 if (*it == 2) { it = list.erase(it); } else { ++it; }} 内存泄漏经常遇到使用容器将指针保存下来的场景,但是当对象被析构或者容器被清空的时候,有时候会忘记删除内部的对象,或者删除了不该删除的对象。对数据的处理应该保持RAII原则,避免直接使用裸指针,而是通过智能指针将指针保存起来,当最后一个对象不再持有智能指针对象时,智能指针会删除持有的对象,完成内存释放。 智能指针的类型 智能指针包含有三种:独占指针unique_ptr、共享指针shared_ptr和弱引用指针week_ptr。 独占指针独占指针std::unique_ptr可以避免对象被转移到其他对象中,如果某个对象持有unique_ptr,则该ptr不允许转移给其他对象,但是可以使用std::move来转移控制权,注意这和普通的转移不一样,unique_ptr禁止的是拷贝,但是没有禁止移动,我们可以转移控制转,unique_ptr保证的是只有一个智能指针持有对象。 std::unique_ptr<T> p1 = std::move(ptr); 共享指针共享指针std::shared_ptr顾名思义是用作共享的,和独占指针不同的是,它支持复制,内部通过引用计数来维持对象的生命周期,当没有任何一个对象持有共享指针时,也就意味着没有任何一个对象可以访问到内部对象了,就可以安全的删除对象,释放内存。 弱引用指针弱引用指针std::week_ptr是为了避免两个共享指针相互持有导致引用计数永远不会归零,导致内存永远不释放而提出的解决方案,具体就是弱引用指针不会导致引用计数增加,但是week_ptr同样不支持复制,必须转换为共享指针std::shared_ptr。 优化判断条件对于常数的判断,尽量使用宏或者定义静态常量来避免直接使用数字或者字符判断。 排序发现很多人在需要排序的时候总是使用冒泡算法,我介绍几个比较方便的排序方法。 使用std::sortC++标准库提供了std::sort方法来方便的排序,它有三个参数,第一个参数是容器的begin迭代器,第二个参数是end迭代器,第三个参数接收一个返回值为bool类型的函数,该函数用于实现手动控制排序的判断。 我们可以提供一个lambda表达式来方便的控制排序,或者提供一个函数指针。 std::list<int> list{ 10, 4, 2, 5 };std::sort(list.begin(), list.end(), [](int num1, int num2) { return num1 < num2;}); 这种排序方式是直接对原始容器进行操作的,如果不希望数据成为脏数据,应该先复制一份。 使用容器使用容器的方式比较麻烦一些,我们需要对象自己支持大小比较,或者顺序是外部某个列表列表控制的。 我们可以使用map将内部数据和标记数据建立映射关系,再通过外部的list或者其他方式,从map中将数据读出来,添加到新的列表容器中,从而完成排序。 [ "page1", "page2", "page3"]std::map<String, QWidget*> map;…const StringList & list = QJsonDocument::fromJson(readAll(“order.json”)).toStdList();QList<QWidget*> pages;for (const QString& key : list) { pages << map[key];}","categories":[],"tags":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"}]},{"title":"使用inquirer提供交互式git commit","slug":"使用inquirer提供交互式git-commit","date":"2020-06-15T02:36:19.000Z","updated":"2024-04-15T05:09:55.095Z","comments":true,"path":"2020/06/15/使用inquirer提供交互式git-commit/","permalink":"https://blog.justforlxz.com/2020/06/15/%E4%BD%BF%E7%94%A8inquirer%E6%8F%90%E4%BE%9B%E4%BA%A4%E4%BA%92%E5%BC%8Fgit-commit/","excerpt":"公司计划规范所有commit提交,开发部门综合出来了一份模板。 title(应当使用陈述句,简短的描述这个提交所做的事情)Description(详细说明代码的改动,包含代码的实现思路,以及为什么这么做,可能会影响哪些功能。对于代码的审核者,需要从这段描述中能完全理解代码中所有改动的内容)Log: 写一段面向于产品的总结性内容,用于自动生成crp上的changlog,需要注意的事,这段描述必须从产品的角度考虑。Bug: https://xxxxxxxxxxx 对应pms bug的链接Issue: fix #xx 所修复的bug对于的github issue,其中 "fix #xx"是github关闭issue的规则,此处内容只需要满足github的要求即可,详情请参考 https://help.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywordsTask: http://xxxxxxxxxxxx 对应pms任务的链接","text":"公司计划规范所有commit提交,开发部门综合出来了一份模板。 title(应当使用陈述句,简短的描述这个提交所做的事情)Description(详细说明代码的改动,包含代码的实现思路,以及为什么这么做,可能会影响哪些功能。对于代码的审核者,需要从这段描述中能完全理解代码中所有改动的内容)Log: 写一段面向于产品的总结性内容,用于自动生成crp上的changlog,需要注意的事,这段描述必须从产品的角度考虑。Bug: https://xxxxxxxxxxx 对应pms bug的链接Issue: fix #xx 所修复的bug对于的github issue,其中 "fix #xx"是github关闭issue的规则,此处内容只需要满足github的要求即可,详情请参考 https://help.github.com/en/enterprise/2.16/user/github/managing-your-work-on-github/closing-issues-using-keywordsTask: http://xxxxxxxxxxxx 对应pms任务的链接 python inquirer之前在掘金上看到有人在使用交互式的commit来规范commit信息,觉得用起来挺不错的,刚好符合本次公司的要求,不过原项目是nodejs的,项目里肯定不能让每个开发都安装一个node,所以就找一下代替品,然后就发现了python-inquirer。 使用起来也非常的方便,通过inquirer.Text、inquirer.List、inquirer.Checkbox就可以创建相应的交互,并把组合好的列表交给inquirer.prompt处理,返回一个对象,内部包含了所有做出的选择。 Textimport inquirerquestions = [ inquirer.Text('name', message="What's your name"), inquirer.Text('surname', message="What's your surname"), inquirer.Text('phone', message="What's your phone number", validate=lambda _, x: re.match('\\+?\\d[\\d ]+\\d', x), )]answers = inquirer.prompt(questions) Listimport inquirerquestions = [ inquirer.List('size', message="What size do you need?", choices=['Jumbo', 'Large', 'Standard', 'Medium', 'Small', 'Micro'], ),]answers = inquirer.prompt(questions) CheckBoximport inquirerquestions = [ inquirer.Checkbox('interests', message="What are you interested in?", choices=['Computers', 'Books', 'Science', 'Nature', 'Fantasy', 'History'], ),]answers = inquirer.prompt(questions) 公司的模板#!/bin/pythonimport inquirerimport sysfrom string import Templateimport subprocessdef number_validation(answers, current): return int(current)def empty_validation(answers, current): return bool(current)def no_validation(answers, current): return Truedef addList(name, message, list): return inquirer.List(name, message, list)def addText(name, message, valid): return inquirer.Text(name, message, validate=valid)def addCheck(_name, _message, _choices): return inquirer.Checkbox(_name, message=_message, choices=_choices)questions = [ addCheck("action", "选择非选项", ['Bug', 'Issue', 'Task'])]optinalAnswers = inquirer.prompt(questions)questions = [ addList('action', "select you action", ['fix', 'feat', 'refactor', 'docs', 'chore', 'style', 'pref', 'test']), addText("module", "input module name", no_validation), addText('title', "input title", empty_validation), addText('description', "input description", empty_validation), addText("log", "input log", empty_validation),]optinal = { "Bug": addText("bug", "input bug id", number_validation), "Issue": addText("issue", "input issue id", empty_validation), "Task": addText("task", "input task id", number_validation),}optinalMap = { "Bug": "bug", "Issue": "issue", "Task": "task",}for action in optinalAnswers["action"]: questions.append(optinal[action])answers = inquirer.prompt(questions)template = '${action}'if answers["module"]: template += '(${module})'template += ': ${title}\\n\\nDescription: ${description}\\n\\nLog: ${log}\\n'for action in optinalAnswers["action"]: template += action + ": ${" + optinalMap[action] + "}\\n"subprocess.run(["git", "commit", "-m", Template(template).substitute(answers)]) 修改git editor将上面的内容保存到/usr/bin/git-inquirer。 当我们执行git inquirer的时候就能看到交互,当操作完成后可以看到git log中message已经是按模板填充了。","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"vue-router路由复用后页面没有刷新","slug":"vue-router路由复用后页面没有刷新","date":"2020-06-01T02:35:41.000Z","updated":"2024-04-15T05:09:55.091Z","comments":true,"path":"2020/06/01/vue-router路由复用后页面没有刷新/","permalink":"https://blog.justforlxz.com/2020/06/01/vue-router%E8%B7%AF%E7%94%B1%E5%A4%8D%E7%94%A8%E5%90%8E%E9%A1%B5%E9%9D%A2%E6%B2%A1%E6%9C%89%E5%88%B7%E6%96%B0/","excerpt":"vue-router提供了页面路由功能,可以用来构建单页面应用,在使用vue-router的动态路由匹配的时候,遇到了url变化了,但是页面却没有任何动静的问题,记录一下。","text":"vue-router提供了页面路由功能,可以用来构建单页面应用,在使用vue-router的动态路由匹配的时候,遇到了url变化了,但是页面却没有任何动静的问题,记录一下。 动态路由匹配url变化了,但是组件没有变化是因为vue进行了组件复用,因为两个路由都渲染同个组件,比起销毁再创建,复用则显得更加高效。不过,这也意味着组件的生命周期钩子不会再被调用。所以我们需要手动进行数据刷新。 我们可以简单的使用watch来监听当前的路由变化,从而实现数据刷新。 import { watch } from "vue";import router from "./router";export default { setup() { // vue2 // watch: { // $route(to, from) { // // 对路由变化作出响应... // } // } // vue3 watch(router.currentRoute, () => { console.log("路由发生了变化"); }); }}; 也可以使用2.2中新加的beforeRouteUpdate路由守卫: export default { setup() { }, beforeRouteUpdate((to, from, next) => { // 不要在这里调用next // 通过to来判断是否重载数据 console.log("路由发生了变化"); }),} 以上就是vue3中使用vue-router-next来处理动态路由变化导致页面不刷新的方法。","categories":[],"tags":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/tags/Web/"}]},{"title":"vue3升级遇到的坑","slug":"vue3-upgrade","date":"2020-05-31T13:11:43.000Z","updated":"2024-04-15T05:09:55.092Z","comments":true,"path":"2020/05/31/vue3-upgrade/","permalink":"https://blog.justforlxz.com/2020/05/31/vue3-upgrade/","excerpt":"最近一直忙工作上的事,对提高自身能力的事有点落下了,趁着今天把之前思考的一些问题都给解决了,也顺手给自己的VueBlog把vue和webpack都升级到最新的beta版本,然后遇到了很多坑,今天就把坑都记录一下,免的以后忘了。 VueBlog目前使用的是webpack5 + vue3 + vue-router-next + typescript构建,目的在于替换当前的hexo站点,同样也是一个静态博客生成器,不过和hexo的定位不同,我使用的是单页面设计,而不是给每个页面生成对应的html文件,所以对SEO不友好,以后再想办法吧。","text":"最近一直忙工作上的事,对提高自身能力的事有点落下了,趁着今天把之前思考的一些问题都给解决了,也顺手给自己的VueBlog把vue和webpack都升级到最新的beta版本,然后遇到了很多坑,今天就把坑都记录一下,免的以后忘了。 VueBlog目前使用的是webpack5 + vue3 + vue-router-next + typescript构建,目的在于替换当前的hexo站点,同样也是一个静态博客生成器,不过和hexo的定位不同,我使用的是单页面设计,而不是给每个页面生成对应的html文件,所以对SEO不友好,以后再想办法吧。 升级Vue3首先使用vue add vue-next来升级vue到beta版本,执行以后vue会对代码进行一次转换,将旧版本的一些api转换为新版本。 App例如将main.ts中创建App对象的代码转换为新的,在vue2中,我们通过new Vue()来创建app对象,并调用$mount函数挂在元素。 import App from "./App.vue";const app = new Vue(App);app.$mount('#app') 在vue3中,主体思想都尽量通过函数来进行了,因为可以通过函数的参数和返回值进行类型推导。在vue3中,创建app对象通过createApp函数来进行,再通过mount函数挂载dom元素。 import App from "./App.vue";const app = createApp(APp);app.mount("#app"); Vur Router如果使用的有vue-router之类的插件,使用方法也有一些变化,router也需要通过对应的create函数创建。首先需要先升级vue-router,vue-router的下一个版本叫vue-router-next。在vue-router中,创建router对象的函数从VueRouter函数改为createRouter。 const router = new VueRouter({ ...});export default router; 在vue3中则需要使用新的函数返回: const router = createRouter({ ...});export default router; 内容也改了一部分,可以访问github仓库来看文档。 composition APIcomposition api是vue3提出的核心功能,其核心目的是通过将分散在各处的数据都整合到一个setup函数中进行初始化,并依赖vue的响应式数据改变来完成功能实现。 在RFC中就有composition api的动机。 更好的逻辑复用与代码组织 随着功能的增长,复杂组件的代码变得越来越难以阅读和理解。这种情况在开发人员阅读他人编写的代码时尤为常见。根本原因是 Vue 现有的 API 迫使我们通过选项组织代码,但是有的时候通过逻辑关系组织代码更有意义。 目前缺少一种简洁且低成本的机制来提取和重用多个组件之间的逻辑。 更好的类型推导另一个来自大型项目开发者的常见需求是更好的 TypeScript 支持。Vue 当前的 API 在集成 TypeScript 时遇到了不小的麻烦,其主要原因是 Vue 依靠一个简单的 this 上下文来暴露 property,我们现在使用 this 的方式是比较微妙的。(比如 methods 选项下的函数的 this 是指向组件实例的,而不是这个 methods 对象)。 换句话说,Vue 现有的 API 在设计之初没有照顾到类型推导,这使适配 TypeScript 变得复杂。相比较过后,本 RFC 中提出的方案更多地利用了天然对类型友好的普通变量与函数。用该提案中的 API 撰写的代码会完美享用类型推导,并且也不用做太多额外的类型标注。 这也同样意味着你写出的 JavaScript 代码几乎就是 TypeScript 的代码。即使是非 TypeScript 开发者也会因此得到更好的 IDE 类型支持而获益。 composition api 文档官方 vue3 rfc rfc网站 setup函数用起来确实舒服,所有有用的东西都可以放在一块,代码整理也方便,不像以前一样需要分散到各种hook和计算属性、data函数中。但是也有我用起来不舒服的地方,基本类型和对象都需要使用ref函数和reactive函数进行包装,有的时候用起来就各种麻烦,需要多注意一些。不过这个问题倒不是什么大问题,和写c++的时候所有的对象用智能指针包裹一层一样,用多了就习惯了。 这是一个vue2的经典例子,通过data函数和计算属性来返回不同的数据。 export default new Vue({ data: function() { return { message: "hello" } }, computed: { reversedMessage: function () { return this.message.split('').reverse().join(''); } }}); 在vue3中就可以全部集中到setup函数,并且一并返回,模板可以直接使用。 import { ref, computed } from "vue";export default { setup() { const message = ref("hello"); const reversedMessage = computed(() => { return message.value.split('').reverse().join(''); }); return { message, reversedMessage }; }}; 可以看出使用setup函数可以将模板所需的内容一块返回,结构更为清晰,vue2的模式也是可以的,只不过侧重点不一样,vue2的目的是一种动作的数据应该被放在一块,而vue3的setup函数则是将数据处理都放在一块,这样对数据的的整理比较方便和集中。 Propsprops是在组件上注册的自定义属性,当一个值传递给props的时候,它就会成为那个组件的一个property。 <hello v-bind:message="message" /> hello组件可以通过定义props函数来接收自定义属性。 export default new Vue({ props: { message: String }}); 这样就可以在helle.vue中使用message这个属性,不过需要注意的是,hello组件不要修改传递进来的message,否则会破坏数据的流向。 在vue3中使用会更加方便,因为类型推导更加方便。 interface Props { message?: String}export default { props: { message: { type: String, require: false, default: "", } }, setup(props: Props) { const reversedMessage = computed(() => { if (props.message === undefined) { return String; } const innerMessage = props.message; return innerMessage.split('').reverse().join(''); }); return { reversedMessage }; }}; 在vue3和typescript中使用props需要有一些注意的地方,首先Props里需要设置值可能为空,否则setup函数的签名将无法匹配。其次访问props数据需要开启setup函数的props参数,还有一个context参数,可以访问上下文的内容。","categories":[],"tags":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/tags/Web/"}]},{"title":"JavaScript建造者模式","slug":"JavaScript建造者模式","date":"2020-02-01T12:52:58.000Z","updated":"2024-04-15T05:09:54.915Z","comments":true,"path":"2020/02/01/JavaScript建造者模式/","permalink":"https://blog.justforlxz.com/2020/02/01/JavaScript%E5%BB%BA%E9%80%A0%E8%80%85%E6%A8%A1%E5%BC%8F/","excerpt":"建造者模式就是指将类的构造和其表示分离开来,调用者可以通过不同的构建过程创造出不同表示的对象。主要解决在软件系统中,有时候面临着”一个复杂对象”的创建工作,由于需求的变化,这个复杂对象的某些部分经常面临着剧烈的变化,一些基本部件不会变。所以需要将变与不变分离。与抽象工厂的区别:在建造者模式里,有个指导者(Director),由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造者模式可以强制实行一种分步骤进行的建造过程。","text":"建造者模式就是指将类的构造和其表示分离开来,调用者可以通过不同的构建过程创造出不同表示的对象。主要解决在软件系统中,有时候面临着”一个复杂对象”的创建工作,由于需求的变化,这个复杂对象的某些部分经常面临着剧烈的变化,一些基本部件不会变。所以需要将变与不变分离。与抽象工厂的区别:在建造者模式里,有个指导者(Director),由指导者来管理建造者,用户是与指导者联系的,指导者联系建造者最后得到产品。即建造者模式可以强制实行一种分步骤进行的建造过程。 建造者模式四要素 产品类Product: 一般是一个较为复杂的对象,也就是说创建对象的过程比较复杂,一般会有较多的代码。 抽象建造者类Builder: 将建造的具体过程交予它的子类来实现。 建造者类ConcreateBuilder: 组件产品,返回组件好的产品 指导类Director: 负责调用适当的建造者来组件产品,指导类一般不与产品类发生依赖关系,与指导类直接交互的是建造者类。 建造者模式的优点建造者模式的封装很好,使用建造者模式可以进行有效的封装变化,在使用建造者模式的场景中,产品类和建造者类是比较稳定的,因此,将主要的业务逻辑封装在指导者类中对整体可以取得比较好的稳定性。 建造者类也很方便扩展,如果有新的需求,只需要实现一个新的建造者类即可。 产品类 product.ts class Product { private _name: String; public name(): String { return this._name; } public setName(name: String) { this._name = name; }} 抽象建造类 builder.ts interface Builder { _product: Product; setName(name: String): Product; build(): Product;} 建造类 concreatebuilder.ts class ConcreateBuilder implements Builder { _product: Product = new Product; public setName(name: String): Product { this._product.setName(name); return this._product; } public build(): Product { return this._product; }}class HelloworldBuilder extends ConcreateBuilder { public build(): Product { this._product.setName("hello world!"); return this._product; }} 指导类 director.ts class Director { private _defaultBuilder: ConcreateBuilder = new ConcreateBuilder; private _helloworldBuilder: HelloworldBuilder = new HelloworldBuilder; public buildForDefault(): Product { return this._defaultBuilder.build(); } public buildForHelloworld(): Product { return this._helloworldBuilder.build(); }} 测试运行: let director = new Director();console.log(director.buildForDefault().name());console.log(director.buildForHelloworld().name()); 执行结果 undefinedhello world! 通过不同的builder就可以构建不同的对象出来,当需求变动的时候,我们只需要扩展出不同的Builder和Director就可以满足。","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.justforlxz.com/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.justforlxz.com/tags/JavaScript/"}]},{"title":"浅谈Javascript构造器模式","slug":"浅谈Javascript构造器模式","date":"2020-01-31T15:15:23.000Z","updated":"2024-04-15T05:09:55.100Z","comments":true,"path":"2020/01/31/浅谈Javascript构造器模式/","permalink":"https://blog.justforlxz.com/2020/01/31/%E6%B5%85%E8%B0%88Javascript%E6%9E%84%E9%80%A0%E5%99%A8%E6%A8%A1%E5%BC%8F/","excerpt":"为了简化操作,JavaScript提供了new关键字。new关键字用于创建一个用户定义类型的实例,或者具有构造函数的内置对象的实例。","text":"为了简化操作,JavaScript提供了new关键字。new关键字用于创建一个用户定义类型的实例,或者具有构造函数的内置对象的实例。 每当我们在一个函数调用前使用new关键字,该函数便会以一种特殊模式——构造模式来运行,在此模式中,JavaScript可以自动完成一些操作。基本上它是指解释器在你的代码中嵌入几行操作代码。 在JavaScript中,构造函数通常是认为用来实现实例的,但是JavaScript中没有类的概念,但是有特殊的构造函数,通过new关键字来调用定义的构造函数,你可以告诉JavaScript你需要创建一个新对象,并且新对象的成员声明都是构造函数里定义的。在构造函数内部,this引用的是新创建的对象。 function People(name: String, age: Number) { this.name = name; this.age = age; this.output = function() { return this.name + "已经" + this.age + "岁了"; }}let people = new People("justforlxz", 24);console.log(people.output()); 上面是个很简单的构造函数模式,我们从字面上this是people对象,但是其实并不是这样的,new运算符帮助我们生成了this的初始化代码。 new运算符一共做了三件事: 创建一个空对象 将空对象的原型赋值为构造器函数的原型 更改构造器函数内部的this,将其指向新创建的对象 let tmp = new Object();tmp.__proto__ = People.prototype;People.call(tmp); 最后会经过一个判断,如果构造器函数设置了返回值,并且返回值是一个Object类型的话,就直接返回该Object,否则就会返回新创建的空对象。 总结一下: JavaScript没有类的概念,但是为了实现OOP,就通过new关键字实现对函数进行插入代码来实现对象实例的初始化。构造器模式就是通过一个方法来new出一个对象,这个操作就叫构造器模式。","categories":[{"name":"设计模式","slug":"设计模式","permalink":"https://blog.justforlxz.com/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"}],"tags":[{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.justforlxz.com/tags/JavaScript/"}]},{"title":"2019 Review","slug":"2019review","date":"2020-01-01T13:25:54.000Z","updated":"2024-04-15T05:09:54.905Z","comments":true,"path":"2020/01/01/2019review/","permalink":"https://blog.justforlxz.com/2020/01/01/2019review/","excerpt":"上一次写年终总结还是18年回家的动车上,可惜写了一半没发表,觉得一年了没有什么能够回想起来的,就又删除了。今年不同了,今年有好多想说的。","text":"上一次写年终总结还是18年回家的动车上,可惜写了一半没发表,觉得一年了没有什么能够回想起来的,就又删除了。今年不同了,今年有好多想说的。 脱单第一件重要的事是我遇到了生命中的她。 自从工作以后,我妈天天念叨我的就是找对象,和我预想的没错,上学的时候盼我毕业,毕业以后盼我工作,工作以后盼我找对象结婚,找对象以后盼我赶紧生个娃让她抱。(大家的父母应该都这样) 加薪这件事确实也令我挺开心的,我的工资在2019年成功涨到了0.375乔(1乔等于**元 @nanpuyue) 涨工资谁不高兴,我估计也就马云不高兴了,毕竟他看不上钱。 学习今年看了很多C++的资料,对C++和编译器都有了更深的了解。想2016年半夜@zccrs在家教我编译原理,到现在我可以理解一门语言从设计到实现,再到使用模板完成编译时计算,我走了快三年,这三年里我一直没停下学习的脚步,在学习各种知识,从各种编程语言到各种框架原理,到图形界面的实现。还学习了单元测试,并且@hualet大佬给我讲了单元测试是什么,以及单元测试的重要性,从那以后我才算真正的了解单元测试的重要性,也使我在写代码的时候注重通过单元测试来保障我的功能。 去年对深度学习进行了一波学习,今年对Web工程化和TypeScript也学习了一下,也算是对目前最热门的两个领域进行了一定的了解。 读书去年买的TensorFlow看了没一半,今年倒是没买书,开始在微信读书上读书,利用一些空余时间读一点,我也推荐大家多利用空闲时间读读书,少刷抖音和bilibili。 《TensorFlow》未读完 《TypeScript实战》正在读 博客2019年我一共水了15篇文章。 12-26 在ArchLinux通过串口调试VMware虚拟机中的deepin 12-26 使用标准库std::sort函数进行排序 12-09 记录一个坑爹的usb网卡 12-09 使用github actions自动部署hexo文章到html仓库 12-08 Vue父子组件传值 —— props & $emit 12-08 添加Vue动画 11-25 使用webpack-dev-server来监听项目变化 10-24 给Archlinux开启BFQ和MuQSS 10-22 使用webpack打包Vue和TypeScript 10-14 webpack入门 06-16 wsl2的使用体验 06-15 入坑typescript了 05-23 CMake CTests for dde-control-center 02-21 如何在Deepin上使用LNMP 02-23 解决用了xposed后淘宝闪退 2019├── 01│ └── 23│ └── fuck-taobao│ └── index.html├── 02│ └── 21│ └── how-to-use-LNMP-on-deepin│ └── index.html├── 05│ └── 23│ └── CMake-CTests-for-dde-control-center│ └── index.html├── 06│ ├── 15│ │ └── 入坑typescript了│ │ ├── 深度录屏_选择区域_20190615202044.gif│ │ └── index.html│ └── 16│ └── wsl2的使用体验│ ├── index.html│ ├── Snipaste_2019-06-16_21-49-15.png│ └── Snipaste_2019-06-16_21-52-42.png├── 10│ ├── 14│ │ └── webpack入门│ │ └── index.html│ ├── 22│ │ └── 使用webpack打包Vue和TypeScript│ │ └── index.html│ └── 24│ └── 给Archlinux开启BFQ和MuQSS│ └── index.html├── 11│ └── 25│ └── 使用webpack-dev-server来监听项目变化│ └── index.html└── 12 ├── 08 │ ├── vue-component-props │ │ └── index.html │ └── vue-transitions │ └── index.html ├── 09 │ ├── 记录一个坑爹的usb网卡 │ │ └── index.html │ └── use-github-actions-to-depoly-hexo │ └── index.html └── 26 ├── cpp-sort │ └── index.html └── use-serial-port-debug-deepin-on-archlinux └── index.html34 directories, 18 files 科普视频 妈咪叔 (一个较真的理工男) 这个名字我第一眼看到的时候,还以为是个卖母婴的,没想到居然是个搞科普的,而且内容讲的也很好,有数学、物理、化学和天文学。 李永乐老师 以前偶尔看过老师的视频,因为一直都在热榜,所以没想到关注,后来是youtube上看到了,就点了关注,youtube上更新的和bilibili的还不是一样的,看最后结尾的时候youtube的只说youtube帐号关注,而bilibili的是bilibili,有时候还要多个平台去看。 萝王二号 之前在科普区随便看的时候,对生物学产生了一些兴趣,萝王讲的风格我很喜欢,特别是他注重昆虫分类学(骨包皮,皮包骨啊2333),还有一些辟谣视频。 芳斯塔夫 (鬼古) 也使对生物学产生了一些兴趣,鬼古说以他很中二的风格带领我学习了一波古生物的相关知识(旧日支配者!!!)。 木偶君 和鬼古一样是专门讲古生物的,不过每次结束的比较仓促,突然就结束了。 木鱼水心 木鱼并不是今年才关注的,最开始关注是他做EVA剧场版解析,后来《木鱼说》开始做一些科普,我开始一直关注了。 宇宙视觉 (永远不要停止思考) 一个讲天文的科普up,不过年底的时候换了配音,疑似配音出去单干了。 电影 流浪地球 阿丽塔:战斗天使 战狼2 惊奇队长 复仇者联盟4 何以为家 速度与激情: 特别行动 叶问4 纪录片 混沌:数学探秘 维度:数学漫步 动漫 刀剑神域 紫罗兰永恒花园 darling in the franxx 心理测量者 进击的巨人第三季 五等分的新娘 citrus~柑橘味香气~","categories":[{"name":"年度总结","slug":"年度总结","permalink":"https://blog.justforlxz.com/categories/%E5%B9%B4%E5%BA%A6%E6%80%BB%E7%BB%93/"}],"tags":[{"name":"2019","slug":"2019","permalink":"https://blog.justforlxz.com/tags/2019/"}]},{"title":"使用伪元素创建一个圆点","slug":"使用伪元素创建一个圆点","date":"2020-01-01T03:11:12.000Z","updated":"2024-04-15T05:09:55.095Z","comments":true,"path":"2020/01/01/使用伪元素创建一个圆点/","permalink":"https://blog.justforlxz.com/2020/01/01/%E4%BD%BF%E7%94%A8%E4%BC%AA%E5%85%83%E7%B4%A0%E5%88%9B%E5%BB%BA%E4%B8%80%E4%B8%AA%E5%9C%86%E7%82%B9/","excerpt":"","text":"我最早接触到CSS中的伪元素是在一次写背景模糊的时候,CSS中的blur会模糊下面所有的元素,但是可以通过伪元素在before中先模糊,这样下层是没有任何元素的,自然也不会有元素被模糊。 伪元素就如同它的名字一样,是假的元素,只是CSS引擎在排版的时候创建出来的,在DOM树中是不存在的,所以javascript是没办法操作伪元素的。伪元素分为before和after,可以在元素的前面或者后面创建一个假的元素,伪元素选择器的标志符号是::。 div::before 在div元素的前面创建一个元素,配合content属性一起使用。 div::after 在div元素的后面创建一个元素,配合content属性一起使用。 使用伪元素选择器需要注意一点的是,必须使用content属性,否则将不起任何作用。 伪元素选择器生效以后,可以在DOM中看到::before或者::after,这里提供一个例子。 html部分: <body> <div> Text </div></body> css部分: div::before { content: "This is before Text, ";}div::after { content: ", This is after Text.";} 此时页面上会看到输出这么一句话,This is before Text, Text , This is after Text.,并且使用鼠标只能选择到最中间的Text文本。 代码可以点击这里查看。 今天写这篇文章呢,是因为今天我在实现hexo的Next主题,看到它在列表中使用after创建了一个小圆点,并且我遇到了一个问题,所以写这篇文章记录一下。 Next用的是浮动布局来实现的,而我决定flex一把梭,整体布局是垂直的flex,首页、分类等列表内部是用水平的inline-flex实现的,最左边是图标,来自fortawesome,中间的文本使用span包裹一下,实现左对齐,然后通过伪元素在最右边创建一个小圆点,设置a元素的宽度为100%,就可以实现圆点在最右边。 坑就是在这里遇到的,如果a元素的宽度设置为100%,伪元素创建的小圆点就不能完全显示,少1像素或者多1像素就可以完全显示。最终的解决办法是给小圆点的周围增加了1像素的padding解决了,但是原因位置,谁看到这篇文章并且恰好知道原因的,还请帮忙评论回复一下。 <div id="site-nav"> <ul> <li v-for="item in items" v-bind:key="item.title"> <a v-bind:href="item.link"> <span id="menu-left" v-bind:class="item.class"></span> <span id="menu-text"> {{ item.title }} </span> </a> </li> </ul></div> #site-nav { background: white; padding: 20px 0;}ul { margin: 0; padding: 0 0;}#site-nav li { list-style-type: none;}#site-nav li a { padding: 5px 0px; text-align: left; line-height: inherit; transition-property: background-color; transition-duration: 0.2s; transition-timing-function: ease-in-out; transition-delay: 0s; display: flex; justify-content: space-between; align-items: center; text-decoration: none; font-size: 13px; border-bottom: 1px solid transparent; color: #555;}#site-nav li a:hover { background: #f9f9f9;}#menu-left { align-content: center; margin-left: 10px;}#menu-text { width: 100%; margin-left: 10px;}#site-nav li a::after { content: ' '; background: #bbb; width: 6px; height: 6px; border-radius: 50%; margin: 0 10px 0 0; min-width: 6px; min-height: 6px; max-height: 6px; max-width: 6px; display: block; box-sizing: border-box;} 但我提取了基本结构和css,demo是能够正常显示小圆点的,但是自己的Vue却不能正常显示,后来发现是display写成块级元素用的flex了,改成inline-flex就能正常显示了,但是在调整宽度的时候,就发现了上面的问题,它又不正常显示了,实在解决不了了,就用padding处理了。 参考资料: 千古壹号","categories":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/categories/Web/"}],"tags":[{"name":"CSS","slug":"CSS","permalink":"https://blog.justforlxz.com/tags/CSS/"}]},{"title":"在ArchLinux通过串口调试VMware虚拟机中的deepin","slug":"use-serial-port-debug-deepin-on-archlinux","date":"2019-12-26T09:26:13.000Z","updated":"2024-04-15T05:09:55.058Z","comments":true,"path":"2019/12/26/use-serial-port-debug-deepin-on-archlinux/","permalink":"https://blog.justforlxz.com/2019/12/26/use-serial-port-debug-deepin-on-archlinux/","excerpt":"电脑主板上的接口:进行串行传输的接口,它一次只能传输1Bit。串行端口可以用于连接外置调制解调器、绘图仪或串行打印机。它也可以控制台连接的方式连接网络设备,例如路由器和交换机,主要用来配置它们。消费性电子已经由USB取代串列接口;但在非消费性用途,如网络设备等,串列接口仍是主要的传输控制方式。","text":"电脑主板上的接口:进行串行传输的接口,它一次只能传输1Bit。串行端口可以用于连接外置调制解调器、绘图仪或串行打印机。它也可以控制台连接的方式连接网络设备,例如路由器和交换机,主要用来配置它们。消费性电子已经由USB取代串列接口;但在非消费性用途,如网络设备等,串列接口仍是主要的传输控制方式。 首先给虚拟机分配一个串口设备,选择Settings->Add->Serial Port。分配好串口设备以后,我们需要选择一个串口设备的调试方式,一个是将输出转向一个文件,或者是通过socket。 如果只是查看方式,选择outpu file即可。如果需要调试,则可以通过socket方式来进行。 socket方式需要给一个固定的路径分配/tmp/,我调试的时候给出的是/tmp/vhost,From选择Server,To选择An Application。From的意思是信息从哪里来,信息是虚拟机里的系统发出的,所以这里选择的是Server,如果是反向操作,需要选择Client。To也是有两个选项,第一个是An Virtual Machine,第二个是An Application。用于把消息发送给另外的虚拟机,或者是宿主机的一个应用程序。 安装minicom包,用于进行调试,minicom这个东西,不是太好用,退出方式是先按Ctrl+A,然后按q,有时候还不一定管用,不知道是没接受到,还是按错了。 先minicom -s 进行初始化,选择Serial port setup,按A编辑Serial Device,这里需要注意一下,通过socket进行调试,需要使用unix#前缀,然后加上在虚拟机里写的路径 unix#/tmp/vhost。然后保存,选择Exit,退出以后其实重启minicom,就进入minicom的调试界面了,然后此时开启虚拟机,给内核添加一个console=ttyS0的参数,就看到minicom显示输出的信息了,还可以交互。 [ 3.855725] [drm:vmw_fb_setcolreg [vmwgfx]] *ERROR* Bad regno 254.[ 3.857125] [drm:vmw_fb_setcolreg [vmwgfx]] *ERROR* Bad regno 255.deepin Login:CTRL-A Z for help | unix-socket | NOR | Minicom 2.7.1 | VT102 | Offline | unix#/tmp/vhost 此时就可以交互了,用法和tty一样,最后一行是minicom的输出,可以看到CTRL-A Z可以看help,minicom的版本,和访问的串口socket。","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"使用标准库std::sort函数进行排序","slug":"cpp-sort","date":"2019-12-26T09:24:23.000Z","updated":"2024-04-15T05:09:54.958Z","comments":true,"path":"2019/12/26/cpp-sort/","permalink":"https://blog.justforlxz.com/2019/12/26/cpp-sort/","excerpt":"std的sort方法接受两个迭代器begin和end。通过迭代器来抽象元素的访问,隐藏内部实现。","text":"std的sort方法接受两个迭代器begin和end。通过迭代器来抽象元素的访问,隐藏内部实现。 这是一个简单的例子: std::list<int> list { 0, 4, 2, 1, 3,};std::sort(list.begin(), list.end()); 结果就是list被排序了,至于使用了什么排序算法,我们并不需要关心。实际上标准库会通过元素的数量来决定使用什么算法,基于Introspective Sorting(内省式排序)。它是一种混合式的排序算法: 在数据量很大时采用正常的快速排序,此时效率为O(logN)。 一旦分段后的数据量小于某个阈值,就改用插入排序,因为此时这个分段是基本有序的,这时效率可达O(N)。 在递归过程中,如果递归层次过深,分割行为有恶化倾向时,它能够自动侦测出来,使用堆排序来处理,在此情况下,使其效率维持在堆排序的O(N logN),但这又比一开始使用堆排序好。 默认情况下排序是升序排序,既结果从小到大,我们可以通过使用std::equal_to、std::not_equal_to、std::greater、std::less、std::greater_equal和std::less_equal来控制排序。 以上是通过标准库内置的一些方式来控制排序,且适用于元素已实现了自定义比较(Compare)的要求。 比较 (Compare) 是一些标准库设施针对用户提供的函数对象类型所期待的一组要求。 对满足比较 (Compare) 的类型的对象运用函数调用操作的返回值,当按语境转换成 bool 时,若此类型所引入的严格弱序关系中,该调用的第一实参先于第二实参,则生成 true,否则生成 false。 与任何二元谓词 (BinaryPredicate) 相同,不允许该表达式的求值通过解引用的迭代器调用非 const 函数。 用人话来说就是,Compare必须提供出对比结果。 看一个例子: struct Test { int i;}std::list<Test> list { new Test(2), new Test(1), new Test(4), new Test(3), new Test(5),};std::sort(list.begin(), list.end(), [=] (const Test& test1, const Test& test2) -> bool { return test1.i < test2.i;}); 这个例子提供了一个Compare,通过lambda来提供自定义的对比函数,返回值必须是bool,否则将不满足对比函数的要求。 通过以上三种方式可以看出,标准库的sort函数可以很方便的为使用者提供标准对比和自定义对比。如果元素自己已实现operator<,则只需要使用标准库内置的对比函数即可,但是大部分情况其实并不会涉及到元素的排序,仅在临时情况下需要列表有序,所以我个人倾向于通过lambda提供Compare函数来完成列表的排序。 std::sort知无涯之std::sort源码剖析","categories":[],"tags":[]},{"title":"记录一个坑爹的usb网卡","slug":"记录一个坑爹的usb网卡","date":"2019-12-09T11:31:04.000Z","updated":"2024-04-15T05:09:55.100Z","comments":true,"path":"2019/12/09/记录一个坑爹的usb网卡/","permalink":"https://blog.justforlxz.com/2019/12/09/%E8%AE%B0%E5%BD%95%E4%B8%80%E4%B8%AA%E5%9D%91%E7%88%B9%E7%9A%84usb%E7%BD%91%E5%8D%A1/","excerpt":"","text":"网卡型号是Realtek RTL8811CU/RTL8821CU USB Wi-Fi adapter,买来是为了让黑苹果上网的,windows下也会自动下载和安装驱动,但是linux比较难受,内核不提供这样的驱动,只能去官方拿源码搞,今天在arch上打算装一下驱动,结果遇到了很多问题。 wiki上推荐的8821应该使用rtl88xxau-aircrack-dkms-git,但是我安装以后压根不能用,一点变化都没有,而且modprobe也没有作者给出的88XXau,无奈只得放弃。 继续谷歌之,在https://forum.mxlinux.org/viewtopic.php?f=107&t=50579看到了别人给的方案,然后果断clone并make,然后就因为没有适配5.x的内核编译失败,这可不行,翻了一下issue,看到作者在https://github.com/whitebatman2/rtl8821CU/issues/33提到了一个#23,这标题写的够可以,Newer version 5.4.1 (Support Linux versions from 4.4.x up to 5.4.x) ,赶紧搞起,去源地址clone和make,成功使用上了驱动,按照作者提到的安装usb_modeswitch,并切换usb模式,我成功的使用上了这个usb网卡。 吐槽一下,开发环境还是linux下舒服,仓库的包安装一下就可以开发了,windows下要自己写路径,mac下brew限制太死,一些库安装以后还要自己手动做些处理,一不小心就把shell的环境变量搞不行了,或者压根不能正常工作。","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"使用github actions自动部署hexo文章到html仓库","slug":"use-github-actions-to-depoly-hexo","date":"2019-12-09T05:19:11.000Z","updated":"2024-04-15T05:09:55.039Z","comments":true,"path":"2019/12/09/use-github-actions-to-depoly-hexo/","permalink":"https://blog.justforlxz.com/2019/12/09/use-github-actions-to-depoly-hexo/","excerpt":"","text":"请先允许我大喊一声:微软牛逼! 本文没有啥含金量,就是简单的说一下如何部署github-actions来自动生成hexo的public,并且再推送到html仓库的。 我的博客仓库一共分为两个,blog仓库是私有的,需要通过我的私钥才能访问,html仓库是公开的,hexo生成的静态内容会被上传到这里。 首先在package.json中添加一些命令,方便我们一键编译和提交: "scripts": { "build": "hexo clean && hexo g", "deploy": "yarn run build && hexo d", "backup": "hexo b",} 因为CI环境需要提交代码到仓库,所以申请一个个人用的token,访问https://github.com/settings/tokens创建一个新的,勾选上repo,生成完token以后,修改一下_config.yml中对deploy仓库的url,格式固定为https://x-access-token:你的[email protected]/你的名字/仓库名.git,例如我这里是https://x-access-token:[email protected]/justforlxz/html.git。 然后新家一个github actions,选择nodejs环境,我们只需要修改最后一个步骤,执行我们自己的命令即可。 设置git的用户名和邮箱地址 npm install -g yarn yarn run deploy 如果你还有一些其他步骤,可以自行扩展,比如我就有主题相关的操作,具体的内容如下: - name: npm install, build, and deploy run: | git config --global user.email "[email protected]" git config --global user.name "justforlxz" git submodule update --init cd themes/next git checkout dev cd ../../ npm install -g yarn yarn yarn run deploy 然后就可以愉快的自动部署了。","categories":[],"tags":[]},{"title":"Vue父子组件传值 —— props & $emit","slug":"vue-component-props","date":"2019-12-08T13:08:00.000Z","updated":"2024-04-15T05:09:55.091Z","comments":true,"path":"2019/12/08/vue-component-props/","permalink":"https://blog.justforlxz.com/2019/12/08/vue-component-props/","excerpt":"Vue的父子组件传值比较有意思,父组件通过属性绑定,把自身的值和子组件的一个属性绑定起来,子组件通过props属性接收,该属性类型为数组,是Vue对象中比较少有的类型,data、computer、methods等方法都是对象的形式,props则是数组的形式。父组件通过v-on来监听子组件发出的事件来接收子组件的调用。在这里我是理解成子组件发送信号来通知上层,毕竟调用的是this.$emit来做到的。","text":"Vue的父子组件传值比较有意思,父组件通过属性绑定,把自身的值和子组件的一个属性绑定起来,子组件通过props属性接收,该属性类型为数组,是Vue对象中比较少有的类型,data、computer、methods等方法都是对象的形式,props则是数组的形式。父组件通过v-on来监听子组件发出的事件来接收子组件的调用。在这里我是理解成子组件发送信号来通知上层,毕竟调用的是this.$emit来做到的。 我们假设子组件名为,我们通过v-bind来绑定一个值给它。 <template> <div id="#app"> // 通过v-bind绑定父子组件的属性 <hello v-bind:messageFromParent="message"/> </div><template><script lang="ts">import Vue from 'vue';import Hello from './Hello.vue';export default Vue.extend({ data: { message: "this message from parent" }, components: { "hello": Hello }});</script> 子组件hello.vue通过props属性接收,内容是这样的: <template> <div> {{ messageFromParent }} </div></template><script lang="ts">import Vue from 'vue';export default Vue.extend({ // 通过props数组接收 props: [ "messageFromParent" ]});</script> 这里有个需要注意的地方,父组件给子组件的数据是单向的,虽然子组件也可以修改父组件传入的数据,但是会产生一个错误,并打印在终端里。 那么我们怎么才能修改父组件的值呢?答案是this.$emit。 我们给子组件绑定上v-on,来监听子组件的事件。 <template> <div id="#app"> // 通过v-bind绑定父子组件的属性,通过v-on监听子组件的属性变化 <hello v-bind:messageFromParent="message" v-on:changeParentData="changeData"/> </div><template><script lang="ts">import Vue from 'vue';import Hello from './Hello.vue';export default Vue.extend({ data: { message: "this message from parent" }, components: { "hello": Hello }, methods: { changeData: function(data: string) { message = data; } }});</script> 子组件只需要发送出修改即可: <template> <div> <button v-on:click="change">修改父组件数据</button> {{ messageFromParent }} </div></template><script lang="ts">import Vue from 'vue';export default Vue.extend({ // 通过props数组接收 props: [ "messageFromParent" ], methods: { change: function() { // 调用this.$emit方法第一个参数是事件的名称,后面全部都是参数。 // this.$emit方法其实是自定义了一个事件,通过这种方式来完成子组件向父组件传递消息。 this.$emit("changeParentData", "change data by child"); } }});</script> 以上就是Vue父子组件传值的一种常用方法,适用于相邻组件的,如果隔代了,那么这种方式就不好用了,中间路过的组件都需要转发这个事件,处理这种情况就需要使用provide/ inject了,不过那就是另一篇文章啦。","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/tags/Vue/"}]},{"title":"添加Vue动画","slug":"vue-transitions","date":"2019-12-08T00:03:40.000Z","updated":"2024-04-15T05:09:55.091Z","comments":true,"path":"2019/12/08/vue-transitions/","permalink":"https://blog.justforlxz.com/2019/12/08/vue-transitions/","excerpt":"以前一直搞不懂动画是怎么做的,它怎么这么神奇,写了一点看不懂的代码,就实现了非常丰富的效果,现在做了三年Qt开发,接触到了Qt的动画类,明白了动画是怎么一会儿事,现在来看当初的css动画代码,也明白了它是如何工作的了。本文会介绍一下Vue提供的组件过渡动画模块——transitions。","text":"以前一直搞不懂动画是怎么做的,它怎么这么神奇,写了一点看不懂的代码,就实现了非常丰富的效果,现在做了三年Qt开发,接触到了Qt的动画类,明白了动画是怎么一会儿事,现在来看当初的css动画代码,也明白了它是如何工作的了。本文会介绍一下Vue提供的组件过渡动画模块——transitions。 概述Vue在插入、更新和移除DOM元素时,提供了多种不同方式的应用过渡效果。包含以下工具: 在css过渡和动画中自动应用class 可以配合第三方动画css类,例如Animae.css 提供钩子函数来使JS操作DOM元素 可以配合使用第三方JavaScript动画库,例如Velocity.js 单元素/组件过渡Vue提供了 transitions 的封装组件,在下面的情况中,可以给任意元素或组件添加进入和离开的过渡效果。 条件渲染 (使用 v-show) 按需渲染 (使用 v-if) 动态节点 组件根元素 这是一个基本的例子: <div id="app"> <button v-on:click="show = !show"> Toggle </button> <transitions name="fade"> <p v-if="show"> hello! </p> <transitions><div> new Vue({ el: "#app", data: { show: false }}); 在head中添加style: .fade-enter-active,.fade-leave-active { transition: opacity .5s;}.fade-enter,.fade-leave-to { opacity: 0;} 这里有三点需要注意一下,需要动画的元素需要使用transitions节包裹起来,transitions需要一个name,css中需要使用固定的拼写来应用动画,入场动画和离场动画的状态是一致的,所以写在了一组里。 当插入和删除包含在 transitions 组件中的元素时,Vue会做以下的事情: 自动嗅探组件是否应用了css的过渡或动画,如果有,则在恰当的实际添加/删除css类名。 如果 transitions 组件提供了钩子函数,Vue会在恰当的时机调用钩子函数。 如果没有找到css过渡和动画,也没有找到钩子函数,则DOM的操作(插入和删除)在下一帧中立即执行。(注意是指浏览器的逐帧动画,而不是Vue的nextTick机制) 过渡的类名Vue的过渡动画一共有6个状态: v-enter: 定义进入过渡的开始状态,在元素被插入之前生效,待元素插入以后被移除。 v-enter-active: 定义进入过渡生效时的状态,在整个进入过渡的阶段中应用,在元素插入之前生效,在过渡/动画完成后被移除。这个类可以定义过渡时间、延迟和动画曲线。 v-enter-to: 在2.1.8版本及以上 定义进入过渡的结束状态,在元素被插入的下一帧生效(与此同时 v-enter 被移除),在过渡/动画完成后移除。 v-leave: 定义离开过渡的开始状态,在离开过渡被触发时立即生效,下一帧被移除。 v-leave-active: 定义离开过渡生效时的状态,在整个离开过渡的阶段中应用,在离开过渡触发时立即生效,在过渡/动画完成后立即被移除。这个类可以定义离开过渡的过程时间、延迟和动画曲线。 v-leave-to: 在2.1。8版本及以上 定义离开过渡的结束状态,在离开过渡被触发之后的下一帧被移除(与此同时v-leave也被删除),在过渡/动画完成之后移除。 可以看出一共两组动画,进入和离开的active。并且分别有两个状态,enter和enter-to,这6个状态控制了入场动画和离场动画。(吐槽一下Qt的动画系统,定义一个QAnimation只能做半场动画,想做到Vue这样的要定义两组,或者反向播放) 对于那些正在过渡中切换的类名来说,如果使用了没有name属性的transition,Vue会使用v-当做默认前缀。为了避免多组动画冲突,我个人建议每一个transition组件都提供name属性。 JavaScript钩子函数transition也提供了钩子函数,使我们可以通过JavaScript来控制DOM元素,一共也包含了8个函数: beforeEnter enter afterEnter enterCancelled beforeLeave leave afterLeave leaveCancelled 和css上要求的命名保持一致,只是增加了两个取消的接口,当动画被取消的时候被调用。 这些钩子函数可以结合CSS transition/animations 使用,也可以单独使用。 当只使用JavaScript过渡的时候,必须在 enter 和 leave 显式调用done()进行回调,否则他们将被同步调用,过渡会立即完成。 推荐对于仅使用JavaScript过渡的元素添加v-bind:css="false",Vue会跳过CSS的检测,这也可以避免过渡过程中css的影响。 列表元素的过渡以上我分享的都是单元素/组件的过渡,那么问题来了,列表这种通过v-for创建的元素该如何增加过渡效果呢? Vue提供了<transition-group>组件,在深入了解之前,需要先介绍一下这个组件的一些特点: 不同于<transition>,<transition-group>会创建一个真实的DOM元素,默认是,可以通过tag属性切换为其他元素。 过渡模式不再可用,因为我们不再相互切换特有的元素 内部元素总是需要提供唯一的key值来进行区分 CSS过渡将会应用在内部的元素中,而不是这个组/容器本身 列表的进入/离开过渡<div id="app"><button v-on:click="add">add</button><button v-on:click="remove">remove</button><transition-group name="group" tag="ul"><li v-for="item in items" v-bind:key="item"> {{ item }}</li></transition-group></div> new Vue({ el: "#app", data: { items: [1,2,3] }, methods: { add() { this.items.push(0) } }}) .group-enter,.group-leave-to { opacity: 0; transform: translateY(10px)}.group-enter-active,.group-leave-active { transition: all 1s;} 代码在这里,点击访问,只实现了添加元素的过渡效果。 希望本文可以帮助你理解Vue是如何处理过渡动画,本文是基于官网的知识和demo所编写的,本文只写了一部分我觉得需要掌握的基本功能,Vue的transition组件还有很多功能等待你的挖掘,点击前往Vue官网文档","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/tags/Vue/"}]},{"title":"使用webpack-dev-server来监听项目变化","slug":"使用webpack-dev-server来监听项目变化","date":"2019-11-25T09:54:36.000Z","updated":"2024-04-15T05:09:55.095Z","comments":true,"path":"2019/11/25/使用webpack-dev-server来监听项目变化/","permalink":"https://blog.justforlxz.com/2019/11/25/%E4%BD%BF%E7%94%A8webpack-dev-server%E6%9D%A5%E7%9B%91%E5%90%AC%E9%A1%B9%E7%9B%AE%E5%8F%98%E5%8C%96/","excerpt":"webpack的出现方便了前端开发者,使开发和部署分成了两部分,开发者可以正常根据工程化的要求进行开发,部署时通过webpack实现代码的裁剪和优化。 本次就介绍一个webpack的功能 webpack-dev-server 将webpack与提供实时重载的开发服务器一起使用。这仅应用于开发。它在后台使用了webpack-dev-middleware,它提供了对Webpack资产的快速内存访问。","text":"webpack的出现方便了前端开发者,使开发和部署分成了两部分,开发者可以正常根据工程化的要求进行开发,部署时通过webpack实现代码的裁剪和优化。 本次就介绍一个webpack的功能 webpack-dev-server 将webpack与提供实时重载的开发服务器一起使用。这仅应用于开发。它在后台使用了webpack-dev-middleware,它提供了对Webpack资产的快速内存访问。 webpack-dev-server提供了一个小型的express的http服务器,这个http服务器和client使用了websocket通讯协议,原始文件作出改动后,webpack-dev-server会实时的编译,但是最后的编译的文件并没有输出到目标文件夹。 注意:启动webpack-dev-server后,在目标文件夹中是看不到编译后的文件的,编译后的文件都保存到了内存当中来加速访问。 启用webpack-dev-servernpm install -D webpack-dev-server 在webpack.config.js中添加devServer对象: var path = require('path');module.exports = { //... devServer: { contentBase: path.join(__dirname, 'dist'), compress: true, // 开启压缩 port: 9000 // 指定运行的端口 }}; 然后通过npx webpack-dev-server启动,终端上会输出一些信息,一般我们会增加一些参数来使输出更加好看: webpack-dev-server --devtool eval-source-map --progress --colors --hot --inline 上面的命令增加一个开发工具 eval-source-map,开启了progress进度显示,开启了colors颜色,hot热更新和inline更新模式。上面的参数也可以添加到devServer的属性中。 终端输出的内容如下: 10% building 1/1 modules 0 activeℹ 「wds」: Project is running at http://localhost:9000/ℹ 「wds」: webpack output is served from /dist/ℹ 「wds」: Content not from webpack is served from /home/justforlxz/Projects/VueBlog/distℹ 「wdm」: Hash: ff9005d9f6ffafd11cd4Version: webpack 4.41.0Time: 2938msBuilt at: 11/25/2019 6:03:50 PM Asset Size Chunks Chunk Namesmain.js 2.09 MiB main [emitted] mainEntrypoint main = main.js[0] multi (webpack)-dev-server/client?http://localhost:9000 (webpack)/hot/dev-server.js ./src/main.ts 52 bytes {main} [built] 我们就可以通过localhost:9000来访问我们的应用了。 需要注意的是,由于我们经常把内容输出到dist目录,但是webpack运行是在项目目录的,访问webpack生成在dist目录的main.js时,需要写上相对于webpack的目录,例如dist/main.js。否则会找不到文件。 如果遇到问题,导航到 /webpack-dev-server 路径,可以显示出文件的服务位置。 例如,http://localhost:9000/webpack-dev-server。 配置webpackwebpack-dev-server支持在服务内部调用中间件对数据进行处理。 devServer.beforefunction (app, server) 在服务内部的所有其他中间件之前, 提供执行自定义中间件的功能。 这可以用来配置自定义处理程序,例如: module.exports = { //... devServer: { before: function(app, server) { app.get('/some/path', function(req, res) { res.json({ custom: 'response' }); }); } }}; devServer.after同devServer.before,在服务内部的所有中间件之后,提供执行自定义中间件的功能。 devServer.allowedHosts允许添加白名单服务,允许一些开发服务器访问。 module.exports = { //... devServer: { allowedHosts: [ 'host.com', 'subdomain.host.com', 'subdomain2.host.com', 'host2.com' ] }}; 模仿 django 的 ALLOWED_HOSTS,以 . 开头的值可以用作子域通配符。.host.com 将会匹配 host.com, www.host.com 和 host.com 的任何其他子域名。 module.exports = { //... devServer: { // 这实现了与第一个示例相同的效果, // 如果新的子域名需要访问 dev server, // 则无需更新您的配置 allowedHosts: [ '.host.com', 'host2.com' ] }}; devServer.clientLogLevelstring: 'none' | 'info' | 'error' | 'warning' 当使用内联模式(inline mode)时,会在开发工具(DevTools)的控制台(console)显示消息,例如:在重新加载之前,在一个错误之前,或者 模块热替换(Hot Module Replacement) 启用时。默认值是 info。 devServer.clientLogLevel 可能会显得很繁琐,你可以通过将其设置为 ‘none’ 来关闭 log。 module.exports = { //... devServer: { clientLogLevel: 'none' }}; devServer.color - 只用于命令行工具(CLI)只在终端下启用,启用/禁用控制台的彩色输出。 webpack-dev-server --color devServer.compressboolean 一切服务都开启gzip压缩。 module.exports = { //... devServer: { compress: true }}; devServer.contentBaseboolean: false string [string] number 告诉服务器从哪个目录中提供内容。只有在你想要提供静态文件时才需要。devServer.publicPath 将用于确定应该从哪里提供 bundle,并且此选项优先。 默认情况下,将使用当前工作目录作为提供内容的目录。将其设置为 false 以禁用 contentBase。 module.exports = { //... devServer: { contentBase: path.join(__dirname, 'public') }}; 也可以从多个目录提供内容: module.exports = { //... devServer: { contentBase: [path.join(__dirname, 'public'), path.join(__dirname, 'assets')] }}; devServer.disableHostCheckboolean 设置为 true 时,此选项绕过主机检查。不建议这样做,因为不检查主机的应用程序容易受到 DNS 重新连接攻击。 module.exports = { //... devServer: { disableHostCheck: true }}; devServer.filenamestring 在 lazy mode(惰性模式) 中,此选项可减少编译。 默认在 lazy mode(惰性模式),每个请求结果都会产生全新的编译。使用 filename,可以只在某个文件被请求时编译。 如果 output.filename 设置为 ‘bundle.js’ ,devServer.filename 用法如下: module.exports = { //... output: { filename: 'bundle.js' }, devServer: { lazy: true, filename: 'bundle.js' }}; 现在只有在请求了bundle.js时,才会去编译bundle。 总结webpack的功能确实很强大,可以针对代码进行各种操作,最终生成出可以适应各种场景的代码,使开发和部署彻底分离开来,开发者可以更加专注项目。","categories":[{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/categories/Vue/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/tags/Vue/"}]},{"title":"给Archlinux开启BFQ和MuQSS","slug":"给Archlinux开启BFQ和MuQSS","date":"2019-10-24T05:19:21.000Z","updated":"2024-04-15T05:09:55.100Z","comments":true,"path":"2019/10/24/给Archlinux开启BFQ和MuQSS/","permalink":"https://blog.justforlxz.com/2019/10/24/%E7%BB%99Archlinux%E5%BC%80%E5%90%AFBFQ%E5%92%8CMuQSS/","excerpt":"最近在Arch上更新系统的时候,总是遇到图形完全卡住的情况,今天上午突然想起来自己曾经设置了使用noop的IO调度,猜测是因为这个。然后本着不折腾不舒服的原则,打算使用ck内核上MuQSS的进程调度和BFQ的IO调度。","text":"最近在Arch上更新系统的时候,总是遇到图形完全卡住的情况,今天上午突然想起来自己曾经设置了使用noop的IO调度,猜测是因为这个。然后本着不折腾不舒服的原则,打算使用ck内核上MuQSS的进程调度和BFQ的IO调度。 ck内核并没有在arch的仓库,但是aur有linux-ck的包,安装一下就可以了。 yay -S linux-ck linux-ck-headers 编译需要一些时间,在我的破本子i7-8550U编译了一顿过桥米线的时间,然后成功使用了ck内核。 开启MuQSSck内核默认使用的就是MuQSS调度,并不需要修改什么,开机即可。 开启BFQ开启BFQ需要一些手动设置。分为两步: 修改grub,给内核提供新的参数 使用udev开启动态调整 修改grub 编辑/etc/default/grub中GRUB_CMDLINE_LINUX_DEFAULT,增加一行内容: GRUB_CMDLINE_LINUX_DEFAULT="quiet scsi_mod.use_blk_mq=1" 然后更新grub配置文件: sudo grub-mkconfig -o /boot/grub/grub.cfg 创建udev规则 创建并编辑/etc/udev/rules.d/60-scheduler.rules # set deadline scheduler for non-rotating disksACTION=="add|change", KERNEL=="sd[a-z]", TEST!="queue/rotational", ATTR{queue/scheduler}="deadline"ACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="bfq"# set cfq scheduler for rotating disksACTION=="add|change", KERNEL=="sd[a-z]", ATTR{queue/rotational}=="1", ATTR{queue/scheduler}="bfq"ACTION=="add|change", KERNEL=="nvme[0-9]n1", ATTR{queue/rotational}=="0", ATTR{queue/scheduler}="bfq" 上面的配置是给固态硬盘使用deadline,给机械盘使用bfq,给nvme盘bfq。 本着电脑只有ssd,所以天不怕地不怕的原则,我选择全部使用bfq。 然后重启电脑,查看所有硬盘的调度器: # justforlxz @ archlinux in ~ [13:29:04]$ cat /sys/block/*/queue/schedulermq-deadline kyber [bfq] nonemq-deadline kyber [bfq] none 通过dmesg查看MuQSS是否开启: $ sudo dmesg | grep -i schedulerAlias tip: _ dmesg | grep -i scheduler[ 0.295872] rcu: RCU calculated value of scheduler-enlistment delay is 10 jiffies.[ 1.223982] io scheduler mq-deadline registered[ 1.223984] io scheduler kyber registered[ 1.224038] io scheduler bfq registered[ 1.586191] MuQSS CPU scheduler v0.193 by Con Kolivas. 总结MuQSS是BFS(脑残调度器)的进化版,主要是改进了BFS的O(n)复杂度,BFS适用于桌面环境用户,可以提供较好的进程切换和延迟。BFQ是针对硬盘的IO调度,它通过预先分配一定的IO吞吐量来合理安排每个进程的IO操作。我需要用几天来感受一下MuQSS和CFQ的好处。","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"使用webpack打包Vue和TypeScript","slug":"使用webpack打包Vue和TypeScript","date":"2019-10-22T07:20:08.000Z","updated":"2024-04-15T05:09:55.095Z","comments":true,"path":"2019/10/22/使用webpack打包Vue和TypeScript/","permalink":"https://blog.justforlxz.com/2019/10/22/%E4%BD%BF%E7%94%A8webpack%E6%89%93%E5%8C%85Vue%E5%92%8CTypeScript/","excerpt":"本文将会介绍如何通过Webpack将基于TypeScript的Vue项目进行打包。","text":"本文将会介绍如何通过Webpack将基于TypeScript的Vue项目进行打包。 webpack基础配置首先创建一个基本的webpack.config.js文件: const path = require( 'path' );module.exports = { entry: { index: "./src/index.ts", }, output: { path: path.resolve( __dirname, 'dist' ), publicPath: '/dist/', filename: '[name].js' }, devtool: 'inline-source-map', mode: 'development', module: { rules: [ ] }, resolve: { }}; 此时webpack只能将src/index.ts文件直接输出为index.js,我们需要添加typescript的loader,进行typescript的转换。 将以下代码加入rules节: { test: /\\.ts?$/, loader: 'ts-loader', exclude: /node_modules/,}, 通过ts-loader进行ts文件的转换,我们还需要创建typescript的一个配置文件。 添加typescript支持创建tsconfig.json { "compilerOptions": { "outDir": "./dist/", "sourceMap": true, "strict": true, "module": "commonjs", "moduleResolution": "node", "target": "es5", "skipLibCheck": true, "esModuleInterop": true, "experimentalDecorators": true }, "include": [ "./src/**/*" ]} 还需要在webpack的配置中添加ts文件,在resolve节中添加: extensions: [ '.ts', '.js' ], 我们指定ts转换出的js代码是es5的。 这个时候我们运行webpack,将会看到正常的转换输出。 Hash: c3a0ae2c47032de12eecVersion: webpack 4.41.0Time: 1880msBuilt at: 10/22/2019 3:40:59 PM Asset Size Chunks Chunk Namesindex.js 11.8 KiB index [emitted] indexEntrypoint index = index.js[./src/index.ts] 269 bytes {index} [built] 入口文件就是index.ts了,之后我们就正常的在index.ts中写我们的代码,webpack就会查找所有的依赖,并打包输出到index.js中。 添加Vue单文件的支持Vue单文件组件(SFC)规范是指在一个文件中,提供html、css和script代码,三者包含在顶级语言块 <template>、<script> 和 <style> 中,还允许添加可选的自定义块。 这是一个简单的vue单文件例子: <template> <div class="example">{{ msg }}</div></template><script>export default { data () { return { msg: 'Hello world!' } }}</script><style>.example { color: red;}</style><custom1> This could be e.g. documentation for the component.</custom1> 我们通过vue-loader来解析该文件,提取每一个语言块,如有需要,会传递给其他loader进行处理,最后组装为一个ES Module。 我们在webpack的rules节中添加vue-loader: { test: /\\.vue$/, loader: 'vue-loader', options: { loaders: { // Since sass-loader (weirdly) has SCSS as its default parse mode, we map // the "scss" and "sass" values for the lang attribute to the right configs here. // other preprocessors should work out of the box, no loader config like this necessary. 'scss': 'vue-style-loader!css-loader!sass-loader', 'sass': 'vue-style-loader!css-loader!sass-loader?indentedSyntax', } // other vue-loader options go here }}, 如果vue是typescript代码?其实这很简单,ts-loader有一个appendTsSuffixTo的功能,可以给某个文件增加.ts的后缀,从而识别这个文件为ts文件。 { test: /\\.tsx?$/, loader: 'ts-loader', exclude: /node_modules/, options: { appendTsSuffixTo: [/\\.vue$/], }}, 我们还需要在项目中添加一个vue-shim.d.ts来让ts正确的识别vue。 declare module '*.vue' { import Vue from 'vue' export default Vue} 还需要在webpack的resolve节追加vue的后缀: resolve: { extensions: [ '.tsx', '.ts', '.js' , '.vue'], alias: { 'vue': 'vue/dist/vue.js' }}, vue-loader现在需要手动处理一下插件,在webpack.config.js的头部导入vue-loader,并在plugins节创建对象。 const { VueLoaderPlugin } = require('vue-loader').......plugins: [ new VueLoaderPlugin()], 否则将不能正确工作。 此时已经完成了webpack+vue+typescript的全部工作。 Hash: 320d4ed3f55f52872694Version: webpack 4.41.0Time: 2494msBuilt at: 10/22/2019 4:00:50 PM Asset Size Chunks Chunk Names bundle.js 1.12 MiB bundle [emitted] bundleelectron.js 12.2 KiB electron [emitted] electron index.html 194 bytes [emitted]Entrypoint bundle = bundle.jsEntrypoint electron = electron.js[./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/sass-loader/dist/cjs.js!./node_modules/vue-loader/lib/index.js?!./src/app.vue?vue&type=style&index=0&id=5ef48958&rel=stylesheet%2Fscss&lang=scss&scoped=true&] ./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/sass-loader/dist/cjs.js!./node_modules/vue-loader/lib??vue-loader-options!./src/app.vue?vue&type=style&index=0&id=5ef48958&rel=stylesheet%2Fscss&lang=scss&scoped=true& 542 bytes {bundle} [built][./node_modules/ts-loader/index.js?!./node_modules/vue-loader/lib/index.js?!./src/Components/About.vue?vue&type=script&lang=ts&] ./node_modules/ts-loader??ref--1!./node_modules/vue-loader/lib??vue-loader-options!./src/Components/About.vue?vue&type=script&lang=ts& 305 bytes {bundle} [built][./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./src/Components/About.vue?vue&type=template&id=aa9c95a6&] ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./src/Components/About.vue?vue&type=template&id=aa9c95a6& 235 bytes {bundle} [built][./node_modules/vue-loader/lib/loaders/templateLoader.js?!./node_modules/vue-loader/lib/index.js?!./src/app.vue?vue&type=template&id=5ef48958&scoped=true&] ./node_modules/vue-loader/lib/loaders/templateLoader.js??vue-loader-options!./node_modules/vue-loader/lib??vue-loader-options!./src/app.vue?vue&type=template&id=5ef48958&scoped=true& 589 bytes {bundle} [built][./node_modules/vue-style-loader/index.js!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/sass-loader/dist/cjs.js!./node_modules/vue-loader/lib/index.js?!./src/app.vue?vue&type=style&index=0&id=5ef48958&rel=stylesheet%2Fscss&lang=scss&scoped=true&] ./node_modules/vue-style-loader!./node_modules/css-loader/dist/cjs.js!./node_modules/vue-loader/lib/loaders/stylePostLoader.js!./node_modules/sass-loader/dist/cjs.js!./node_modules/vue-loader/lib??vue-loader-options!./src/app.vue?vue&type=style&index=0&id=5ef48958&rel=stylesheet%2Fscss&lang=scss&scoped=true& 1.64 KiB {bundle} [built][./src/Components/About.vue] 1.06 KiB {bundle} [built][./src/Components/About.vue?vue&type=script&lang=ts&] 350 bytes {bundle} [built][./src/Components/About.vue?vue&type=template&id=aa9c95a6&] 203 bytes {bundle} [built][./src/app.vue] 1.08 KiB {bundle} [built][./src/app.vue?vue&type=style&index=0&id=5ef48958&rel=stylesheet%2Fscss&lang=scss&scoped=true&] 716 bytes {bundle} [built][./src/app.vue?vue&type=template&id=5ef48958&scoped=true&] 207 bytes {bundle} [built][./src/entry.ts] 538 bytes {bundle} [built][./src/main.ts] 1.11 KiB {electron} [built][./src/route.ts] 1.35 KiB {bundle} [built]","categories":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/categories/Web/"}],"tags":[{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/tags/Vue/"},{"name":"Webpack","slug":"Webpack","permalink":"https://blog.justforlxz.com/tags/Webpack/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://blog.justforlxz.com/tags/TypeScript/"}]},{"title":"webpack入门","slug":"webpack入门","date":"2019-10-14T07:34:52.000Z","updated":"2024-04-15T05:09:55.093Z","comments":true,"path":"2019/10/14/webpack入门/","permalink":"https://blog.justforlxz.com/2019/10/14/webpack%E5%85%A5%E9%97%A8/","excerpt":"现在前端开发不像以前一样,只需要写html、css和javascript文件就可以了。现代前端开发讲究工程化。 什么是工程化? 工程化即系统化、模块化、规范化的一个过程。 为什么要工程化? 工程化是让开发、测试和维护都变得更加可靠和提高效率的方式。 制定规范 版本管理 单元测试 自动化 通过制定流程的方式,规范了开发和测试的流程,让工作有章可循,方便团队协作。","text":"现在前端开发不像以前一样,只需要写html、css和javascript文件就可以了。现代前端开发讲究工程化。 什么是工程化? 工程化即系统化、模块化、规范化的一个过程。 为什么要工程化? 工程化是让开发、测试和维护都变得更加可靠和提高效率的方式。 制定规范 版本管理 单元测试 自动化 通过制定流程的方式,规范了开发和测试的流程,让工作有章可循,方便团队协作。 最初的网页开发,是写好几份的javascript代码和css文件,手动在html中引入的。这样不适合多人协作开发,一旦开发人员多了,不可避免的会造成文件和命名冲突。为了避免这些事情的发生,javascript增加了模块的概念。 有好的事情出现,就会有坏的事情发生。 过多的模块导致js文件下载很慢,而且有冗余,为了避免这件事情影响用户体验,webpack横空出世了。 webpack是一个现代javascript的静态模块打包器。它会递归的构建出依赖图,并根据依赖图来输出应用程序需要的每个模块,然后将所有这些模块打包成一个或多个 bundle。 webpack有四个核心概念: 入口(entry) 输出(output) loader 插件 入口决定了webpack要从哪个文件开始构建依赖图。 看一个简单的例子: module.exports = { entry: './src/index.js'} output则决定了webpack会在哪里输出生成的bundles,以及如何命名这些bundles。输出目录默认为 ./dist/ 。 const path = require('path');module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }} loader可以让webpack打包非javascript文件,loader可以将所有类型的文件转换为webpack可以识别的有效模块,然后利用webpack的打包能力,对他们进行处理。 const path = require('path');module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, module: { rules: { test: /\\.css$/, use: 'css-loader' } }} rules中的意思是,当require()/impot中被解析为.css的路径时,先使用css-loader转换一下。 我们可以开发新的loader去加载不同的文件,最终都通过webpack来打包到一起。 loader用于转换某些类型的模块,插件则工作的更加广泛。插件的范围包括,从打包优化和压缩,一直到重新定义环境中的变量。插件接口功能极其强大,可以用来处理各种各样的任务。 想要使用一个插件,你只需要 require() 它,然后把它添加到 plugins 数组中。多数插件可以通过选项(option)自定义。你也可以在一个配置文件中因为不同目的而多次使用同一个插件,这时需要通过使用 new 操作符来创建它的一个实例。 const path = require('path');const HtmlWebpackPlugin = require('html-webpack-plugin'); // 通过 npm 安装const webpack = require('webpack');module.exports = { entry: './src/index.js', output: { path: path.resolve(__dirname, 'dist'), filename: 'bundle.js' }, module: { rules: { test: /\\.css$/, use: 'css-loader' } }, plugins: [ new HtmlWebpackPlugin({template: './src/index.html'}) ]} 总结通过webpack,我们可以将整个项目都打包为一个文件进行分发,而且还可以进行优化。webpack的出现,将前端的开发和发布彻底的分离开,开发人员可以以各种方式进行开发,通过webpack打包以后输出部署需要的文件。","categories":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/categories/Web/"},{"name":"webpack","slug":"Web/webpack","permalink":"https://blog.justforlxz.com/categories/Web/webpack/"}],"tags":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/tags/Web/"}]},{"title":"wsl2的使用体验","slug":"wsl2的使用体验","date":"2019-06-16T04:59:59.000Z","updated":"2024-04-15T05:09:55.093Z","comments":true,"path":"2019/06/16/wsl2的使用体验/","permalink":"https://blog.justforlxz.com/2019/06/16/wsl2%E7%9A%84%E4%BD%BF%E7%94%A8%E4%BD%93%E9%AA%8C/","excerpt":"wsl2已经是虚拟机平台了。","text":"wsl2已经是虚拟机平台了。 需要Windows版本在18917及以上,先开启虚拟机平台才能继续,在管理员权限的powershell中执行 Enable-WindowsOptionalFeature -Online -FeatureName VirtualMachinePlatform 对已安装的wsl1进行转换 wsl --set-version <Distro> 2 Distro可以通过 wsl --list 查看。 转换需要点时间,完成以后就可以浪起来了。 wsl2新增了一些参数: wsl --set-version <Distro> <Version> 可以设置某个wsl的版本,1是旧版,2是新版。 wsl --set-default-version <Version> 设置默认的wsl版本,推荐设置一下。 wsl --shutdown 出于某些目的,比如已经完成了任务,不再需要wsl工作在后台,可以手动关闭。 wsl --list --quiet 仅列出分发名称,此命令对于脚本编写很有用,因为它只会输出您已安装的发行版的名称,而不显示其他信息,如默认发行版,版本等。 wsl --list --verbose 显示有关所有分发的详细信息。此命令列出每个发行版的名称,发行版所处的状态以及正在运行的版本。它还显示哪些分发是默认的星号。 当一切准备就绪,我就安装了docker,测试一波。 sudo apt install docker-ce 添加用户到docker组。 sudo usermod -aG docker $USER sudo service docker --full-restart 已经可以跑docker了,我们来做个测试,请出万能的hello world! docker pull hello-world docker run --rm hello-world 然后就看到了想要的结果,hello world成功的跑起来了。 Hello from Docker!This message shows that your installation appears to be working correctly.To generate this message, Docker took the following steps: 1. The Docker client contacted the Docker daemon. 2. The Docker daemon pulled the "hello-world" image from the Docker Hub. (amd64) 3. The Docker daemon created a new container from that image which runs the executable that produces the output you are currently reading. 4. The Docker daemon streamed that output to the Docker client, which sent it to your terminal.To try something more ambitious, you can run an Ubuntu container with: $ docker run -it ubuntu bashShare images, automate workflows, and more with a free Docker ID: https://hub.docker.com/For more examples and ideas, visit: https://docs.docker.com/get-started/ 总的来说,因为wsl2改成虚拟机方案了,不过wsl2的启动速度还是挺快的,微软也努力让wsl2和wsl1之间在使用上没有差异。目前微软还没有完成wsl2的网络部分,wsl2和宿主机之间还需要使用专门的ip进行访问,等微软完成wsl2的localhost网络以后,就可以像以前一样直接跑一些网站或者需要端口的服务了。 来一张合照 点我查看wsl2的发布说明 点我查看如何安装wsl2","categories":[],"tags":[{"name":"Windows","slug":"Windows","permalink":"https://blog.justforlxz.com/tags/Windows/"}]},{"title":"入坑typescript了","slug":"入坑typescript了","date":"2019-06-15T15:41:49.000Z","updated":"2024-04-15T05:09:55.095Z","comments":true,"path":"2019/06/15/入坑typescript了/","permalink":"https://blog.justforlxz.com/2019/06/15/%E5%85%A5%E5%9D%91typescript%E4%BA%86/","excerpt":"今天算是正式入坑 typescript 了,基于 vue 写了第一个函数,用来做一个文字效果。 演示效果:","text":"今天算是正式入坑 typescript 了,基于 vue 写了第一个函数,用来做一个文字效果。 演示效果: 整体思路听简单的,就是用定时器和延时器来做,通过定时器来间隔的处理文本,延时器来延后所有的方法。 class TextHandle { private allDuration: number = 0; public appendText(text: string) : void { setTimeout(() = >{ const LeftMessage = document.getElementById('LeftMessage'); if (LeftMessage === null) { return; } let index: number = 0; const MessageText: string = LeftMessage.innerText; // tslint:disable-next-line:only-arrow-functions const interval = setInterval(function() { if ((LeftMessage === null) || (index++ === text.length + 1)) { return clearInterval(interval); } LeftMessage.innerText = MessageText + text.substring(0, index); }, 300); }, this.AllDuration); this.AllDuration += text.length * 300 + 100; }} typescript确实挺不错的,平时都在写静态语言,如C++,所以当我开始学动态语言的时候,就会觉得水土不服,现在通过typescript就可以让我继续使用静态语言的开发方式来写web,而且代码更容易理解。","categories":[],"tags":[{"name":"typescript","slug":"typescript","permalink":"https://blog.justforlxz.com/tags/typescript/"}]},{"title":"CMake CTests for dde-control-center","slug":"CMake-CTests-for-dde-control-center","date":"2019-05-23T09:16:15.000Z","updated":"2024-04-15T05:09:54.912Z","comments":true,"path":"2019/05/23/CMake-CTests-for-dde-control-center/","permalink":"https://blog.justforlxz.com/2019/05/23/CMake-CTests-for-dde-control-center/","excerpt":"什么是单元测试? 在计算机编程中,单元测试又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。 单元测试存在的意义在于,如果程序发生了异常情况,比如接收了错误的值,从而导致结果不正确,当修正程序中的错误后,为了避免再次遇到这个问题,需要对出问题的值和函数/功能进行一次测试,确保结果符合预期。 单元测试很重要,如果是新项目,请一定要刚开始就规划好单元测试。 为什么说单元测试很重要呢?因为单元测试的目的是隔离其他单元,并证明当前单元是正确的。这需要开发者在设计程序的时候就要考虑很多,合理的设计和规划项目。当未来重构项目的时候,可以局部重构来优化项目,而不是从零重写。 本文没有详细说明Qt的单元测试是如何编写的,编写Qt的单元测试放在以后再写(咕咕咕)。","text":"什么是单元测试? 在计算机编程中,单元测试又称为模块测试,是针对程序模块(软件设计的最小单位)来进行正确性检验的测试工作。 单元测试存在的意义在于,如果程序发生了异常情况,比如接收了错误的值,从而导致结果不正确,当修正程序中的错误后,为了避免再次遇到这个问题,需要对出问题的值和函数/功能进行一次测试,确保结果符合预期。 单元测试很重要,如果是新项目,请一定要刚开始就规划好单元测试。 为什么说单元测试很重要呢?因为单元测试的目的是隔离其他单元,并证明当前单元是正确的。这需要开发者在设计程序的时候就要考虑很多,合理的设计和规划项目。当未来重构项目的时候,可以局部重构来优化项目,而不是从零重写。 本文没有详细说明Qt的单元测试是如何编写的,编写Qt的单元测试放在以后再写(咕咕咕)。 写这篇文章是因为最近在给控制中心写单元测试,控制中心的模块都是MVC的,本身就做好了大方向的隔离,每个函数也基本是拆分出来的最小功能,可以单独拿出来测试。控制中心目前存在一个问题,Worker类是从DBus上接收数据,处理完成后放入Model中,如果测试Worker类,需要做很多和DBus相关的处理,比较麻烦,所以最开始我先把重心放在了创建Tests和测试一个基本的转换函数的功能,验证单元测试的流程。 控制中心单元测试PR 控制中心项目使用的CMake作为项目构建工具,所以用到了CTests,控制中心使用的Qt进行的开发,Qt也提供了自己的单元测试,我两个都做了支持。 在顶层的CMakeLists.txt中添加CTests的支持: # 启用CTest检查include(Dart)# 启用CTestinclude(CTest) 这两行内容需要在顶层CMakeLists.txt中添加,不然不会生效。 在子项目中创建一个dcc_test.h,用来写单元测试的类。 #ifndef DCC_TEST_H#define DCC_TEST_H#include <QMap>#include <QString>#include <QTest>#include "modules/display/displaywidget.h"namespace Tests {class Tests : public QObject { Q_OBJECTprivate Q_SLOTS: void testSliderValue_data() { QTest::addColumn<float>("value"); QTest::addColumn<int>("result"); QMap<float, int> testMap{ { 1.0, 1 }, { 1.25, 2 }, { 1.5, 3 }, { 1.75, 4 }, { 2.0, 5 }, { 2.25, 6 }, { 2.5, 7 }, { 2.75, 8 }, { 3.0, 9 } }; for (auto it = testMap.constBegin(); it != testMap.constEnd(); ++it) { QTest::newRow("converToSlider") << it.key() << it.value(); } } void testSliderValue() { QFETCH(float, value); QFETCH(int, result); using namespace dcc::display; QCOMPARE(DisplayWidget::convertToSlider(value), result); QCOMPARE(DisplayWidget::convertToScale(result), value); }};} // namespace TestsQTEST_MAIN(Tests::Tests)#endif // !DCC_TEST_H 在子项目的CMakeLists.txt中添加一个二进制,用来当作单元测试程序。 # 这个宏是Dart提供的,用来判断是否开启CTestif(BUILD_TESTING)find_package(Qt5 COMPONENTS TestREQUIRED)set(Qt_LIBS ${Qt_LIBS} Qt5::Test)set(TEST_SRCStests/dcc_test.h${DISPLAY_FILES}${WIDGETS_FILES}${MODULE_FILES})# 添加一个叫unit-test的二进制add_executable(unit-test${TEST_SRCS}${PROJECT_BINARY_DIR})target_include_directories(unit-test PUBLIC${TEST_SRCS}${PROJECT_BINARY_DIR}${DFrameworkDBus_INCLUDE_DIRS}${QGSettings_INCLUDE_DIRS}${Qt5Gui_PRIVATE_INCLUDE_DIRS})target_link_libraries(unit-test PRIVATE${Qt_LIBS}${DFrameworkDBus_LIBRARIES}${QGSettings_LIBRARIES}${DtkWidget_LIBRARIES}${XCB_EWMH_LIBRARIES}) 到这里,直接编译启动unit-test就可以使用Qt的单元测试了,但是加上CTest的支持只需要一行: add_test(ctest unit-test)endif() 使用ctest -j6 -C Debug -T test –output-on-failure跑CTest,得到执行结果: [ctest] Site: xiaomi-air[ctest] Build name: Linux-c++[ctest] Test project /home/justforlxz/Projects/Deepin/dde-control-center/build[ctest] Start 1: ctest[ctest] 1/1 Test #1: ctest ............................ Passed 0.05 sec[ctest][ctest] 100% tests passed, 0 tests failed out of 1[ctest][ctest] Total Test time (real) = 0.06 sec[ctest] CTest finished with return code 0 如果是跑unit-test二进制,则会得到Qt打印的相关信息: ********* Start testing of Tests::Tests *********Config: Using QtTest library 5.12.3, Qt 5.12.3 (x86_64-little_endian-lp64 shared (dynamic) release build; by GCC 8.3.0)PASS : Tests::Tests::initTestCase()PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::testSliderValue(converToSlider)PASS : Tests::Tests::cleanupTestCase()Totals: 11 passed, 0 failed, 0 skipped, 0 blacklisted, 0ms********* Finished testing of Tests::Tests ********* 对比CTest和Qt的单元测试,Qt会告诉你详细的函数调用和执行过程,CTest更注重结果,不过在Qtcreator的单元测试面板中,会看到更好的输出。 说到底,CTest支持启动了一个带有单元测试的程序,而程序自己使用了Qt提供的单元测试类进行测试。","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"}],"tags":[{"name":"CMake Linux","slug":"CMake-Linux","permalink":"https://blog.justforlxz.com/tags/CMake-Linux/"}]},{"title":"如何在Deepin上使用LNMP","slug":"how-to-use-LNMP-on-deepin","date":"2019-02-21T02:11:15.000Z","updated":"2024-04-15T05:09:54.994Z","comments":true,"path":"2019/02/21/how-to-use-LNMP-on-deepin/","permalink":"https://blog.justforlxz.com/2019/02/21/how-to-use-LNMP-on-deepin/","excerpt":"为了节省读者的时间,我先简述一下阅读这篇文章需要了解的知识。 这篇文章将基于Docker来构建nginx、php和mysql来搭建LNMP环境,和其他教程有所不同的是,需要有一定的Docker基础。","text":"为了节省读者的时间,我先简述一下阅读这篇文章需要了解的知识。 这篇文章将基于Docker来构建nginx、php和mysql来搭建LNMP环境,和其他教程有所不同的是,需要有一定的Docker基础。 Docker是一个不错的工具,使我们不需要虚拟机那样的庞然大物就可以轻松的隔离运行的程序,这要感谢Linux的资源分离机制,避免启动一个虚拟机造成了大量资源浪费。 首先需要在Deepin上安装Docker,添加Docker的deb仓库,并安装docker-ce。 创建文件 sudo nano /etc/apt/sources.list.d/docker.list 写入 deb [arch=amd64] https://download.docker.com/linux/debian jessie edge 刷新一下仓库就可以安装了。 sudo apt update && sudo apt install docker-ce docker-compose 安装完成后重启一下系统,准备工作就算完成了一半了。 在家目录创建一个Projects目录,当做我们LNMP的工作目录,创建一个名叫docker-compose.yaml的文件,这是docker-compose的配置文件,我们通过docker-compose这个工具来管理我们的Docker容器。 所有的镜像均采用最新版本,nginx(1.15.8),php(7.3.2),mysql(8.0.15),如有需要,自行选择不同版本的镜像。 注意PHP7已经不支持mysql扩展,使用内置的MySQLnd。 写入以下配置文件: version: '3'services: nginx: # 设置容器名字 container_name: "nginx" # 采用最新的nginx image: nginx:latest # 绑定80端口 ports: - "80:80" # 添加php容器的依赖 depends_on: - "php" # 绑定数据目录 volumes: - "./volumes/nginx/conf.d:/etc/nginx/conf.d" - "./volumes/html:/usr/share/nginx/html" restart: always php: # 设置容器名字 container_name: "php" # 采用最新的php image: php:fpm # 绑定端口 ports: - "9000:9000" # 绑定数据目录 volumes: - "./volumes/html:/var/www/html" restart: always mysql: # 设置容器名字 container_name: "mysql" # 采用最新的mysql image: mysql:latest # 绑定端口 ports: - "3306:3306" # 设置环境变量 environment: - MYSQL_ROOT_PASSWORD=(自己设置密码) # 绑定数据目录 volumes: - "./volumes/mysql:/var/lib/mysql" restart: always 创建nginx的配置文件,编辑 ./volumes/nginx/conf.d/nginx.conf : server { listen 80; server_name localhost; location / { root /usr/share/nginx/html; index index.html index.htm index.php; } error_page 500 502 503 504 /50x.html; location = /50x.html { root /usr/share/nginx/html; } location ~ \\.php$ { fastcgi_pass php:9000; fastcgi_index index.php; fastcgi_param SCRIPT_FILENAME /var/www/html/$fastcgi_script_name; include fastcgi_params; }} 创建php测试文件,编辑 ./volumes/html/index.php : <?phpphpinfo();?> 启动docker,第一次需要拉取一下镜像: docker-compose up --build -d 等全部结束以后,就可以访问localhost看到php的信息了。 通过Docker的方法来使用LNMP,不污染宿主机环境,不会再因为各种依赖问题而搞坏系统,这恰恰是新手容易犯的错误,使用Docker,方便你我。","categories":[{"name":"Solution","slug":"Solution","permalink":"https://blog.justforlxz.com/categories/Solution/"}],"tags":[{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/tags/Web/"},{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"LNMP","slug":"LNMP","permalink":"https://blog.justforlxz.com/tags/LNMP/"},{"name":"Deepin","slug":"Deepin","permalink":"https://blog.justforlxz.com/tags/Deepin/"}],"author":"Lorem Ipsum"},{"title":"解决用了xposed后淘宝闪退","slug":"fuck-taobao","date":"2019-01-23T02:27:52.000Z","updated":"2024-04-15T05:09:54.966Z","comments":true,"path":"2019/01/23/fuck-taobao/","permalink":"https://blog.justforlxz.com/2019/01/23/fuck-taobao/","excerpt":"","text":"反正都用xposed了,肯定也有root权限。删除/data/data/com.taobao.taobao/files/bundleBaseline/里的文件,然后设置该目录为500。","categories":[],"tags":[]},{"title":"使用swapfile来休眠","slug":"hibernate-for-swapfile","date":"2018-12-12T03:01:55.000Z","updated":"2024-04-15T05:09:54.994Z","comments":true,"path":"2018/12/12/hibernate-for-swapfile/","permalink":"https://blog.justforlxz.com/2018/12/12/hibernate-for-swapfile/","excerpt":"最近deepin要添加休眠功能,但是之前测试的通过swapfile来休眠失败了,所以对正在使用swap分区的用户提供休眠功能。但是昨天我在askubuntu上看到有人发了在ubuntu下通过swapfile休眠的方案,今天试了一下,效果良好,觉得可以考虑给deepin也加上这样的功能。","text":"最近deepin要添加休眠功能,但是之前测试的通过swapfile来休眠失败了,所以对正在使用swap分区的用户提供休眠功能。但是昨天我在askubuntu上看到有人发了在ubuntu下通过swapfile休眠的方案,今天试了一下,效果良好,觉得可以考虑给deepin也加上这样的功能。 原文链接: Hibernate and resume from a swap file 具体步骤是通过uswsusp这个包来做的,uswsusp是一组用户空间工具,用于Linux系统上的休眠(挂起到磁盘)和挂起(挂起到RAM或待机)。详细内容可以在ArchWiki上参考。点这里 先创建一个和内存同等大小的swapfile,为了确保休眠成功,不能小于内存的容量。 sudo fallocate -l 16g /swapfile # 我的机子是16G,具体自己修改sudo chmod 600 /swapfilesudo mkswap /swapfilesudo swapon /swapfileecho '/swapfile swap swap defaults 0 0' | sudo tee -a /etc/fstab 安装用户空间软休眠(Userspace Software Suspend)包 sudo apt install uswsusp 创建需要的配置文件,只需要创建文件即可。 sudo touch /etc/uswsusp.confsudo dpkg-reconfigure -pmedium uswsusp 这时候终端会提醒是否继续,选择Yes,然后会要求你创建一个密码,设置一个密码继续即可。 此时就可以测试一下功能了,不过我是跳过这个步骤了(比较喜欢作死)。 修改systemd的hibernate服务,使用uswsusp来代替systemd的功能。 sudo systemctl edit systemd-hibernate.service 写入以下内容: [Service]ExecStart=ExecStart=/usr/sbin/s2diskExecStartPost=/bin/run-parts -a post /lib/systemd/system-sleep 这时候可以使用systemd的命令来测试的,我表示工作的非常正常。 systemctl hibernate 执行以后可以看到屏幕上会打印当前保存的进度,然后设备就关机了,此时再开机,等待一会儿以后就看到了背景是我漂亮老婆的锁屏,解锁以后看到工作区还是执行命令前的,一切ok。 参考以下内容: https://askubuntu.com/questions/6769/hibernate-and-resume-from-a-swap-file https://wiki.archlinux.org/index.php/Uswsusp","categories":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"}],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"C++快速排序","slug":"quick-sort-for-cpp","date":"2018-11-11T08:57:44.000Z","updated":"2024-04-15T05:09:55.027Z","comments":true,"path":"2018/11/11/quick-sort-for-cpp/","permalink":"https://blog.justforlxz.com/2018/11/11/quick-sort-for-cpp/","excerpt":"快速排序是基于分治思想的排序算法,通过这种策略把列表分为两个子列,重复该过程。是由东尼·霍尔提出,在平均状况下,排序N个数据要O(nlogn)次比较,在最坏情况下则需要O(n^2),但退化成冒泡的情况比较少见,快速排序比其他排序算法通常情况是最佳的,因为内部使用的循环在很多平台都有优化。","text":"快速排序是基于分治思想的排序算法,通过这种策略把列表分为两个子列,重复该过程。是由东尼·霍尔提出,在平均状况下,排序N个数据要O(nlogn)次比较,在最坏情况下则需要O(n^2),但退化成冒泡的情况比较少见,快速排序比其他排序算法通常情况是最佳的,因为内部使用的循环在很多平台都有优化。 快速排序的步骤很简单: 选择一个基准 遍历列表,将小于基准的放在列表左边,大于基准的放在列表右边 递归这个操作 在维基百科上的这张图可以很直观的展示快速排序的过程。 代码实现: 首先需要一个返回基准的函数,该函数负责从指定的范围中挑选一个位置作为基准,并对范围内列表进行排序,并返回基准所在的位置。 int Division(int a[], int left, int right) { int base = a[left]; // 取第一个数为基准 while (left < right) { while (left < right && a[left] > base) { // 从右向左找第一个比基准小的元素 --right; } a[left] = a[right]; // 交换位置,把小元素放在左侧 while (left < right && a[left] < base) { // 从左向右找第一个比基准大的元素 ++left; } a[right] = a[left]; // 交换位置,把大元素放在右侧 } a[left] = base; return left;} Division函数只做了最简单的事,找一个基准,并交换左右的元素,使列表左侧均小于基准元素,使右侧均大于基准元素,接下来需要一个函数,使列表趋向最小,直至列表元素剩一(这里我感觉其实有点极限的思想)。 void quick_sort(int a[], int left, int right) { if (left < right) { int index = Division(a, left, right); //对列表进行分割 quick_sort(a, left, i -1); //对左侧进行排序 quick_sort(a, i + 1, right); //对右侧进行排序 }} 配合上方的gif,就可以很清楚的了解快速排序是如何使用分治法来排序的,通过将大任务拆分成小任务,最终达成完整的排序.","categories":[],"tags":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"},{"name":"Program","slug":"Program","permalink":"https://blog.justforlxz.com/tags/Program/"}]},{"title":"使用Google日历安排工作任务","slug":"use-google-calendar-to-finishe-work","date":"2018-11-09T13:15:45.000Z","updated":"2024-04-15T05:09:55.039Z","comments":true,"path":"2018/11/09/use-google-calendar-to-finishe-work/","permalink":"https://blog.justforlxz.com/2018/11/09/use-google-calendar-to-finishe-work/","excerpt":"目前我们正在尝试把工作的分配和讨论放在github上进行,这样可以使我们的用户和开发者更容易接触到我们,可以提bug和对需求进行讨。 但是使用起来还是有些不便,比如使用tower进行任务分配的时候,可以方便的移动一个任务到某个分类,或者指派一个时间。但是github上是基于issue的,并不是为了做这种事来设计的,所以需求上有一些出入。但是@hualet大佬根据github的api写了一个bot来做一点微小的事,当一个issue的assignees只剩QA的同事时,issue会被bot移动到测试栏中,只剩一个开发同事时(基本上是负责该任务的开发者),会被移动到开发栏中。 但是因为不能做到比如今天、明天、下周等时间的显示,所以任务只能通过每天开会来口头告知时间,但是这并不妨碍我进行自己的任务时间安排。请出世界第一的神器(日历)。","text":"目前我们正在尝试把工作的分配和讨论放在github上进行,这样可以使我们的用户和开发者更容易接触到我们,可以提bug和对需求进行讨。 但是使用起来还是有些不便,比如使用tower进行任务分配的时候,可以方便的移动一个任务到某个分类,或者指派一个时间。但是github上是基于issue的,并不是为了做这种事来设计的,所以需求上有一些出入。但是@hualet大佬根据github的api写了一个bot来做一点微小的事,当一个issue的assignees只剩QA的同事时,issue会被bot移动到测试栏中,只剩一个开发同事时(基本上是负责该任务的开发者),会被移动到开发栏中。 但是因为不能做到比如今天、明天、下周等时间的显示,所以任务只能通过每天开会来口头告知时间,但是这并不妨碍我进行自己的任务时间安排。请出世界第一的神器(日历)。 我选择使用谷歌日历,才不是因为它有网页还有安卓客户端【哼 谷歌日历上支持新建三种类型,分别是活动、提醒和任务。活动是开始时间明确,但是结束时间未知的类型,适合用作对时间不严格的情况。提醒则是在活动的基础上添加了提供功能,在活动即将开始时发送通知提醒。任务则是熟悉的ToDoList,适合用来分配今天一定要做,但是时间未知的事。 我添加了每天的开会提醒,再开完会以后,我会把身上的新任务创建成task,然后再添加大概的活动来确定一下要完成的task。把今天没有时间做的task移动到明天,留在当天的task尽量要当天完成,可以得到今天的任务列表和延期列表,让我对要做的事有完整的控制。 谷歌日历的日视图和周视图会显示一条线,告诉你现在的时间,应该进行什么活动了。 在手机上需要使用两个app,Google calendar和Google task,活动和提醒需要calendar,task则需要单独使用一个app,只有网页上才是整合的。 因为我也是才开始用日历来分配任务的时间,所以记录的内容并不多,我也在摸索如何使用这些功能,但是我觉得使用日历来记录和管理时间是非常不错的一件事,我可以通过看某天的活动来回忆当天所做的事,也可以根据记录的内容来分析自己在某些任务上使用了多少的时间。","categories":[],"tags":[]},{"title":"把博客转移到coding","slug":"hexo page move to coding","date":"2018-11-09T12:17:32.000Z","updated":"2024-04-15T05:09:54.994Z","comments":true,"path":"2018/11/09/hexo page move to coding/","permalink":"https://blog.justforlxz.com/2018/11/09/hexo%20page%20move%20to%20coding/","excerpt":"上周末折腾黑果子的时候,不小心被果子坑爹的磁盘管理坑了,整个home被直接改成HFS+了,本来是打算分配一个空闲分区出来的,当我新建分区以后,从空间分区开始到home,分区全部都变成HFS+了,但是… 空闲分区新建失败,提示我磁盘空间不足,我就重启进deepin打算直接新建一个算了,然后就GG几率了。在windows下看到home已经成果子的文件系统了,然后我用arch的安装盘看了一下,已经无法重新挂载了(成功GG),然后数据就都没了。 还好我的数据在公司还有一份,私钥也都在,经过一星期的努力复制,大部分数据都恢复了,不过topbar的新功能代码是彻底没了,周五晚上太自信了,没有提交到gayhub上(猛叹气)。 我们现在正在尝试把日常工作转向github的project和看板,每天早上开一下晨会,简单分配一下任务,开完会以后我会把自己的任务写在谷歌日历和task上,然后安排一下任务的先后顺序,我准备把自己的一些做法写到博客上,但是home已经不在了,所以我要先恢复我的博客,刚好国内有人说我博客访问的很慢,我打算国内解析到coding,国外解析到github。","text":"上周末折腾黑果子的时候,不小心被果子坑爹的磁盘管理坑了,整个home被直接改成HFS+了,本来是打算分配一个空闲分区出来的,当我新建分区以后,从空间分区开始到home,分区全部都变成HFS+了,但是… 空闲分区新建失败,提示我磁盘空间不足,我就重启进deepin打算直接新建一个算了,然后就GG几率了。在windows下看到home已经成果子的文件系统了,然后我用arch的安装盘看了一下,已经无法重新挂载了(成功GG),然后数据就都没了。 还好我的数据在公司还有一份,私钥也都在,经过一星期的努力复制,大部分数据都恢复了,不过topbar的新功能代码是彻底没了,周五晚上太自信了,没有提交到gayhub上(猛叹气)。 我们现在正在尝试把日常工作转向github的project和看板,每天早上开一下晨会,简单分配一下任务,开完会以后我会把自己的任务写在谷歌日历和task上,然后安排一下任务的先后顺序,我准备把自己的一些做法写到博客上,但是home已经不在了,所以我要先恢复我的博客,刚好国内有人说我博客访问的很慢,我打算国内解析到coding,国外解析到github。 首先,创建新的博客目录,用来拉取旧的数据。 mkdir blog && cd blog 初始化git目录。 git init 添加远程仓库。 git remote add origin 你的博客git地址 取回origin的backup分支,和本地master合并。因为hexo-git-backup插件只支持master,但是coding只支持master部署page服务,所以需要使用其他分支。 git pull origin backup:master 拉取了代码以后,我们需要做点其他设置,首先设置上游分支。 git branch --set-upstream-to=origin/backup master 设置git的默认push策略,可以参考thekaiway的文章。 git config push.default upstream 然后添加coding的git地址。 git remote add coding 你的git地址 之后就正常使用了,通过npm安装hexo,再安装需要的插件,最后完成了在一台新电脑上恢复hexo博客。","categories":[],"tags":[]},{"title":"智能指针","slug":"cpp-smart-pointer","date":"2018-08-29T01:43:17.000Z","updated":"2024-04-15T05:09:54.958Z","comments":true,"path":"2018/08/29/cpp-smart-pointer/","permalink":"https://blog.justforlxz.com/2018/08/29/cpp-smart-pointer/","excerpt":"其实一直都对智能指针的应用场景不清楚,项目中也很少用到,今天在 @zccrs 大佬的帮助下,大概理解了智能指针的作用和应用场景。","text":"其实一直都对智能指针的应用场景不清楚,项目中也很少用到,今天在 @zccrs 大佬的帮助下,大概理解了智能指针的作用和应用场景。 设计思想智能指针依赖一种叫引用计数的手段来协助管理对象指针,通过引用计数为0时删除对象指针来完成内存的释放,本质上是通过栈对象来管理堆对象的一种方法。 传统做法void test() { Test* t = new Test; ... if (...) { throw exception(); } delete t;} 当出现异常时,delete将不会被执行到,t也就泄露了。虽然我们可以在异常那里把delete给加上,但是在较为大型的项目中,如果对代码进行review来排查这种错误,将会是非常麻烦的一件事,所以为了避免内存泄漏,发明了基于引用技术的智能指针。 智能指针做法void test() { std::unique_ptr<Test> t(new Test); ... if (...) { throw exception(); }} 如果不关心std::unique_ptr是什么,这段代码无意是糟糕的,new出来的Test对象根本没有地方被删除,内存泄露了。 但是不必担心,指针已经由std::unique_ptr来管理了,根本不会发生内存泄漏,对象将在离开函数作用域以后被删除。 这就是智能指针的方便之处。 智能指针的基本实现智能指针都通过模板编程来实现,模板是C++的另一大功能,可以使我们更关心实现而不需要关心具体的对象,通过更加抽象的方式来编写程序。 智能指针有两层,里层用来保存对象的指针和引用计数,外层用来调用里层来控制引用计数。 里层的辅助类 template<typename T>class P_ptr { private: friend class Pointer<T>; P_ptr(T t) : pointer(t) , count(1) { } uint count; T pointer;} 外层的控制类 template<typename T>class Pointer { public: Pointer(T t) : m_ptr(new P_ptr(t)) { } Pointer(const Pointer &pointer) : m_ptr(pointer.m_ptr) { ++m_ptr->count; } Pointer& operator=(const Pointer &pointer) { ++pointer->count; if (--m_ptr->count == 0) { // 应对自赋值 delete m_ptr; } m_ptr = pointer->m_ptr; return *this; } ~Pointer() { if (--m_ptr->count == 0) { delete m_ptr; } } private: P_ptr m_ptr;} 通过重写控制类的拷贝构造函数和赋值运算符重载来更新引用计数。 使用实例 void test() { Pointer<Test> t(new Test); // 引用计数目前是1 Pointer<Test> t1 = t; // t的引用计数是2,t1的引用计数也是2}// 离开作用域,t被删除,引用计数是1. t1被删除,引用计数为0,Test被删除,内存没有泄露。 这样我们就有一个简单的智能指针了,不过他还存在一些问题,比如循环引用导致内存泄漏,没有->和*的操作运算符等。所以我们需要更强大的智能指针来帮助我们。 几种智能指针的介绍标准库提供了几个针对不同方面使用的智能指针,以满足我们的需求。 unique_ptr 只允许一个所有者,除非确信你需要共享该指针,则应该使用shared_ptr。可以转移到新的所有者,但是不会复制和共享。 shared_ptr 采用引用计数的智能指针,如果你想将一个原始指针分配给多个所有者,请使用该智能指针,直到shared_ptr所有者超出了范围或放弃所有权,才会删除原始指针,大小为两个指针,一个用于对象,一个用于引用计数。 weak_ptr 结合shared_ptr使用的特殊智能指针,提供一个或多个shared_ptr实例所拥有的对象的访问,但是不会增加引用计数。如果你想观察某个对象,但是不需要保持活动状态,则可以使用该智能指针。在某些情况下,需要断开shared_ptr实例间的循环引用。 如何正确的选择智能指针智能指针只需要区分需不需要共享使用,如果外部需要使用这个对象,使用shared_ptr,否则就使用unique_ptr进行独占使用。 陷阱和坑 不要使用相同的内置指针来初始化多个智能指针 不要主动回收智能指针内原始指针的内存 不要使用智能指针的get来初始化或者reset另一个智能指针 智能指针管理的资源只会默认删除new分配的内存,如果不是new分配的,则需要使用删除器","categories":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/categories/C/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"}]},{"title":"卷积神经网络简述","slug":"卷积神经网络简述","date":"2018-07-14T08:00:32.000Z","updated":"2024-04-15T05:09:55.096Z","comments":true,"path":"2018/07/14/卷积神经网络简述/","permalink":"https://blog.justforlxz.com/2018/07/14/%E5%8D%B7%E7%A7%AF%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E7%AE%80%E8%BF%B0/","excerpt":"前言: 我太菜了… 本来想着写个小例子,结果写到一半发现自己其实根本不会,我还是撸C++去吧。 卷积神经网络(Convolutional Neural Network)是一种前馈神经网络。它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色的表现。 卷积神经网络由一个或多个卷积层和顶端的全连通层组成,同时也包含关联权重和池化层。这一结构可以使得卷积神经网络能够利用输入数据的二维结构。与其他深度学习结构相比,卷积神经网络在图像和声音上能够给出更好的结果,这一模型也可以用反向传播算法进行训练。相比较于其他神经网络、前馈神经网络,卷积神经网络需要考虑的参数更少,使之成为一种颇具吸引力的深度学习结构。","text":"前言: 我太菜了… 本来想着写个小例子,结果写到一半发现自己其实根本不会,我还是撸C++去吧。 卷积神经网络(Convolutional Neural Network)是一种前馈神经网络。它的人工神经元可以响应一部分覆盖范围内的周围单元,对于大型图像处理有出色的表现。 卷积神经网络由一个或多个卷积层和顶端的全连通层组成,同时也包含关联权重和池化层。这一结构可以使得卷积神经网络能够利用输入数据的二维结构。与其他深度学习结构相比,卷积神经网络在图像和声音上能够给出更好的结果,这一模型也可以用反向传播算法进行训练。相比较于其他神经网络、前馈神经网络,卷积神经网络需要考虑的参数更少,使之成为一种颇具吸引力的深度学习结构。 结构卷积层卷积神经网络中每层卷积层由若干卷积单元构成。每个卷积单元的参数都可以由反向传播算法来调整。卷积运算的目的是提取输入的不同特征,第一层卷积可能只提取非常小的特征,更多层的网络只能从低级特征中提取更复杂的特征。 激活函数运行时激活神经网络中某一部分神经元,将激活信息向后传入下一层神经网络。神经网络之所以能解决非线性问题,如语音和图像,本质上就是激活函数加入了非线性的因素,弥补了线性模型的表达力,把“激活的神经元的特征”通过函数保留并映射到下一层。 因为神经网络的数学基础是处处可微,所以选取的激活函数要能保证数据输入与输出也是可微的,介绍四种函数: sigmoid sigmoid函数是传统神经网络中最常用的激活函数之一,它的优点在于,它的输出映射在(0, 1)内,单调连续,非常适合作为输出层,并且求导比较容易,缺点也比较明显,因为软饱和性,一旦落入饱和区,f’(x)就会变得接近0,很容易产生阶梯消失。 tanh tanh函数也具有软饱和性,因为它的输出以0为中心,收敛速度比sigmoid要快,但是仍然无法解决梯度消失问题。 relu relu是目前最受欢迎的激活函数,softplus可以看做是relu的平滑版本。使用线性整流(Rectified Linear Units, ReLU)f(x)=max(0,x)作为这一层神经的激励函数(Activation function)。它可以增强判定函数和整个神经网络的非线性特性,而本身并不会改变卷积层。 dropout 一个神经元将以概率决定是否要被抑制,被抑制的神经元会被暂时认为不属于网络,但是它的权重将会被保留。 池化层池化是卷积神经网络中另外一个非常重要的概念。它实际上是形式的降采样。有多种不同形式的非线性池化函数,而其中“最大池化”是最为常见的。它是将输入的图像划分为若干个矩形区域,对每个子区域输出最大值。直觉上,这种机制能够有效地原因在于,在发现一个特征之后,它的精确位置远不及它和其他特征的相对位置的关系重要。池化层会不断地减小数据的空间大小,因此参数的数量和计算量也会下降,这在一定程度上也控制了过拟合。通常来说,CNN的卷积层之间都会周期性地插入池化层。 池化层通常会分别作用于每个输入的特征并减小其大小。目前最常用形式的池化层是每隔2个元素从图像划分出2x2的区块,然后对每个区块中的4个数取最大值。这将会减少75%的数据量。 除了最大池化之外,池化层也可以使用其他池化函数,例如“平均池化”甚至“L2-范数池化”等。过去,平均池化的使用曾经较为广泛,但是最近由于最大池化在实践中的表现更好,平均池化已经不太常用。 由于池化层过快地减少了数据的大小,目前文献中的趋势是使用较小的池化滤镜,甚至不再使用池化层。 损失函数层损失函数层用于决定训练过程如何来“惩罚”网络的预测结果和真实结果之间的差异,它通常是网络的最后一层。各种不同的损失函数适用于不同类型的任务。例如,Softmax交叉熵损失函数常常被用于在K个类别中选出一个,而Sigmoid交叉熵损失函数常常用于多个独立的二分类问题。欧几里德损失函数常常用于结果取值范围为任意实数的问题。","categories":[{"name":"Deep Learning","slug":"Deep-Learning","permalink":"https://blog.justforlxz.com/categories/Deep-Learning/"}],"tags":[{"name":"Deep Learning","slug":"Deep-Learning","permalink":"https://blog.justforlxz.com/tags/Deep-Learning/"}]},{"title":"深度学习笔记","slug":"深度学习笔记","date":"2018-07-14T02:01:01.000Z","updated":"2024-04-15T05:09:55.100Z","comments":true,"path":"2018/07/14/深度学习笔记/","permalink":"https://blog.justforlxz.com/2018/07/14/%E6%B7%B1%E5%BA%A6%E5%AD%A6%E4%B9%A0%E7%AC%94%E8%AE%B0/","excerpt":"机器学习,顾名思义当然是用机器来学习。本文完。","text":"机器学习,顾名思义当然是用机器来学习。本文完。 上面的话是开玩笑,各位客官不要走… 人工智能人工智能其实不算新名词,在人类历史的长河中,就有过很多关于技艺高超的工匠制造人造人,并赋予智慧。现代的人工智能则始于古典哲学家用机械符号的观点来解释人类思考过程的尝试。 20世纪50年代,人类信心满满的开始了人工智能的征途,但是这趟旅程并不平坦,1973年美英两国政府停止了没有明确目标的人工智能项目的研究。七年后又受到日本政府研究规划的刺激,又恢复了拨款,但是在80年代末再次停止了拨款。人工智能的研究就这样在跌宕起伏中不断的前进。 时光荏苒,人类进入了21世纪,这次人工智能领域终于迎来了新的人生,计算机已经得到了充足的发展,计算能力与日俱增,曾经因为计算能力不足导致无法研究的项目和算法也可以得到重新的验证,当然除了计算能力提升带来的提升,更多的是幕后的工作者不断的改进和完善各种算法,对不同的课题进行长期深度的研究。 人工智能再次进入大众视野是2016年,来自Google公司的AlphaGo成功击败了韩国选手李世石,成为第一个在围棋上战胜人类的人工智能,立下了里程碑。在AlphaGo进行比赛前,人类还自信的认为机器无法在围棋赢得胜利(人类疯狂的奶自己…)。 AlphaGo采用了蒙特卡洛树搜索和两个深度神经网络结合的办法,蒙特卡洛树搜索是基于某种决策过程的启发式搜索算法,两个深度神经网络一个以估值网络来评估大量的选点,一个则以走棋网络来选择落子,在这种设计下,系统可以结合树搜索来长远推断,就像人脑一样评估落点,提高下棋能力。 人工智能、机器学习和深度学习的区别我也对这三个名词产生过疑问,其实很简单: 人工智能: 人工实现的智能 机器学习: 一种人工智能的实现方法 深度学习: 一种机器学习的实现方法 机器学习机器学习是人工智能的一个分支,人工智能的研究历史中有一条以“推理”为重点,到以“知识”为重点,再到以“学习”为重点的自然、清晰的脉络。显然机器学习是是实现人工智能的一条途径,即以机器学习为手段解决人工智能中的问题。 机器学习在近30多年已发展为一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、计算复杂性理论等多门学科。机器学习理论主要是设计和分析一些让计算机可以自动“学习”的算法。机器学习算法是一类从数据中自动分析获得规律,并利用规律对未知数据进行预测的算法。因为学习算法中涉及了大量的统计学理论,机器学习与推断统计学联系尤为密切,也被称为统计学习理论。算法设计方面,机器学习理论关注可以实现的,行之有效的学习算法。很多推论问题属于无程序可循难度,所以部分的机器学习研究是开发容易处理的近似算法。 机器学习的应用机器学习已广泛应用于数据挖掘、计算机视觉、自然语言处理、生物特征识别、搜索引擎、医学诊断、检测信用卡欺诈、证券市场分析、DNA序列测序、语音和手写识别、战略游戏和机器人等领域。 机器学习的定义机器学习有下面几种定义: 机器学习是一门人工智能的科学,该领域的主要研究对象是人工智能,特别是如何在经验学习- 中改善具体算法的性能。 机器学习是对能通过经验自动改进的计算机算法的研究。 机器学习是用数据或以往的经验,以此优化计算机程序的性能标准。 机器学习的分类机器学习可以大概分为以下几类: 监督学习:从给定的训练数据集中学习出一个函数,当新的数据到来时,可以根据这个函数预测结果。监督学习的训练集要求是包括输入和输出,也可以说是特征和目标。训练集中的目标是由人标注的。常见的监督学习算法包括回归分析和统计分类。 半监督学习: 介于监督学习与无监督学习之间。 无监督学习: 与监督学习相比,训练集没有人为标注的结果。常见的无监督学习算法有生成对抗网络(GAN)、聚类。 强化学习: 通过观察来学习做成如何的动作。每个动作都会对环境有所影响,学习对象根据观察到的周围环境的反馈来做出判断。 监督学习和无监督学习的差别在于训练数据是否人为标记,他们都有训练集,都有输入输出。 机器学习的算法具体的机器学习算法有: 构造间隔理论分布:聚类分析和模式识别 人工神经网络 决策树 感知器 支持向量机 集成学习AdaBoost 降维与度量学习 聚类 贝叶斯分类器 构造条件概率:回归分析和统计分类 高斯过程回归 线性判别分析 最近邻居法 径向基函数核 通过再生模型构造概率密度函数: 最大期望算法 概率图模型:包括贝叶斯网和Markov随机场 Generative Topographic Mapping 近似推断技术: 马尔可夫链 蒙特卡罗方法 变分法 最优化:大多数以上方法,直接或者间接使用最优化算法。 人工神经网络在机器学习中,目前应用最广泛的是人工神经网络(Artificial Neural Network,ANN),简称神经网络。是一种模仿生物神经网络的结构和功能的数学模型或计算模型,用于对函数进行估计和近似。神经网络由大量的人工神经元连结进行计算。大多数情况下人工神经网络能在外界信息的基础上改变内部结构,是一种自适应系统,通俗叫具备学习能力。 人工神经网络的组成现代神经网络是一种非线性统计行数据建模工具,典型的神经网络具有以下三个部分: 结构(Architecture): 指定了网络中的变量和它们的拓扑关系。例如,神经网络中的变量可以是神经元连接的权重(weights)和神经元的激励值(activities of the neurons)。 激励函数(Activity Rule): 大部分神经网络具有一个短时间尺度的动力学规则,来定义神经元如何根据其他神经元的活动来改变自己的激励值。一般激励值依赖于网络中的权重(即该网络中的参数)。 学习规则(Learning Rule): 学习规则指定了网络中的权重如何随着时间推进而调整。这一般被看做是一种长时间尺度的动力学规则。一般情况下,学习规则依赖于神经元的激励值。它也可能依赖于监督者提供的目标值和当前权重的值。例如,用于手写识别的一个神经网络,有一组输入神经元。输入神经元会被输入图像的数据所激发。在激励值被加权并通过一个函数(由网络的设计者确定)后,这些神经元的激励值被传递到其他神经元。这个过程不断重复,直到输出神经元被激发。最后,输出神经元的激励值决定了识别出来的是哪个字母。 在这里我推荐观看3Blue1Brown的三期视频。 深度学习之神经网络的结构 Part 1 ver 2.0 深度学习之梯度下降法 Part 2 ver 0.9 beta 深度学习之反向传播算法 上/下 Part 3 ver 0.9 beta 人工神经网络的基础神经网络的构筑理念是受到生物(人或其他动物)神经网络功能的运作启发而产生的。人工神经网络通常是通过一个基于数学统计学类型的学习方法(Learning Method)得以优化,所以人工神经网络也是数学统计学方法的一种实际应用,通过统计学的标准数学方法我们能够得到大量的可以用函数来表达的局部结构空间,另一方面在人工智能学的人工感知领域,我们通过数学统计学的应用可以来做人工感知方面的决定问题(也就是说通过统计学的方法,人工神经网络能够类似人一样具有简单的决定能力和简单的判断能力),这种方法比起正式的逻辑学推理演算更具有优势。 和其他机器学习方法一样,神经网络已经被用于解决各种各样的问题,例如机器视觉和语音识别。这些问题都是很难被传统基于规则的编程所解决的。 对人类中枢神经系统的观察启发了人工神经网络这个概念。在人工神经网络中,简单的人工节点,称作神经元(neurons),连接在一起形成一个类似生物神经网络的网状结构。 人工神经网络目前没有一个统一的正式定义。不过,具有下列特点的统计模型可以被称作是“神经化”的:具有一组可以被调节的权重,换言之,被学习算法调节的数值参数,并且可以估计输入数据的非线性函数关系这些可调节的权重可以被看做神经元之间的连接强度。人工神经网络与生物神经网络的相似之处在于,它可以集体地、并行地计算函数的各个部分,而不需要描述每一个单元的特定任务。神经网络这个词一般指统计学、认知心理学和人工智能领域使用的模型,而控制中央神经系统的神经网络属于理论神经科学和计算神经科学。 在神经网络的现代软件实现中,被生物学启发的那种方法已经很大程度上被抛弃了,取而代之的是基于统计学和信号处理的更加实用的方法。在一些软件系统中,神经网络或者神经网络的一部分(例如人工神经元)是大型系统中的一个部分。这些系统结合了适应性的和非适应性的元素。虽然这种系统使用的这种更加普遍的方法更适宜解决现实中的问题,但是这和传统的连接主义人工智能已经没有什么关联了。不过它们还有一些共同点:非线性、分布式、并行化,局部性计算以及适应性。从历史的角度讲,神经网络模型的应用标志着二十世纪八十年代后期从高度符号化的人工智能(以用条件规则表达知识的专家系统为代表)向低符号化的机器学习(以用动力系统的参数表达知识为代表)的转变。 神经网络在早期的进展非常缓慢,第一个问题是基本感知器无法解决异或问题,第二个问题是计算机没有足够的能力处理大型神经网络所需要的计算时间,直到计算机具备更强的计算能力前,神经网络的进展都一直很缓慢。 反向传播算法的出现后来出现了关键的的进展: 在1975年由Paul Werbos提出的反向传播算法。该算法解决了异或的问题,还能更普遍的训练多层神经网络。反向传播算法在3Blue1Brown的第二期视频中有讲解,视频中采用了通俗易懂的方式来介绍反向传播算法是如何调整神经元的。 神经网络的又一个关键进展是显卡性能的提升。大家都知道CPU偏向于控制而非计算,这就造成早期处理训练神经网络的代价非常大,使用CPU来训练神经网络的速度非常慢,而现代图形处理器有强大的并行处理能力和可编程流水线,令流处理器也可以处理非图形数据。特别是在面对单指令流多数据流(SIMD)且数据处理的运算量远大于数据调度和传输的需要时,通用图形处理器在性能上大大超越了传统的中央处理器应用程序。现在我们可以轻易的使用Nvidia的CUDA方案或者OpenCL来编写代码,并使用显卡来进行计算。 多层前馈网络一种常见的多层结构的前馈网络(Multilayer Feedforward Network)由三部分组成: 输入层: 众多神经元接受大量非线性的信息,输入的信息被称为输入向量。 隐含层: 是输入层和输出层之间众多神经元和链接组成的各个层面。隐含层可以有一层或多层。隐含层的节点(神经元)数目不定,但数目越多神经网络的非线性越显著,从而神经网络的强健性(robustness)(控制系统在一定结构、大小等的参数摄动下,维持某些性能的特性)更显著。习惯上会选输入节点1.2至1.5倍的节点。 输出层: 在神经元链接中传输、分析、权衡,形成输出结果。输出的信息称为输出向量。 这种网络一般称为感知器(对单隐藏层)或多层感知器(对多隐藏层),神经网络的类型已经演变出很多种,这种分层的结构也并不是对所有的神经网络都适用。 理论性质计算能力多层感知器(MLP)是一个通用的函数逼近器,由Cybenko定理证明。然而,证明不是由所要求的神经元数量或权重来推断的。Hava Siegelmann和Eduardo D. Sontag的工作证明了,一个具有有理数权重值的特定递归结构(与全精度实数权重值相对应)相当于一个具有有限数量的神经元和标准的线性关系的通用图灵机。他们进一步表明,使用无理数权重值会产生一个超图灵机。 容量人工神经网络模型有一个属性,称为“容量”,这大致相当于他们模拟任何函数的能力。它与网络中可以存储的信息量有关,也与复杂性有关。 收敛性模型并不总是收敛到唯一解,因为它取决于一些因素。首先,函数可能存在许多局部极小值,这取决于成本函数和模型。其次,在远离局部最小值时,优化方法可能无法保证收敛。第三,对大量的数据或参数,一些方法变得不切实际。在一般情况下,我们发现,理论保证的收敛不能成为实际应用的一个可靠的指南。 综合统计在目标是创建一个普遍系统的应用程序中,过度训练的问题出现了。这出现在回旋或过度具体的系统中当网络的容量大大超过所需的自由参数。为了避免这个问题,有两个方向:第一个是使用交叉验证和类似的技术来检查过度训练的存在和选择最佳参数如最小化泛化误差。二是使用某种形式的正规化。这是一个在概率化(贝叶斯)框架里出现的概念,其中的正则化可以通过为简单模型选择一个较大的先验概率模型进行;而且在统计学习理论中,其目的是最大限度地减少了两个数量:“风险”和“结构风险”,相当于误差在训练集和由于过度拟合造成的预测误差。 一个小例子现在作为深度学习入门的是手写数字识别,3Blue1Brown的三期视频就是基于此。 通过神经网络来学习如何识别手写数字,本质上就是人类通过算法来分解图像的信息,比如数字9,它可以认为是竖线和圆的组合,输出层是9,则隐含层需要处理竖线和圆,输入层输入的是手写9的全部像素,隐含层就是神经网络的核心,它需要只要竖线和圆又是由什么组成,最后一步步的分解为一个像素,再通过反向传播算法来训练和调节隐含层中的偏置和权值,最后整个网络就可以学习到正确的识别手写数字。 深度学习就是通过人工神经网络来告诉计算机结果是如何产生的,以及如何通过结果来调整网络结构,达到预测和处理未标记的信息。","categories":[{"name":"Deepin Learning","slug":"Deepin-Learning","permalink":"https://blog.justforlxz.com/categories/Deepin-Learning/"}],"tags":[{"name":"Deepin Learning","slug":"Deepin-Learning","permalink":"https://blog.justforlxz.com/tags/Deepin-Learning/"}]},{"title":"C plus plus Iterator 笔记","slug":"C-plus-plus-Iterator-笔记","date":"2018-07-09T05:05:12.000Z","updated":"2024-04-15T05:09:54.911Z","comments":true,"path":"2018/07/09/C-plus-plus-Iterator-笔记/","permalink":"https://blog.justforlxz.com/2018/07/09/C-plus-plus-Iterator-%E7%AC%94%E8%AE%B0/","excerpt":"本文记录了咱对迭代器的一些理解 C++ 标准库提供了三种类型组件: 容器 迭代器 算法 容器是指存储某种类型的结构,容器有两种: 顺序容器 (vector、list和string等,是元素的有序集合。) 关联容器 (set、map等,是包含查找元素的键值。 ) 遍历容器的方式之一就是迭代器,迭代器是一种泛型指针,普通指针指向一块内存,迭代器指向容器中的一个位置。STL的每个模板容器中,都定义了一组对应的迭代器类,使用迭代器和算法,就可以访问容器中特定位置的元素,而无需关心元素的类型。 每种容器都定义了一对begin和end的函数,用于返回迭代器。如果容器中有元素的话,begin返回的迭代器指向第一个元素。","text":"本文记录了咱对迭代器的一些理解 C++ 标准库提供了三种类型组件: 容器 迭代器 算法 容器是指存储某种类型的结构,容器有两种: 顺序容器 (vector、list和string等,是元素的有序集合。) 关联容器 (set、map等,是包含查找元素的键值。 ) 遍历容器的方式之一就是迭代器,迭代器是一种泛型指针,普通指针指向一块内存,迭代器指向容器中的一个位置。STL的每个模板容器中,都定义了一组对应的迭代器类,使用迭代器和算法,就可以访问容器中特定位置的元素,而无需关心元素的类型。 每种容器都定义了一对begin和end的函数,用于返回迭代器。如果容器中有元素的话,begin返回的迭代器指向第一个元素。 std::list<int>::iterator it = list.begin(); 上述语句把it初始化为由list的begin返回的迭代器,如果list不为空,it将指向该元素list[0]。 由end操作返回的迭代器指向list的末端元素的下一个,通常指超出末端迭代器(off-the-end-iterator),表明指向一个不存在的元素,如果容器为空,begin返回的迭代器将和end相同,在使用中,可以通过判断end来检查是否处理完容器种所有的元素。 迭代器类型定义了一些操作来获取迭代器所指向的元素,并允许程序员将迭代器从一个元素移动到另一个元素。 遍历列表: std::list<int> listfor (std::list<int>::const_iterator it = list.constBegin(); it != list.constEnd(); ++it) { // 通过迭代器访问元素需要解引用。 std::cout << *it << std::endl;} std::list<int> list;std::sort(list.begin(), list.end(), [=] (int _i1, int _i2) { return _i1 < _i2;}); 上面的示例代码是对一个int类型的list进行排序,","categories":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/categories/C/"}],"tags":[{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"}]},{"title":"在DeepinLinux下使用nVidia CUDA","slug":"在DeepinLinux下使用nVidia-CUDA","date":"2018-06-27T19:21:50.000Z","updated":"2024-04-15T05:09:55.097Z","comments":true,"path":"2018/06/28/在DeepinLinux下使用nVidia-CUDA/","permalink":"https://blog.justforlxz.com/2018/06/28/%E5%9C%A8DeepinLinux%E4%B8%8B%E4%BD%BF%E7%94%A8nVidia-CUDA/","excerpt":"CUDA(Compute Unified Device Architecture,统一计算架构)是由NVIDIA所推出的一种集成技术,是该公司对于GPGPU的正式名称。通过这个技术,用户可利用NVIDIA的GeForce 8以后的GPU和较新的Quadro GPU进行计算。亦是首次可以利用GPU作为C-编译器的开发环境。NVIDIA营销的时候,往往将编译器与架构混合推广,造成混乱。实际上,CUDA可以兼容OpenCL或者自家的C-编译器。无论是CUDA C-语言或是OpenCL,指令最终都会被驱动程序转换成PTX代码,交由显示核心计算。 在论坛上看到有些用户希望在deepin下使用CUDA,但是他们采取的做法往往是手动下载nvidia的二进制文件,直接进行安装。 但是这样会破坏一部分的glx链接,导致卸载的时候无法彻底恢复,结果就是系统因为卸载nvidia驱动而废掉,所以我推荐使用包管理器的方式安装nvidia驱动和cuda相关的东西,尽量不要手动修改。","text":"CUDA(Compute Unified Device Architecture,统一计算架构)是由NVIDIA所推出的一种集成技术,是该公司对于GPGPU的正式名称。通过这个技术,用户可利用NVIDIA的GeForce 8以后的GPU和较新的Quadro GPU进行计算。亦是首次可以利用GPU作为C-编译器的开发环境。NVIDIA营销的时候,往往将编译器与架构混合推广,造成混乱。实际上,CUDA可以兼容OpenCL或者自家的C-编译器。无论是CUDA C-语言或是OpenCL,指令最终都会被驱动程序转换成PTX代码,交由显示核心计算。 在论坛上看到有些用户希望在deepin下使用CUDA,但是他们采取的做法往往是手动下载nvidia的二进制文件,直接进行安装。 但是这样会破坏一部分的glx链接,导致卸载的时候无法彻底恢复,结果就是系统因为卸载nvidia驱动而废掉,所以我推荐使用包管理器的方式安装nvidia驱动和cuda相关的东西,尽量不要手动修改。 需要安装的很少,只有五个包,不过会依赖很多nvidia的库,总量还是有一些的。 sudo apt install nvidia-cuda-toolkit nvidia-profiler nvidia-visual-profiler nvidia-cuda-doc nvidia-cuda-dev nvcc是cuda的编译器,它目前只支持g++5,所以还需要安装g++5。 sudo apt install g++-5 然后,重启一下计算机。 这里有个小栗子,可以用来测试cuda是否能够成功编译和运行 将以下代码保存为 main.cu #include <stdio.h>__global__ void vector_add(const int *a, const int *b, int *c) { *c = *a + *b;}int main(void) { const int a = 2, b = 5; int c = 0; int *dev_a, *dev_b, *dev_c; cudaMalloc((void **)&dev_a, sizeof(int)); cudaMalloc((void **)&dev_b, sizeof(int)); cudaMalloc((void **)&dev_c, sizeof(int)); cudaMemcpy(dev_a, &a, sizeof(int), cudaMemcpyHostToDevice); cudaMemcpy(dev_b, &b, sizeof(int), cudaMemcpyHostToDevice); vector_add<<<1, 1>>>(dev_a, dev_b, dev_c); cudaMemcpy(&c, dev_c, sizeof(int), cudaMemcpyDeviceToHost); printf("%d + %d = %d, Is that right?\\n", a, b, c); cudaFree(dev_a); cudaFree(dev_b); cudaFree(dev_c); return 0;} 编译: nvcc main.cu 运行: ./a.out 如果一切顺利,在编译的时候就不会有报错,不过在我的环境下nvcc会有架构被弃用的警告,本着只要不error就算没事的原则,我们无视这条警告即可。 输出结果: 2 + 5 = 0, Is that right?","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"nVidia Cuda","slug":"nVidia-Cuda","permalink":"https://blog.justforlxz.com/tags/nVidia-Cuda/"}]},{"title":"deepin待机后键盘和触摸板无法使用的解决方法","slug":"deepin待机后键盘和触摸板无法使用的解决方法","date":"2018-06-24T22:01:22.000Z","updated":"2024-04-15T05:09:54.959Z","comments":true,"path":"2018/06/25/deepin待机后键盘和触摸板无法使用的解决方法/","permalink":"https://blog.justforlxz.com/2018/06/25/deepin%E5%BE%85%E6%9C%BA%E5%90%8E%E9%94%AE%E7%9B%98%E5%92%8C%E8%A7%A6%E6%91%B8%E6%9D%BF%E6%97%A0%E6%B3%95%E4%BD%BF%E7%94%A8%E7%9A%84%E8%A7%A3%E5%86%B3%E6%96%B9%E6%B3%95/","excerpt":"笔记本一直使用的bumblebee来省电,毕竟我也不想笔记本的电只够从一张桌子移动到另一张桌子,但是今天在调待机唤醒后dde-dock崩溃的问题,我需要切换到私有驱动下,因为笔记本使用bumblebee需要使用acpi的参数,否则会见图形就死。","text":"笔记本一直使用的bumblebee来省电,毕竟我也不想笔记本的电只够从一张桌子移动到另一张桌子,但是今天在调待机唤醒后dde-dock崩溃的问题,我需要切换到私有驱动下,因为笔记本使用bumblebee需要使用acpi的参数,否则会见图形就死。 一切准备就绪以后,我开始调试dde-dock,通过codedump已经知道崩溃在wifi列表为空时访问了first节点,但是当我开始测试修复的代码时,发生了很意外的事情,恢复待机以后键盘和触摸板无法使用了。 虽然之前我也偶尔会用用私有驱动,但是还没遇到过无法键盘和触摸板无法使用的情况。想到论坛好像也有人报了类似的问题,恢复待机以后无wifi和外置键盘无法使用,刚好可以趁这个机会调试一下。 /var/log/Xorg.0.log里看到了大量的synaptics错误,然后该模块被卸载,键盘则是没看到什么信息。 尝试重新modprobe synaptics模块,但是失败了,然后在/etc/modprobe.d/nvidia.conf里看到了几行配置。 # These aliases are defined in *all* nvidia modules.# Duplicating them here sets higher precedence and ensures the selected# module gets loaded instead of a random first match if more than one# version is installed. See #798207.#alias pci:v000010DEd00000E00sv*sd*bc04sc80i00* nvidia#alias pci:v000010DEd00000AA3sv*sd*bc0Bsc40i00* nvidia#alias pci:v000010DEd*sv*sd*bc03sc02i00* nvidia#alias pci:v000010DEd*sv*sd*bc03sc00i00* nvidia 似乎是通配出错了,匹配到了键盘和触摸板,然后就无法使用了。刚好deepin 15.6升级了nvidia驱动,所以是现在才会出这个问题。","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"dreamscene插件开发<等待填坑>","slug":"dreamscene插件开发","date":"2018-06-04T21:25:40.000Z","updated":"2024-04-15T05:09:54.966Z","comments":true,"path":"2018/06/05/dreamscene插件开发/","permalink":"https://blog.justforlxz.com/2018/06/05/dreamscene%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/","excerpt":"本篇会介绍一下deepin-dreamscene的插件机制。","text":"本篇会介绍一下deepin-dreamscene的插件机制。","categories":[],"tags":[{"name":"Linux 填坑","slug":"Linux-填坑","permalink":"https://blog.justforlxz.com/tags/Linux-%E5%A1%AB%E5%9D%91/"}]},{"title":"Dock插件开发<等待填坑>","slug":"Dock插件开发","date":"2018-05-22T21:22:12.000Z","updated":"2024-04-15T05:09:54.913Z","comments":true,"path":"2018/05/23/Dock插件开发/","permalink":"https://blog.justforlxz.com/2018/05/23/Dock%E6%8F%92%E4%BB%B6%E5%BC%80%E5%8F%91/","excerpt":"从零构建 dde-dock 的插件本教程将展示一个简单的 dde-dock 插件的开发过程,插件开发者可跟随此步骤为 dde-dock 创造出更多具有丰富功能的插件。","text":"从零构建 dde-dock 的插件本教程将展示一个简单的 dde-dock 插件的开发过程,插件开发者可跟随此步骤为 dde-dock 创造出更多具有丰富功能的插件。 在本教程中,将创建一个可以实时显示用户家目录(~/)使用情况的小工具。 插件的工作原理dde-dock 插件本质是一个按 Qt 插件标准所开发的共享库文件(so)。通过 dde-dock 预定的规范与提供的接口,共同完成 dde-dock 的功能扩展。 准备环境插件的开发环境可以是任意的,只要是符合 Qt 插件规范及 dde-dock 插件规范的共享库文件,都可以被当作 dde-dock 插件载入。下面以 Qt + qmake 为例进行说明: 安装依赖以 Deepin 15.5.1 环境为基础,至少先安装如下的包: dde-dock-dev qt5-qmake qtbase5-dev-tools libqt5core5a libqt5widgets5 pkg-config 基本的项目结构创建必需的项目目录与文件插件名称叫做home_monitor,所以创建以下的目录结构: home_monitor├── home_monitor.json├── homemonitorplugin.cpp├── homemonitorplugin.h└── home_monitor.pro","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"Linux的PAM是什么","slug":"Linux的PAM是什么","date":"2018-04-01T04:16:08.000Z","updated":"2024-04-15T05:09:54.952Z","comments":true,"path":"2018/04/01/Linux的PAM是什么/","permalink":"https://blog.justforlxz.com/2018/04/01/Linux%E7%9A%84PAM%E6%98%AF%E4%BB%80%E4%B9%88/","excerpt":"本文会基础的介绍一下PAM是什么,让你能够回答PAM是什么、PAM有什么用、如何根据需求自己开发PAM模块。 PAM是什么PAM即可插拔认证模块。它提供了一个所有服务的中心验证机制,适用于普通登录、ssh登录等需要进行身份认证的系统中。","text":"本文会基础的介绍一下PAM是什么,让你能够回答PAM是什么、PAM有什么用、如何根据需求自己开发PAM模块。 PAM是什么PAM即可插拔认证模块。它提供了一个所有服务的中心验证机制,适用于普通登录、ssh登录等需要进行身份认证的系统中。 为什么使用PAM为了安全起见,计算机只能给通过授权的用户进行使用,在创建用户时,密码会被加密保存在/etc/passwd中,在用户登录时,重新计算密码,然后在/etc/passwd中进行对比。 除了上面这种,还有其他方式的验证,比如现在经常使用的指纹认证,其核心思想都是检查内容是否匹配。但是这些方案都有一些通病,那就是需要随着应用程序一起编译来使用,如果认证系统有问题,或者更新了算法,就需要重新编译才能使用。 鉴于以上原因,人们开始寻找一种更佳的替代方案:一方面,将鉴别功能从应用中独立出来,单独进行模块化设计,实现和维护;另一方面,为这些鉴别模块建立标准 API,以便各应用程序能方便的使用它们提供的各种功能;同时,鉴别机制对其上层用户(包括应用程序和最终用户)是透明的。 PAM是如何工作的 PAM采用了分层的模块式开发,提供了四种类型的模块: 认证管理 账号管理 会话管理 口令管理 这四个接口就可以满足用户的认证和管理。一个模块可以同时属于多种类型,只需实现对应的函数就可以。 目前PAM的实现有以下三种: Linux-PAM: Linux-PAM 涵盖了本文中讨论的所有 PAM。在任何一个 Linux 平台中的 PAM 的主要结构都类似于 Linux-PAM 版本。 OpenPAM: OpenPAM 是由 NAI 实验室的 Dag-Erling Smorgrav 开发的另一个 PAM 实现,属于 DARPA-CHATS 研究项目。由于它是开源的,因此它主要由 FreeBSD、NetBSD 及应用程序(加上 Mac OS X)使用。 Java™ PAM 或 JPam: PAM 主要是支持 Linux 和 UNIX 的标准验证模块。JPam 将 Java 部分与普通 PAM 联系了起来。JPam 允许基于 Java 的应用程序使用 PAM 模块或工具(如 auth、account、passwd、session 等)。它提供了 JAAS 和直接 API,并且支持大多数 Unix OS 和架构。 虽然有不同的PAM实现,但是主要功能都是类似的,完成用户的验证。 想要了解更多,可查看IBM的文档库。深入 Linux PAM 体系结构 如何自己开发PAM模块PAM模块使用一个pam_handle类型的结构当做句柄,也是唯一一个PAM和程序进行通信的结构。 首先在编写的服务模块的源程序里要包含下列头文件: #include <security/pam_modules.h> PAM模块是一个个的so动态库。PAM会通过dlopen来装载这些so。四个模块分别需要实现对应的方法,PAM会根据配置文件来调用这些方法。 每个PAM模块的认证程序都以pam_start开始,以pam_end结束。PAM还提供了pam_get_item和pam_set_item共享有关认证会话的某些公共信息,例如用户名、服务名和密码。应用程序在调用了pam_start以后可以用这些APIs来改变状态信息。实际工作的函数有6个: 模块类型 函数 功能 认证管理 PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) 认证用户 认证管理 PAM_EXTERN int pam_sm_setcred(pam_handle_t *pamh, int flags, int argc, const char **argv) 设置用户证书 账号管理 PAM_EXTERN int pam_sm_acct_mgmt(pam_handle_t *pamh, int flags, int argc, const char **argv) 账号管理 会话管理 PAM_EXTERN int pam_sm_open_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 打开会话 会话管理 PAM_EXTERN int pam_sm_close_session(pam_handle_t *pamh, int flags, int argc, const char **argv) 关闭会话 口令管理 PAM_EXTERN int pam_sm_chauthtok(pam_handle_t *pamh, int flags, int argc, const char **argv) 设置口令 同一个模块可以同时支持不同的类型,可以一个模块全部实现这些方法,也可以实现部分。PAM自带的pam_unix.so就是支持四种类型。 在函数内进行详细的操作,最后返回结果,即可完成整个验证流程。 配置PAMPAM的配置通常在/etc/pam.d/下。 模块将按照在配置文件中列出的顺序被调用,这取决于每个条目允许的 Control_flag 的值。Control_flag 值包括: Required:堆栈中的所有 Required 模块必须看作一个成功的结果。如果一个或多个 Required 模块失败,则实现堆栈中的所有 Required 模块,但是将返回第一个错误。 Sufficient:如果标记为 sufficient 的模块成功并且先前没有 Required 或 sufficient 模块失败,则忽略堆栈中的所有其余模块并返回成功。 Optional:如果堆栈中没有一个模块是 required 并且没有任何一个 sufficient 模块成功,则服务/应用程序至少要有一个 optional 模块成功。 在程序中使用PAM进行验证 开发PAM验证模块 #include <security/pam_appl.h>#include <security/pam_modules.h>#include <stdio.h>// 只实现账户认证PAM_EXTERN int pam_sm_authenticate(pam_handle_t *pamh, int flags, int argc, const char **argv) { char *username; char password[256]; //得到用户名 pam_get_user(pamh, &username, "Username: "); // 得到密码 printf("Password: "); gets(password); // 测试判断,如果用户名和密码不相等,就认证失败 if (strcmp(username, password) != 0) { return PAM_AUTH_ERR; } printf("Password is: %s\\n", password); return PAM_SUCCESS;} 编译: gcc -fPIC -fno-stack-protector -c pam_test_mod.csudo ld -x --shared -o /lib/security/pam_test_mod.so pam_test_mod.o 还需要修改pam的配置,来加载这个so。编辑/etc/pam.d/common-auth auth [success=1 default=ignore] pam_test_mod.so 这里的success的值需要根据实际情况来调整,必须是所有里面的最大值。 使用模块进行验证 // PAM必须的两个头文件#include <iostream>#include <security/pam_appl.h>#include <security/pam_misc.h>using namespace std;extern int misc_conv(int num_msg, const struct pam_message **msgm, struct pam_response **response, void *appdata_ptr) { return PAM_SUCCESS;}const struct pam_conv conv = {misc_conv, NULL};int main(int argc, char *argv[]) { // 初始化 pam_handle_t *pamh = NULL; int retval; const char *username = argv[1]; // 初始化PAM 设置common-auth为验证配置 if ((pam_start("common-auth", username, &conv, &pamh)) != PAM_SUCCESS) { return -1; } // //认证用户 retval = pam_authenticate(pamh, 0); cout << (retval == PAM_SUCCESS ? "SUCCESS\\n" : "Failed\\n") << endl; // // 结束PAM if (pam_end(pamh, retval) != PAM_SUCCESS) { cout << "check_user: failed to release authenticator\\n" << endl; return -1; } return retval == PAM_SUCCESS ? 0 : 1;} 编译测试一下: g++ -o pam_test pam_test.cc -lpam -lpam_miscsudo ./pam_test $USER 输出为: $ ./pam_test test Password: testPassword is: testSUCCESS 总结基于PAM认证体系,我们可以根据自己的需求任意的扩展linux账户,linux下的pbis-open,就是基于PAM扩展出来的一个AD域登录模块,它提供了一个pam_lsass.so的文件,来进行账户的验证。我们也可以自己设计一套认证流程,只需要满足上面的接口要求就可以。 提供机制,而非策略","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}],"author":"张丁元"},{"title":"重构dde-session-ui","slug":"重构dde-session-ui","date":"2018-03-11T03:48:01.000Z","updated":"2024-04-15T05:09:55.101Z","comments":true,"path":"2018/03/11/重构dde-session-ui/","permalink":"https://blog.justforlxz.com/2018/03/11/%E9%87%8D%E6%9E%84dde-session-ui/","excerpt":"dde-session-ui里面包含了很多项目,是一个集合,但是其中的代码缺少合理的维护,以至于已经到了必须重构才能继续开发和维护,在支持AD域登录的时候,如果强制加上功能,代码会变得更加糟糕,所以和石博文一块重构了其中非常重要的UserWidget。","text":"dde-session-ui里面包含了很多项目,是一个集合,但是其中的代码缺少合理的维护,以至于已经到了必须重构才能继续开发和维护,在支持AD域登录的时候,如果强制加上功能,代码会变得更加糟糕,所以和石博文一块重构了其中非常重要的UserWidget。 重构前的设计重构前的dde-lock和lightdm-deepin-greeter是非常混乱的,处理逻辑都混杂在一块,虽然能看出有基本的结构,但是整体并未解耦。 重构后的设计 基于User类的处理 UserWidget负责提供对用户的处理,暴露出基本的currentUser和LogindUsers。 Lock和Greeter的Manager从UserWidget、SessionWidget中获取用户和用户的会话。 Manager只负责控件的位置和用户的验证。 背景修改为Manager提供模糊的壁纸,FullBackground只提供绘制。 重构以后用了大概原代码的1/3,启动速度也快了,感觉世界充满了美好… 就是重构历程太辛苦… 本次也发现了很多自身的问题,基础并没有学好,很多地方都可以使用更好的处理方式【就是管不住这手…】","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"使用DTK开发","slug":"使用DTK开发","date":"2018-01-12T03:05:26.000Z","updated":"2024-04-15T05:09:55.095Z","comments":true,"path":"2018/01/12/使用DTK开发/","permalink":"https://blog.justforlxz.com/2018/01/12/%E4%BD%BF%E7%94%A8DTK%E5%BC%80%E5%8F%91/","excerpt":"在阅读本篇文章之前,你需要掌握基本的Qt/C++开发知识。","text":"在阅读本篇文章之前,你需要掌握基本的Qt/C++开发知识。 注意:本篇文章基于Deepin平台,其他平台请自行补充依赖关系。 先安装DTK的依赖关系。 sudo apt install libdtkwidget2 libdtkcore2 新建Qt项目,编辑pro文件,添加项目依赖。 CONFIG += c++14 link_pkgconfigPKGCONFIG += dtkwidget DTK目前有两个组件,一个是提供库功能的core,一个是提供控件的widget。 修改main.cpp,删除QApplication的相关内容,改为DApplication。 注意: 使用DTK的组件,需要使用DTK的宏,根据使用的文件来选择对应的宏。 DWIDGET_USE_NAMESPACEDCORE_USE_NAMESPACE DTK使用了deepin自己的qt插件,需要在DApplication前调用。 DApplication::loadDXcbPlugin();DApplication app(argc, argv); DApplication中提供了很多方法来设置程序的各种信息,具体请看头文件的定义。 主窗口由DMainWindow提供,新建类,然后添加DMainWindow的头文件和DTKWIDGET的宏。 #include <DMainWindow>DWIDGET_USE_NAMESPACE 然后修改继承关系,改为继承DMainWindow。DMainWindow提供了一些我们封装的方法。目前为止,该程序的界面已经符合Deepin程序的风格了,我们封装了一些其他控件,使其样式符合我们的风格,如果要在其他Qt程序中使用,也是同样的步骤,载入Qt插件,添加对应的头文件和DTK的宏。","categories":[],"tags":[{"name":"Linux DTK","slug":"Linux-DTK","permalink":"https://blog.justforlxz.com/tags/Linux-DTK/"}]},{"title":"解决IntelliJ IDEA界面瞎眼","slug":"解决IntelliJ-IDEA界面瞎眼","date":"2017-12-25T06:22:25.000Z","updated":"2024-04-15T05:09:55.100Z","comments":true,"path":"2017/12/25/解决IntelliJ-IDEA界面瞎眼/","permalink":"https://blog.justforlxz.com/2017/12/25/%E8%A7%A3%E5%86%B3IntelliJ-IDEA%E7%95%8C%E9%9D%A2%E7%9E%8E%E7%9C%BC/","excerpt":"今天在逛深度论坛的时候,无意间看到了有个回复,是处理IEDA字体很挫的,试了一下,效果非常棒。 我之前也试了些网上的办法,都没有解决,字体挫的根本看不了,被逼无奈跑到windows下写MOD了。","text":"今天在逛深度论坛的时候,无意间看到了有个回复,是处理IEDA字体很挫的,试了一下,效果非常棒。 我之前也试了些网上的办法,都没有解决,字体挫的根本看不了,被逼无奈跑到windows下写MOD了。 原文链接 在/etc/profile.d/新建一个文件,用来设置java的环境变量: sudo vim /etc/profile.d/z999__java_options.sh #!/bin/bashopts=" -Dswing.aatext=true -Dawt.useSystemAAFontSettings=lcd -Djava.net.useSystemProxies=true "export _JAVA_OPTIONS="`echo ${_JAVA_OPTIONS} |sed -Ee 's/-Dawt.useSystemAAFontSettings=\\w+//ig'` $opts" 然后注销再登录,就可以看到效果了。 其实这个解决办法在arch的wiki上有,只不过似乎是我写错了吧,反正是没生效,按照这种方法是可以的,就这么用吧。非常感觉@ihipop。","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"修复Archlinux的Grub","slug":"修复Archlinux的Grub","date":"2017-12-18T01:44:55.000Z","updated":"2024-04-15T05:09:55.095Z","comments":true,"path":"2017/12/18/修复Archlinux的Grub/","permalink":"https://blog.justforlxz.com/2017/12/18/%E4%BF%AE%E5%A4%8DArchlinux%E7%9A%84Grub/","excerpt":"","text":"又双叒叕不知道怎么搞的,就把arch的grub给弄坏了,但是在重新安装grub的时候,提示了这么一个错误: Could not prepare Boot variable: No space left on device 诶不对啊,boot分区还有800M呢,怎么这么快没空间了,根目录也有52G呢,于是谷歌了一把,找到了解决办法. sudo rm /sys/firmware/efi/efivars/dump-* 新式 efivarfs (EFI VARiable FileSystem) 接口 (CONFIG_EFIVAR_FS) - 由位于 /sys/firmware/efi/efivars 的 efivarfs 内核模块挂载使用 - 老式 sysfs-efivars 接口的替代品,不限制变量数据大小,支持 UEFI Secure Boot 变量并被上游推荐使用。在3.8版的内核中引入,新的 efivarfs 模块在3.10版内核中从旧的 efivars 内核模块中分离。 删掉dump文件,就可以正常安装了【有点迷,不应该啊。 参考资料 : Unified_Extensible_Firmware_Interface","categories":[],"tags":[{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"}]},{"title":"解决NVIDIA重新启动以后系统冻结","slug":"解决NVIDIA重新启动以后系统冻结","date":"2017-09-01T09:01:47.000Z","updated":"2024-04-15T05:09:55.100Z","comments":true,"path":"2017/09/01/解决NVIDIA重新启动以后系统冻结/","permalink":"https://blog.justforlxz.com/2017/09/01/%E8%A7%A3%E5%86%B3NVIDIA%E9%87%8D%E6%96%B0%E5%90%AF%E5%8A%A8%E4%BB%A5%E5%90%8E%E7%B3%BB%E7%BB%9F%E5%86%BB%E7%BB%93/","excerpt":"分期买了一台神舟 Z6-kp5s1,配置还不错,够用三年了,但是在linux下使用bumblebee的时候,发生了问题,折腾了好久,现在把解决方法写出来。","text":"分期买了一台神舟 Z6-kp5s1,配置还不错,够用三年了,但是在linux下使用bumblebee的时候,发生了问题,折腾了好久,现在把解决方法写出来。 先说一下问题吧,正常安装bumblebee、bbswitch和nvidia驱动,重新启动系统以后,系统出现冻结,没有任何的输入输出,没有任何日志产生。问题似乎是固件错误,详情查看讨论和Linux的bug讨论。 解决方法是看的Witiko的博客,通过给内核传递参数来防止系统出现冻结。修改/etc/default/grub,在文件底部追加以下内容: # Bumblebee 3.2.1 fix (see https://github.com/Bumblebee-Project/Bumblebee/issues/764)GRUB_CMDLINE_LINUX_DEFAULT="$GRUB_CMDLINE_LINUX_DEFAULT "'acpi_osi=! acpi_osi="Windows 2009"' 如果不放心,请先禁用登录管理器,防止开机就出现冻结,然后尝试手动启动登录管理器。在tty登录,然后执行: sudo systemctl start display-manager.service 如果一切正常,你将会看到图形,并且lspci -v中能看到nvidia已经被禁用,然后使用提供的测试方法进行测试,可以看到nvidia被启用,关闭测试成功,nvidia被禁用。 提供一下我关闭nvidia以后的使用和续航时间吧。亮度调节为50%,cpu设置为powersave,运行了一下程序: telegram chrome dde-file-manager vs code meow 若干ss client 还有一大堆乱七八糟的服务,懒得写了 从14:35开始断电测试,到17:17还有23%的电量。","categories":[],"tags":[{"name":"linux","slug":"linux","permalink":"https://blog.justforlxz.com/tags/linux/"}]},{"title":"我的代理折腾方案","slug":"我的代理折腾方案","date":"2017-08-31T12:43:48.000Z","updated":"2024-04-15T05:09:55.100Z","comments":true,"path":"2017/08/31/我的代理折腾方案/","permalink":"https://blog.justforlxz.com/2017/08/31/%E6%88%91%E7%9A%84%E4%BB%A3%E7%90%86%E6%8A%98%E8%85%BE%E6%96%B9%E6%A1%88/","excerpt":"最近在准备做新的代理设置界面,然后想到自己也改改代理的配置,好方便的用在新的设置上。","text":"最近在准备做新的代理设置界面,然后想到自己也改改代理的配置,好方便的用在新的设置上。 我以前的旧方案是: privoxy –> nginx –> 多个ss客户端 ==== 多个ss服务端 privoxy用来将socks转成http,nginx是用来多个ss负载均衡。 这个方案虽然有点麻烦,但是用起来还是很吼的。 但是我们改了控制中心对代理的设置,在程序前面加上了proxychains。刚好可以和我的privoxy在作用上冲突了。但是其实还有一些其他问题,我是比较懒的人,跳过大陆ip和局域网对我来说是有很大帮助的,这样我就可以设置一个全局代理,而不需要给每个程序单独设置。 以前肥猫给我介绍过一些方法,当初我弄的时候,还是太图样,总是不能好好的稳定工作,所以暂时放弃了全局代理的方案。provixy其实是可以做这样的事,但是好麻烦,要自己添加很多规则,gfwlist是可以转成它支持的action,但是自己再添加的话,很麻烦。【而我是根比较懒的竹子 今天把provixy给撤下来了,换上以前用的meow,是用go写的,作用也是转成http,但是它支持的方案比较多,可以直接添加ss,也可以正向代理。当初不用它的原因是我想随机使用一个代理,当时电信和我过不去,一个端口用久了,就封我一天,害得我早上到公司了,先远程到服务器改端口,后来又觉得麻烦,直接开了5个端口,每天改本机,再后来就想随机使用了… 【还有次把我的ssh端口封了一天… 现在的话,就是变成了这个结构: meow –> nginx –> 多个ss客户端 ==== 多个ss服务端 nginx还是代替meow做负载均衡,meow的工作就变成了转成http代理和黑白名单。 虽然不需要新的控制中心的代理方案,现在这套就工作的很好了,但是没有它,我就不会再折腾新的【笑哭","categories":[],"tags":[{"name":"linux","slug":"linux","permalink":"https://blog.justforlxz.com/tags/linux/"}]},{"title":"开发topbar中的技术问题","slug":"开发topbar中的技术问题","date":"2017-08-23T00:54:56.000Z","updated":"2024-04-15T05:09:55.097Z","comments":true,"path":"2017/08/23/开发topbar中的技术问题/","permalink":"https://blog.justforlxz.com/2017/08/23/%E5%BC%80%E5%8F%91topbar%E4%B8%AD%E7%9A%84%E6%8A%80%E6%9C%AF%E9%97%AE%E9%A2%98/","excerpt":"这里记录了开发topbar中遇到的坑和一些问题。","text":"这里记录了开发topbar中遇到的坑和一些问题。 使用Qt提供的qxcb方法注册阴影为dock类型,反而处于DESKTOP和NORMAL之间。其实当初并不是想设置为DOCK类型的,因为这样阴影也会在窗口上方,我希望的是阴影在普通程序下方,在桌面上方。今天曹哥来讲窗管的一些坑,讲到窗管是如何控制窗口的,我的阴影其实是被Qt注册成_NET_WM_STATE_BELOW了。这里可以看到一些type的介绍。 在_NET_WM_STATE中一共有这么几个类型: _NET_WM_STATE_MODAL, ATOM_NET_WM_STATE_STICKY, ATOM_NET_WM_STATE_MAXIMIZED_VERT, ATOM_NET_WM_STATE_MAXIMIZED_HORZ, ATOM_NET_WM_STATE_SHADED, ATOM_NET_WM_STATE_SKIP_TASKBAR, ATOM_NET_WM_STATE_SKIP_PAGER, ATOM_NET_WM_STATE_HIDDEN, ATOM_NET_WM_STATE_FULLSCREEN, ATOM_NET_WM_STATE_ABOVE, ATOM_NET_WM_STATE_BELOW, ATOM_NET_WM_STATE_DEMANDS_ATTENTION, ATOM 如果程序被注册成_NET_WM_STATE_BELOW,则会被放置在DESKTOP之上的一层。不是很清楚Qt是出于什么策略,才把我的阴影注册为这个状态,反而是刚好满足了我的需求。 这要多谢曹哥了,我才终于明白了为什么会这样,以及以后如何正确的设置type。","categories":[],"tags":[]},{"title":"TKL主题优化 -<转>","slug":"TKL主题优化-转","date":"2017-08-19T21:05:54.000Z","updated":"2024-04-15T05:09:54.954Z","comments":true,"path":"2017/08/20/TKL主题优化-转/","permalink":"https://blog.justforlxz.com/2017/08/20/TKL%E4%B8%BB%E9%A2%98%E4%BC%98%E5%8C%96-%E8%BD%AC/","excerpt":"","text":"http://suninuni.com/2015/09/30/frontend/hexo/hexo-config/ 这个主题确实挺好的,我也魔改了一部分来达成自己的目的,添加tags是看的这篇文章。","categories":[],"tags":[{"name":"Hexo","slug":"Hexo","permalink":"https://blog.justforlxz.com/tags/Hexo/"}]},{"title":"debug了两天,只删了一行代码","slug":"debug了两天,只删了一行代码","date":"2017-08-16T10:25:39.000Z","updated":"2024-04-15T05:09:54.958Z","comments":true,"path":"2017/08/16/debug了两天,只删了一行代码/","permalink":"https://blog.justforlxz.com/2017/08/16/debug%E4%BA%86%E4%B8%A4%E5%A4%A9%EF%BC%8C%E5%8F%AA%E5%88%A0%E4%BA%86%E4%B8%80%E8%A1%8C%E4%BB%A3%E7%A0%81/","excerpt":"前言: 项目一定要留一些文档!! 修bug前一定要知道所有的流程!!!","text":"前言: 项目一定要留一些文档!! 修bug前一定要知道所有的流程!!! 这两天一直在修一个用户切换的bug,众所周知,deepin的多用户切换一直都不是正常工作的,确切来说是压根没有正常工作,还好用户不是经常切换,不然早就收到一大波的报告了。 dde-session-ui项目中包含了以下软件: dde-lock dde-shutdown dde-osd lightdm-deepin-greeter dde-switchtogreeter dde-suspend-dialog dde-warning-dialog dde-welcome dde-wm-chooser dde-lowpower dde-offline-upgrader 大部分项目根据名字就可以知道是做什么的,这是一个软件组的集合。而dde-lock和lightdm-deepin-greeter二者有大量重复的功能和代码,这是它俩的工作性质决定的。 lightdm-deepin-greeter: display-manager启动的实体,登录的界面是它负责的。 dde-lock: 用户层面的屏幕锁定,基于我们的设计,和lightdm-deepin-greeter是大致相同的布局。 而且都包含了用户密码的验证,用户的切换,但是二者工作的层面是不同的,为了方便切换,就有了dde-switchtogreeter,用来协调二者的工作,只需要提供用户名就可以切换。 然而,虽然这样的想法是很好的,可是当初并没有人写文档,随着人员的变动,现在公司应该没有一个人是比较完整了解整个的工作流程了,用户切换的bug也就这样被留下来了。 上次修复用户切换的问题,是发现登录以后lightdm-deepin-greeter没有退出,由于不是很清楚linux的登录流程,再加上代码中有不工作的退出代码,当时就改好了退出的问题,这样就引入了第二个问题,而这个问题,就导致了两天三个人在一直查找问题所在。 这次的问题是发现一直切换greeter,会导致Xorg一直在开新的display,这就很奇怪了,正常来说是不会一直创建才对。 最开始以为是dde-switchtogreeter的问题,毕竟切换是它在做,dde-switchtogreeter是单文件的c代码,代码没有任何的说明,真的是为切换而生,我从main函数开始自己走了好几遍的流程,一边看着d-feet的数据来验证,然而只发现了一个小问题,整个代码是没啥问题的。 最后在后端大佬的帮助下,知道了display-manager会自己退掉greeter,不需要自己退,然后我就想起来了以前改的地方,赶紧把退出代码删掉,重新编译,问题得到了解决。 如果我知道display-manager的工作流程,也许这个问题就不会拖两天了。","categories":[],"tags":[]},{"title":"正常的流程在界面上却是bug","slug":"正常的流程在界面上却是bug","date":"2017-08-14T23:43:43.000Z","updated":"2024-04-15T05:09:55.100Z","comments":true,"path":"2017/08/15/正常的流程在界面上却是bug/","permalink":"https://blog.justforlxz.com/2017/08/15/%E6%AD%A3%E5%B8%B8%E7%9A%84%E6%B5%81%E7%A8%8B%E5%9C%A8%E7%95%8C%E9%9D%A2%E4%B8%8A%E5%8D%B4%E6%98%AFbug/","excerpt":"排查了一天,最后终于确认了流程,知道了问题所在,不得不说,dde-session-ui这个项目太需要一个文档了,要把工作流程写的非常详细才可以。","text":"排查了一天,最后终于确认了流程,知道了问题所在,不得不说,dde-session-ui这个项目太需要一个文档了,要把工作流程写的非常详细才可以。 上午收到了一条新任务,是龙芯上新安装的系统需要输入两次密码才可以登录,没有错误提示。近期并没有什么太大的改动,无非是给龙芯也用上了简单重构过的dde-session-ui,怎么会导致这样的问题。 由于是新安装的系统才会发生,而且是现象一旦发生,就无法重现,这让我头有点大,怎么会有这样的神奇的事情,而且日志中很正常,没有收到message导致密码框被清空。我提交了一个添加了更多日志的,然后重装的龙芯的系统(龙芯重装一次要半个小时),等重装完了,切换到tty去安装这个包,然后重启lightdm,让我输入密码回车以后,密码消失,我赶紧去看日志,但是日志中并没有我的输出,回车以后肯定会有的一行输出也没有(内心OS:What the fuck is that?) 我又回去看验证的流程,并没有发现有什么不对的地方,而且是近期才有的,我在自己电脑上使用了龙芯的编译参数,打了一个deb包,并没有发生和龙芯一样的情况(这里并不需要,既然是新安装的系统才会发生,在旧系统上是无法重现的)。 再然后我暂时没有管这个,先去修其他bug了。忙完以后,我去问了一下其他大佬,大佬给我提了几条让我去看看,是不是起了两个lightdm-deepin-greeter进程,确认一下使用的二进制是不是你加了log的。(然后我又重装龙芯了),之后确认了是我的二进制,也没有起两个进程。但是ps中有两行输出,我以为是起了两个,就让后端大佬看了一下,后端大佬告诉我说一个是shell的进程,一个是本体,还是只有一个进程存在的。我彻底懵逼了,然后后端大佬告诉我,是不是greeter进程写入什么了,之后的验证中内容已经存在,所以就不会重现了。 其实这个我也想过,但是没考虑太深,greeter并没有操作文件,但是大佬这么一说,我想到有一些dbus的调用,是有写入文件的,然后我把/var/lib/lightdm/lightdm-deepin-greeter目录给删除了,完美重现。 我的天啊,排查了快一天,居然是这个目录在新装的系统上没有,所以回车登录以后收到了来自dbus的switchToUser,界面重启导致的内容消失,根本不是收到了Message才被清空的,所以我的log也没有打印出来。 知道了如何重现,可是要怎么修复呢,似乎在greeter上并不能修复,只能去改dde-daemon中LockService,如果文件不存在,就不要发送userChanged的信号。(流程是读取这个文件的信息,和传入的参数进行对比,但是文件是空的,所以被认为不是同一个账户,就发送了信号,也导致了界面上重启,以后无法重现是因为里面已经有内容了)。 就这样,一个流程很正确,但是表现到界面上时就成了一个bug的问题被解决了。写下这篇内容是为了记录我如何解决对我来说很棘手的问题,其实这个问题并不是很困难,但是对整个工作的流程不是很熟悉,导致浪费了大量的时间在非关键点处理,有空要写一些文档了。","categories":[],"tags":[]},{"title":"在deepin上使用dnsmasq来解决dns解析缓慢","slug":"在deepin上使用dnsmasq来解决dns解析缓慢","date":"2017-08-11T06:07:26.000Z","updated":"2024-04-15T05:09:55.097Z","comments":true,"path":"2017/08/11/在deepin上使用dnsmasq来解决dns解析缓慢/","permalink":"https://blog.justforlxz.com/2017/08/11/%E5%9C%A8deepin%E4%B8%8A%E4%BD%BF%E7%94%A8dnsmasq%E6%9D%A5%E8%A7%A3%E5%86%B3dns%E8%A7%A3%E6%9E%90%E7%BC%93%E6%85%A2/","excerpt":"其实这个问题影响并不是很大,只是稍微的增加一点点访问速度,缓存这东西有利有弊。","text":"其实这个问题影响并不是很大,只是稍微的增加一点点访问速度,缓存这东西有利有弊。 在写完这篇文章以后,我就不用dnsmasq了,现在用的是github上的Pcap_DNSProxy。用来防止dns污染的。 根据https://stackoverflow.com/questions/11020027/dns-caching-in-linux 中回答者提供的信息来看,linux发行版是不提供dns解析缓存的,上面提到的nscd也不在deepin的预装列表中,所以我们只能自己动手丰衣足食了。 首先安装口碑较好的dnsmasq,来为我们提供dns缓存。 sudo apt install dnsmasq 如果是deepin最新的2015.4.1版本中安装,安装结束会提醒一个错误,这个错误的解决办法来自https://stackoverflow.com/questions/16939306/i-cant-restart-my-dnsmasq-service-so-my-fog-server-wont-work.这个错误似乎是因为/usr/share/dns/root.ds文件更新后结构导致的错误。 编辑/etc/init.d/dnsmasq,并找到 ROOT_DS="/usr/share/dns/root.ds"if [ -f $ROOT_DS ]; then DNSMASQ_OPTS="$DNSMASQ_OPTS `sed -e s/". IN DS "/--trust-anchor=.,/ -e s/" "/,/g $ROOT_DS | tr '\\n' ' '`" fi 修改为 ROOT_DS="/usr/share/dns/root.ds"if [ -f $ROOT_DS ]; then DNSMASQ_OPTS="$DNSMASQ_OPTS `sed -e s/".*IN[[:space:]]DS[[:space:]]"/--trust-anchor=.,/ -e s/"[[:space:]]"/,/g $ROOT_DS | tr '\\n' ' '`" fi 当错误解决以后,我们手动重启一下dnsmasq的systemd服务。 sudo systemctl restart dnsmasq deepin的/etc/resolv.conf来自/etc/NetworkManager/resolv.conf.是一个软连接。我采取的行为是删除这个文件,重新创建。 sudo rm /etc/resolv.confsudo vim /etc/resolv.conf 然后写入本地地址当做dns地址。 nameserver 127.0.0.1 dnsmasq是一个本地的dns和dhcp服务器,当我们在上面成功启动dnsmasq以后,个人系统中就已经在提供dns服务了,所以本机使用回环地址来表明dns服务器就是本机,所有的dns查询都会发送到本机的dnsmasq中。 如果需要额外添加dns服务器,做法来自https://wiki.archlinux.org/index.php/Dnsmasq#More_than_three_nameservers. 创建一个 /etc/resolv.dnsmasq.conf,写入其他dns服务器的地址。 nameserver 114.114.114.114 然后编辑 /etc/dnsmasq.conf,找到resolv-file字段,改为 resolv-file=/etc/resolv.dnsmasq.conf 然后重启dnsmasq。 验证的话通过dig命令。 dig blog.mkacg.com 通过执行两次来判断,Query time在第二次查询是为0 msec。","categories":[],"tags":[]},{"title":"PPA","slug":"PPA","date":"2017-07-24T08:07:50.000Z","updated":"2024-04-15T05:09:54.952Z","comments":true,"path":"2017/07/24/PPA/","permalink":"https://blog.justforlxz.com/2017/07/24/PPA/","excerpt":"","text":"也许需要安装dirmngr maybe you need install dirmngr 追加内容到/etc/apt/sources.list Append content to /etc/apt/sources.list deb [arch=amd64] https://packages.mkacg.com panda main 导入key import key sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3BBF73EE77F2FB2A","categories":[],"tags":[]},{"title":"topbar PPA","slug":"topbar-PPA","date":"2017-07-20T05:37:51.000Z","updated":"2024-04-15T05:09:55.038Z","comments":true,"path":"2017/07/20/topbar-PPA/","permalink":"https://blog.justforlxz.com/2017/07/20/topbar-PPA/","excerpt":"自己搭了一个仓库,提供deepin-topbar及相关依赖的包。 I created a repository,provide deepin-topbar and dependencies.","text":"自己搭了一个仓库,提供deepin-topbar及相关依赖的包。 I created a repository,provide deepin-topbar and dependencies. 也许需要安装dirmngr maybe you need install dirmngr 追加内容到/etc/apt/sources.listAppend content to /etc/apt/sources.list deb [arch=amd64] https://packages.mkacg.com panda main 导入keyimport key sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys 3BBF73EE77F2FB2A 刷新列表,进行安装then, refresh list and install sudo apt update && sudo apt install deepin-topbar","categories":[],"tags":[]},{"title":"webhook","slug":"webhook","date":"2017-07-13T18:52:20.000Z","updated":"2024-04-15T05:09:55.093Z","comments":true,"path":"2017/07/14/webhook/","permalink":"https://blog.justforlxz.com/2017/07/14/webhook/","excerpt":"","text":"blog现在是用hexo,放在自己的code网站上。 code是用无闻大大的gogs搭建的,跑在台式机的docker中,本机跑了很多docker服务,有hexo,有aria2c,有gogs,还有个webserver caddy。 caddy这东西还是基友 不爱写博客的mioto推荐给我的,之前我一直是用nginx的,那配置文件太复杂了,根本玩不来。 写一篇文章,会先提交到code,然后触发webhook,caddy会拉取code中的文章,由于是静态的,所以不需要处理其他的,只需要拉取最新的就可以了。 caddy的配置 blog.mkacg.com { gzip redir 301 { if {>X-Forwarded-Proto} is http / https://{host}{uri} } tls [email protected] root /srv/www/blog git code.mkacg.com/kirigayakazushin/blog { path /srv/www/blog branch gh-pages hook /_webhook xxx hook_type gogs then git pull }} gogs上只需要创建一个webhook,地址填写成caddy中的hook地址,加密填写hook后的xxx即可,加密自己设置。 然后就可以提交了。 提交会触发push操作,gogs会根据设置的webhook中的规则,执行和push相关的webhook,webhook会向指定的url发送POST操作,发送的内容中包含了相关信息,caddy会根据相关信息,来处理webhook,执行你规定的操作。","categories":[],"tags":[{"name":"linux","slug":"linux","permalink":"https://blog.justforlxz.com/tags/linux/"}]},{"title":"SAOUTILS","slug":"SAOUTILS","date":"2017-07-03T22:20:00.000Z","updated":"2024-04-15T05:09:54.952Z","comments":true,"path":"2017/07/04/SAOUTILS/","permalink":"https://blog.justforlxz.com/2017/07/04/SAOUTILS/","excerpt":"","text":"鼠标手势还没想好要怎么实现,流程无法完全确认,这项稍后再做。 主界面有两层构成,半透明全屏黑色背景和菜单。 菜单较为复杂,除了左边是一个大面板,其他部分全部都可以使用一种方式实现。使用Qt的QAbstractItemDelegate、QListView来做列表和界面绘制。 右边则是无限展开的菜单。 每层菜单只是用一个对象,和topbar dock的popup window一样。点击每层菜单的时候,计算下一项要显示的位置。 主界面应该是只有左右两部分,除了左边的大面板,右侧全部都是相同结构的菜单,只不过在功能上略有不同。 点击的时候,所有菜单对鼠标点击的地点进行坐标计算,如果在鼠标右侧,则隐藏。如果要显示的菜单是自己,不隐藏。点击时开始timer,松开时停止,timeout以后显示选项。 保持最后一个菜单在最中间的位置,主界面向左或向右用动画移动固定长度。","categories":[],"tags":[{"name":"linux","slug":"linux","permalink":"https://blog.justforlxz.com/tags/linux/"}]},{"title":"Topbar","slug":"Topbar","date":"2017-06-25T18:32:35.000Z","updated":"2024-04-15T05:09:54.954Z","comments":true,"path":"2017/06/26/Topbar/","permalink":"https://blog.justforlxz.com/2017/06/26/Topbar/","excerpt":"topbar的架构参考的是dde-dock,就是一个精简的dock,只有一个方向,一个位置,没有右键菜单,只有插件类型。","text":"topbar的架构参考的是dde-dock,就是一个精简的dock,只有一个方向,一个位置,没有右键菜单,只有插件类型。 计划是用来支持各种各样的方便的插件,比如: 活动窗口指示器 多媒体控制器 电源控制 时间控制 计划要完成有: shadowsocks vpn控制器 系统资源监视器 剩下的计划待完成的插件由于各种原因,开发比较难,还需要学习一部分知识才可以完成。","categories":[],"tags":[{"name":"linux","slug":"linux","permalink":"https://blog.justforlxz.com/tags/linux/"}]},{"title":"ArchLinux运行steam出现缺少LibGL--steam libGL error: failed to load driver: swrast","slug":"ArchLinux运行steam出现缺少LibGL-steam-libGL-error-failed-to-load-driver-swrast","date":"2016-07-14T23:18:53.000Z","updated":"2024-12-31T05:44:23.270Z","comments":true,"path":"2016/07/15/ArchLinux运行steam出现缺少LibGL-steam-libGL-error-failed-to-load-driver-swrast/","permalink":"https://blog.justforlxz.com/2016/07/15/ArchLinux%E8%BF%90%E8%A1%8Csteam%E5%87%BA%E7%8E%B0%E7%BC%BA%E5%B0%91LibGL-steam-libGL-error-failed-to-load-driver-swrast/","excerpt":"其实arch的wiki已经提到了,而且这个问题是比较常见的,只需要删除steam的库就行。 wiki原文链接","text":"其实arch的wiki已经提到了,而且这个问题是比较常见的,只需要删除steam的库就行。 wiki原文链接 删除运行库运行此命令,删除在issues上已知的运行库问题: find ~/.steam/root/ \\( -name "libgcc_s.so*" -o -name "libstdc++.so*" -o -name "libxcb.so*" -o -name "libgpg-error.so*" \\) -print -delete 如果上面的命令不工作,则再次运行上面的命令,然后运行此命令。 find ~/.local/share/Steam/ \\( -name "libgcc_s.so*" -o -name "libstdc++.so*" -o -name "libxcb.so*" -o -name "libgpg-error.so*" \\) -print -delete","categories":[],"tags":[{"name":"实验室","slug":"实验室","permalink":"https://blog.justforlxz.com/tags/%E5%AE%9E%E9%AA%8C%E5%AE%A4/"}],"author":"kirigaya"},{"title":"docker-hexo","slug":"docker-hexo","date":"2016-07-14T20:53:24.000Z","updated":"2024-12-31T05:44:23.271Z","comments":true,"path":"2016/07/15/docker-hexo/","permalink":"https://blog.justforlxz.com/2016/07/15/docker-hexo/","excerpt":"引用一下基友的话 最开始接触 Hexo 的时候是在 Windows 下, 安装过程还算顺利, 因此在初期还整理了6篇关于 Hexo 博客的搭建教程. 后来转投 Linux 大法, 期间重装电脑无数次, 每一次安装 Hexo 所需要的 nodejs, 和各种插件的时候都是闹心的过程, 玩的多了自然就熟了, Linux 下的安装基本没问题了.","text":"引用一下基友的话 最开始接触 Hexo 的时候是在 Windows 下, 安装过程还算顺利, 因此在初期还整理了6篇关于 Hexo 博客的搭建教程. 后来转投 Linux 大法, 期间重装电脑无数次, 每一次安装 Hexo 所需要的 nodejs, 和各种插件的时候都是闹心的过程, 玩的多了自然就熟了, Linux 下的安装基本没问题了. 然后入职公司, 公司配了 Mac Pro 又需要安装 Nodejs, 以及各种插件, 人傻搞不定啊.., 晚上迷迷糊糊的还 rm -rf /usr/bin 了.., 所以决定放弃在实体机安装 Nodejs 的想法转战到了 Docker. 来自mashiro.io 他后来更新了一下dockerfile,他觉得以前的思路是错的,现在他要把hexo封装进docker当做工具,又写了几个alias。安装docker,然后pull镜像 docker pull kirigayakazushin/docker-hexo pull镜像以后,写入alias,将docker-hexo当做本地工具。 alias hexo='docker run -it --rm -p 4000:4000 -v $PWD:/Hexo -v $HOME/.gitconfig:/home/docker/.gitconfig -v $HOME/.ssh:/home/docker/.ssh kirigayakazushin/docker-hexo hexo' alias npm='docker run -it --rm -v $PWD:/Hexo kirigayakazushin/docker-hexo npm' alias ddnone="docker images | grep none | awk '{print \\$3}' |xargs docker rmi" 然后执行一下命令 npm install hexo-generator-sitemap --save \\ && npm install hexo-generator-feed --save \\ && npm install hexo-deployer-git --save 把以上内容写进rc文件,我是用zsh的,所以写入~/.zshrc。 接下来基本演示一下使用方式。","categories":[],"tags":[{"name":"实验室","slug":"实验室","permalink":"https://blog.justforlxz.com/tags/%E5%AE%9E%E9%AA%8C%E5%AE%A4/"}],"author":"kirigaya"},{"title":"docker-aria2c","slug":"docker-aria2c","date":"2016-05-31T14:43:54.000Z","updated":"2024-12-31T05:44:23.271Z","comments":true,"path":"2016/05/31/docker-aria2c/","permalink":"https://blog.justforlxz.com/2016/05/31/docker-aria2c/","excerpt":"该项目是将aria2c封装进docker并提供服务。 docker pull kirigayakazushin/docker-aria2c","text":"该项目是将aria2c封装进docker并提供服务。 docker pull kirigayakazushin/docker-aria2c 下载好镜像,然后保存一份运行 docker run -p 6800:6800 --name docker-aria2c -d \\ -v {下载目录的绝对路径}:/aria2/downloads \\ imashiro/docker-aria2c 打开浏览器,访问http://yaaw.qiniudn.com/输入 http://token:[email protected]:6800/jsonrpc 注意,暂时还无法处理文件的所有权,目前下载好的文件归属root。","categories":[],"tags":[{"name":"实验室","slug":"实验室","permalink":"https://blog.justforlxz.com/tags/%E5%AE%9E%E9%AA%8C%E5%AE%A4/"}],"author":"kirigaya"},{"title":"aria2配置","slug":"aria2配置","date":"2016-05-25T15:56:00.000Z","updated":"2024-12-31T05:44:23.270Z","comments":true,"path":"2016/05/25/aria2配置/","permalink":"https://blog.justforlxz.com/2016/05/25/aria2%E9%85%8D%E7%BD%AE/","excerpt":"安装好aria2,然后执行一下内容 $ sudo nano /etc/systemd/user/aria2.service [Unit] Description=Aria2 Service After=network.target [Service] ExecStart=/usr/bin/aria2c --enable-rpc --rpc-listen-all=true --rpc-secret=secret --rpc-allow-origin-all --conf-path=/home/用户名字/.config/aria2/aria2.conf --input-file /home/用户名字/.config/aria2/session.lock --disk-cache=100M --max-overall-download-limit=0 --split=10 --max-connection-per-server=10 --max-concurrent-downloads=4 --dir=/home/用户名字/Downloads/ [Install] WantedBy=default.target","text":"安装好aria2,然后执行一下内容 $ sudo nano /etc/systemd/user/aria2.service [Unit] Description=Aria2 Service After=network.target [Service] ExecStart=/usr/bin/aria2c --enable-rpc --rpc-listen-all=true --rpc-secret=secret --rpc-allow-origin-all --conf-path=/home/用户名字/.config/aria2/aria2.conf --input-file /home/用户名字/.config/aria2/session.lock --disk-cache=100M --max-overall-download-limit=0 --split=10 --max-connection-per-server=10 --max-concurrent-downloads=4 --dir=/home/用户名字/Downloads/ [Install] WantedBy=default.target 注意以上内容需要把用户名字更改成自己的 在用户目录新建三个文件 touch ~/.config/aria2.conf touch ~/.config/aria2.session touch ~/.config/session.lock ~/.config/aria2.conf 里面需要填写以下内容,其他两个文件保持空。 dir=下载目录【需要自行修改】 enable-rpc=true 启动服务 systemctl --user enable aria2 systemctl --user start aria 浏览器打开:http://yaaw.qiniudn.com/将服务器地址改成 http://token:[email protected]:6800/jsonrpc 然后应该页面的右上角就显示网速了。","categories":[],"tags":[{"name":"教程","slug":"教程","permalink":"https://blog.justforlxz.com/tags/%E6%95%99%E7%A8%8B/"}],"author":"kirigaya"},{"title":"linux下安装vmware及archlinux的安装和配置","slug":"linux下安装vmware及archlinux的安装和配置","date":"2016-04-10T09:02:51.000Z","updated":"2024-12-31T05:44:23.271Z","comments":true,"path":"2016/04/10/linux下安装vmware及archlinux的安装和配置/","permalink":"https://blog.justforlxz.com/2016/04/10/linux%E4%B8%8B%E5%AE%89%E8%A3%85vmware%E5%8F%8Aarchlinux%E7%9A%84%E5%AE%89%E8%A3%85%E5%92%8C%E9%85%8D%E7%BD%AE/","excerpt":"","text":"视频中给出了vmware的下载地址和安装过程,系统的下载我也会演示一遍。这篇教程会一篇完成,从安装到配置和美化,顺便也总结一下我的成果。由于我已经安装过一次vmware了,所以有个脚本的地方没有出现,输入界面上的提示信息即可。宿舍太乱,所以就没有录麦克风,操作我会尽量慢一些,然后打字讲述。这次大更。。gtk主题还没更新上来,所以界面好丑。 = =。安装完成以后安装源里面的vmware-patch。","categories":[],"tags":[{"name":"教程","slug":"教程","permalink":"https://blog.justforlxz.com/tags/%E6%95%99%E7%A8%8B/"}],"author":"kirigaya"},{"title":"Archlinux 添加漂亮的字体","slug":"font-config","date":"2016-04-08T08:54:26.000Z","updated":"2024-12-31T05:44:23.271Z","comments":true,"path":"2016/04/08/font-config/","permalink":"https://blog.justforlxz.com/2016/04/08/font-config/","excerpt":"该教程不能保证适用于所有人的情况。字体也不是配置,而是补充了字体。使用的是第三方的源。","text":"该教程不能保证适用于所有人的情况。字体也不是配置,而是补充了字体。使用的是第三方的源。 打开/etc/pacman.conf文件,添加以下内容到最底部。 [infinality-bundle] SigLevel = Never Server = http://bohoomil.com/repo/$arch [infinality-bundle-multilib] SigLevel = Never Server = http://bohoomil.com/repo/multilib/$arch [infinality-bundle-fonts] SigLevel = Never Server = http://bohoomil.com/repo/fonts 执行安装命令: sudo pacman -S infinality-bundle infinality-bundle-multilib ibfonts-meta-extended #(用于64位系统) sudo pacman -S infinality-bundle ibfonts-meta-extended #(用于32位系统) 如果有遇到错误,可以手动添加hosts: 188.226.219.173 bohoomil.com 会出现很多冲突,选择Y,然后安装。如果中断了,重新执行安装命令。 来自:如何给任意一款 Linux 发行版添加漂亮的字体-桌面应用|Linux.中国-开源社区","categories":[],"tags":[{"name":"教程","slug":"教程","permalink":"https://blog.justforlxz.com/tags/%E6%95%99%E7%A8%8B/"}],"author":"kirigaya"},{"title":"My Life","slug":"my-life","date":"2016-03-25T03:56:08.000Z","updated":"2024-12-31T05:44:23.272Z","comments":true,"path":"2016/03/25/my-life/","permalink":"https://blog.justforlxz.com/2016/03/25/my-life/","excerpt":"这是我用markdown写的第一篇文章(水文),先来个自我介绍吧,我是小竹,对没错,是小竹,不是竹子,不是竹酱,更不是竹基。","text":"这是我用markdown写的第一篇文章(水文),先来个自我介绍吧,我是小竹,对没错,是小竹,不是竹子,不是竹酱,更不是竹基。 我玩linux应该有五六年了吧,初二的时候接触的,不过很多年都保持在换各种发行版上,并没有真正意义的玩。上了大学以后,接触的更多了,玩的也更嗨了。现在也用上arch+btrfs+uefi了,各种叼炸天。irc里面也经常学习【看别人装逼。依旧是英语渣渣,数学渣渣,看到win32api,我直接放弃win编程了,我的智商也就玩玩wpf了。下面就贴几张我的日常截图。 My Computer info kirigayaloveyousei@linuser OS: Arch Linux Kernel: x86_64 Linux 4.4.5-1-ARCH ackages: 1092 Shell: zsh 5.2 Resolution: 1366x768 WM: Mutter(DeepinGala) WM Theme: Adwaita GTK Theme: Arc-OSX [GTK2/3] Icon Theme: deepin Font: Noto Sans CJK SC Regular 10 CPU: Intel Core i5-4210U CPU @ 2.7GHz RAM: 1980MiB / 3861MiB","categories":[],"tags":[{"name":"日常","slug":"日常","permalink":"https://blog.justforlxz.com/tags/%E6%97%A5%E5%B8%B8/"}],"author":"kirigaya"},{"title":"标题","slug":"page","date":"2016-03-24T16:00:00.000Z","updated":"2024-12-31T05:44:23.271Z","comments":true,"path":"2016/03/25/page/","permalink":"https://blog.justforlxz.com/2016/03/25/page/","excerpt":"","text":"","categories":[],"tags":[{"name":"模板","slug":"模板","permalink":"https://blog.justforlxz.com/tags/%E6%A8%A1%E6%9D%BF/"}],"author":"kirigaya"}],"categories":[{"name":"技术分享","slug":"技术分享","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF%E5%88%86%E4%BA%AB/"},{"name":"心情随笔","slug":"心情随笔","permalink":"https://blog.justforlxz.com/categories/%E5%BF%83%E6%83%85%E9%9A%8F%E7%AC%94/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/categories/Vue/"},{"name":"Solution","slug":"Vue/Solution","permalink":"https://blog.justforlxz.com/categories/Vue/Solution/"},{"name":"ReactNative","slug":"ReactNative","permalink":"https://blog.justforlxz.com/categories/ReactNative/"},{"name":"Solution","slug":"ReactNative/Solution","permalink":"https://blog.justforlxz.com/categories/ReactNative/Solution/"},{"name":"deepin","slug":"deepin","permalink":"https://blog.justforlxz.com/categories/deepin/"},{"name":"Solution","slug":"Solution","permalink":"https://blog.justforlxz.com/categories/Solution/"},{"name":"development tools","slug":"development-tools","permalink":"https://blog.justforlxz.com/categories/development-tools/"},{"name":"neovim","slug":"neovim","permalink":"https://blog.justforlxz.com/categories/neovim/"},{"name":"photos 开发笔记","slug":"photos-开发笔记","permalink":"https://blog.justforlxz.com/categories/photos-%E5%BC%80%E5%8F%91%E7%AC%94%E8%AE%B0/"},{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/categories/Linux/"},{"name":"Game","slug":"Game","permalink":"https://blog.justforlxz.com/categories/Game/"},{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/categories/Web/"},{"name":"技术","slug":"技术","permalink":"https://blog.justforlxz.com/categories/%E6%8A%80%E6%9C%AF/"},{"name":"HelloMetal系列","slug":"HelloMetal系列","permalink":"https://blog.justforlxz.com/categories/HelloMetal%E7%B3%BB%E5%88%97/"},{"name":"Vue","slug":"Web/Vue","permalink":"https://blog.justforlxz.com/categories/Web/Vue/"},{"name":"Vuex","slug":"Web/Vue/Vuex","permalink":"https://blog.justforlxz.com/categories/Web/Vue/Vuex/"},{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/categories/Wayland/"},{"name":"leetcode算法","slug":"leetcode算法","permalink":"https://blog.justforlxz.com/categories/leetcode%E7%AE%97%E6%B3%95/"},{"name":"技术","slug":"Linux/技术","permalink":"https://blog.justforlxz.com/categories/Linux/%E6%8A%80%E6%9C%AF/"},{"name":"优化","slug":"优化","permalink":"https://blog.justforlxz.com/categories/%E4%BC%98%E5%8C%96/"},{"name":"unit test","slug":"unit-test","permalink":"https://blog.justforlxz.com/categories/unit-test/"},{"name":"设计模式","slug":"设计模式","permalink":"https://blog.justforlxz.com/categories/%E8%AE%BE%E8%AE%A1%E6%A8%A1%E5%BC%8F/"},{"name":"年度总结","slug":"年度总结","permalink":"https://blog.justforlxz.com/categories/%E5%B9%B4%E5%BA%A6%E6%80%BB%E7%BB%93/"},{"name":"webpack","slug":"Web/webpack","permalink":"https://blog.justforlxz.com/categories/Web/webpack/"},{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/categories/C/"},{"name":"Deep Learning","slug":"Deep-Learning","permalink":"https://blog.justforlxz.com/categories/Deep-Learning/"},{"name":"Deepin Learning","slug":"Deepin-Learning","permalink":"https://blog.justforlxz.com/categories/Deepin-Learning/"}],"tags":[{"name":"uos","slug":"uos","permalink":"https://blog.justforlxz.com/tags/uos/"},{"name":"UOS, Vulkan, 图形学, 开发","slug":"UOS-Vulkan-图形学-开发","permalink":"https://blog.justforlxz.com/tags/UOS-Vulkan-%E5%9B%BE%E5%BD%A2%E5%AD%A6-%E5%BC%80%E5%8F%91/"},{"name":"dotfile","slug":"dotfile","permalink":"https://blog.justforlxz.com/tags/dotfile/"},{"name":"健康, 思考","slug":"健康-思考","permalink":"https://blog.justforlxz.com/tags/%E5%81%A5%E5%BA%B7-%E6%80%9D%E8%80%83/"},{"name":"C++, Wayland, 图形学","slug":"C-Wayland-图形学","permalink":"https://blog.justforlxz.com/tags/C-Wayland-%E5%9B%BE%E5%BD%A2%E5%AD%A6/"},{"name":"C++","slug":"C","permalink":"https://blog.justforlxz.com/tags/C/"},{"name":"k8s","slug":"k8s","permalink":"https://blog.justforlxz.com/tags/k8s/"},{"name":"vuex","slug":"vuex","permalink":"https://blog.justforlxz.com/tags/vuex/"},{"name":"reactnative","slug":"reactnative","permalink":"https://blog.justforlxz.com/tags/reactnative/"},{"name":"ios","slug":"ios","permalink":"https://blog.justforlxz.com/tags/ios/"},{"name":"deepin","slug":"deepin","permalink":"https://blog.justforlxz.com/tags/deepin/"},{"name":"kwin","slug":"kwin","permalink":"https://blog.justforlxz.com/tags/kwin/"},{"name":"Bash","slug":"Bash","permalink":"https://blog.justforlxz.com/tags/Bash/"},{"name":"Linux","slug":"Linux","permalink":"https://blog.justforlxz.com/tags/Linux/"},{"name":"Next.js","slug":"Next-js","permalink":"https://blog.justforlxz.com/tags/Next-js/"},{"name":"Web","slug":"Web","permalink":"https://blog.justforlxz.com/tags/Web/"},{"name":"Kitty","slug":"Kitty","permalink":"https://blog.justforlxz.com/tags/Kitty/"},{"name":"parallels desktop","slug":"parallels-desktop","permalink":"https://blog.justforlxz.com/tags/parallels-desktop/"},{"name":"neovim","slug":"neovim","permalink":"https://blog.justforlxz.com/tags/neovim/"},{"name":"dap","slug":"dap","permalink":"https://blog.justforlxz.com/tags/dap/"},{"name":"typescript","slug":"typescript","permalink":"https://blog.justforlxz.com/tags/typescript/"},{"name":"react","slug":"react","permalink":"https://blog.justforlxz.com/tags/react/"},{"name":"react native","slug":"react-native","permalink":"https://blog.justforlxz.com/tags/react-native/"},{"name":"docker","slug":"docker","permalink":"https://blog.justforlxz.com/tags/docker/"},{"name":"Game","slug":"Game","permalink":"https://blog.justforlxz.com/tags/Game/"},{"name":"Unity","slug":"Unity","permalink":"https://blog.justforlxz.com/tags/Unity/"},{"name":"Shader","slug":"Shader","permalink":"https://blog.justforlxz.com/tags/Shader/"},{"name":"javascript","slug":"javascript","permalink":"https://blog.justforlxz.com/tags/javascript/"},{"name":"jsx","slug":"jsx","permalink":"https://blog.justforlxz.com/tags/jsx/"},{"name":"kubernetes","slug":"kubernetes","permalink":"https://blog.justforlxz.com/tags/kubernetes/"},{"name":"Github Action","slug":"Github-Action","permalink":"https://blog.justforlxz.com/tags/Github-Action/"},{"name":"evil","slug":"evil","permalink":"https://blog.justforlxz.com/tags/evil/"},{"name":"Metal","slug":"Metal","permalink":"https://blog.justforlxz.com/tags/Metal/"},{"name":"GameEngine","slug":"GameEngine","permalink":"https://blog.justforlxz.com/tags/GameEngine/"},{"name":"vim","slug":"vim","permalink":"https://blog.justforlxz.com/tags/vim/"},{"name":"CSS","slug":"CSS","permalink":"https://blog.justforlxz.com/tags/CSS/"},{"name":"latex","slug":"latex","permalink":"https://blog.justforlxz.com/tags/latex/"},{"name":"Qt","slug":"Qt","permalink":"https://blog.justforlxz.com/tags/Qt/"},{"name":"systemd","slug":"systemd","permalink":"https://blog.justforlxz.com/tags/systemd/"},{"name":"JavaScript","slug":"JavaScript","permalink":"https://blog.justforlxz.com/tags/JavaScript/"},{"name":"Wayland","slug":"Wayland","permalink":"https://blog.justforlxz.com/tags/Wayland/"},{"name":"数组","slug":"数组","permalink":"https://blog.justforlxz.com/tags/%E6%95%B0%E7%BB%84/"},{"name":"算法","slug":"算法","permalink":"https://blog.justforlxz.com/tags/%E7%AE%97%E6%B3%95/"},{"name":"简单","slug":"简单","permalink":"https://blog.justforlxz.com/tags/%E7%AE%80%E5%8D%95/"},{"name":"VS Code","slug":"VS-Code","permalink":"https://blog.justforlxz.com/tags/VS-Code/"},{"name":"Windows","slug":"Windows","permalink":"https://blog.justforlxz.com/tags/Windows/"},{"name":"dde","slug":"dde","permalink":"https://blog.justforlxz.com/tags/dde/"},{"name":"go","slug":"go","permalink":"https://blog.justforlxz.com/tags/go/"},{"name":"CMake","slug":"CMake","permalink":"https://blog.justforlxz.com/tags/CMake/"},{"name":"GTest","slug":"GTest","permalink":"https://blog.justforlxz.com/tags/GTest/"},{"name":"CTest","slug":"CTest","permalink":"https://blog.justforlxz.com/tags/CTest/"},{"name":"2019","slug":"2019","permalink":"https://blog.justforlxz.com/tags/2019/"},{"name":"Vue","slug":"Vue","permalink":"https://blog.justforlxz.com/tags/Vue/"},{"name":"Webpack","slug":"Webpack","permalink":"https://blog.justforlxz.com/tags/Webpack/"},{"name":"TypeScript","slug":"TypeScript","permalink":"https://blog.justforlxz.com/tags/TypeScript/"},{"name":"CMake Linux","slug":"CMake-Linux","permalink":"https://blog.justforlxz.com/tags/CMake-Linux/"},{"name":"LNMP","slug":"LNMP","permalink":"https://blog.justforlxz.com/tags/LNMP/"},{"name":"Deepin","slug":"Deepin","permalink":"https://blog.justforlxz.com/tags/Deepin/"},{"name":"Program","slug":"Program","permalink":"https://blog.justforlxz.com/tags/Program/"},{"name":"Deep Learning","slug":"Deep-Learning","permalink":"https://blog.justforlxz.com/tags/Deep-Learning/"},{"name":"Deepin Learning","slug":"Deepin-Learning","permalink":"https://blog.justforlxz.com/tags/Deepin-Learning/"},{"name":"nVidia Cuda","slug":"nVidia-Cuda","permalink":"https://blog.justforlxz.com/tags/nVidia-Cuda/"},{"name":"Linux 填坑","slug":"Linux-填坑","permalink":"https://blog.justforlxz.com/tags/Linux-%E5%A1%AB%E5%9D%91/"},{"name":"Linux DTK","slug":"Linux-DTK","permalink":"https://blog.justforlxz.com/tags/Linux-DTK/"},{"name":"linux","slug":"linux","permalink":"https://blog.justforlxz.com/tags/linux/"},{"name":"Hexo","slug":"Hexo","permalink":"https://blog.justforlxz.com/tags/Hexo/"},{"name":"实验室","slug":"实验室","permalink":"https://blog.justforlxz.com/tags/%E5%AE%9E%E9%AA%8C%E5%AE%A4/"},{"name":"教程","slug":"教程","permalink":"https://blog.justforlxz.com/tags/%E6%95%99%E7%A8%8B/"},{"name":"日常","slug":"日常","permalink":"https://blog.justforlxz.com/tags/%E6%97%A5%E5%B8%B8/"},{"name":"模板","slug":"模板","permalink":"https://blog.justforlxz.com/tags/%E6%A8%A1%E6%9D%BF/"}]}