[{"content":"Notes on Self-Hosted Bookmark Service I mainly tried Karakeep, ArchiveBox and linkding, but in the end I chose the forked linkding-cn.\nMy Requirements Required Features Provide a browser extension for quickly saving webpage bookmarks Be able to tag bookmarks and search by tag Support importing/exporting Netscape HTML Optional Features Support webpage snapshots, and try to store them locally to avoid wasting archive.org resources Support browser extension snapshot capture, to avoid triggering CAPTCHA and firewall page issues Support recursive bookmarks, making it convenient to split and organize bookmarks by major category Support AI auto-tagging Karakeep It satisfies the following features:\nProvide a browser extension for quickly saving webpage bookmarks Be able to tag bookmarks and search by tag Support importing/exporting Netscape HTML Support webpage snapshots, and try to store them locally to avoid wasting archive.org resources Support snapshot capture through a browser extension, avoiding CAPTCHA and firewall page issues Support recursive bookmarks for convenient splitting and organizing bookmarks by major category Support AI auto-tagging On first try I felt the UI design was nice and the webpage also had mobile adaptation, but in the end I did not adopt it, for the following reasons.\nThe AI tagging feature is not easy to use\nThe AI tagging feature is not user-friendly in its default state. It often creates new tags even when there are already matching existing tags, and the newly created tags exist in multiple versions such as Simplified Chinese, Traditional Chinese and even Japanese characters.\nAlthough you can manually select the usable tag scope one by one and add model prompt lines in the settings to process them, it could have been made more convenient.\nThe webpage snapshot effect is average\nThe service captures webpage snapshots through a browser container, but this approach often triggers anti-crawling firewalls and CAPTCHA. Although the provided browser extension can also capture snapshots, for some unknown reason some webpages take very long to capture (apparently related to the number of multimedia resources), and the captured snapshots always lose their layout.\nArchiveBox Provide a browser extension for quick webpage bookmark saving Be able to tag bookmarks and search by tag Support importing/exporting Netscape HTML Support webpage snapshots, try to store them locally to avoid wasting archive.org resources Support snapshot capture through a browser extension, avoiding CAPTCHA and firewall page issues Support recursive bookmarks, making it convenient to split and organize bookmarks by major category Support AI auto-tagging Strictly speaking, the core purpose of this service is to archive pages, just like archive.org does; bookmarks are merely incidental. Therefore the pages are relatively simple and the configuration is relatively complex.\nThe main reason for not adopting it is still the snapshot capture having the same problem as Karakeep. Also, connecting the server seems to require a command to generate a token, which cannot be generated directly in the backend admin page.\nlinkding Support to provide a browser extension for quick bookmarking webpages Be able to tag bookmarks and search by tag Support importing/exporting Netscape HTML Support webpage snapshots, try to store them locally to avoid wasting archive.org resources Support browser extension to capture snapshots, avoiding CAPTCHA and firewall page problems Support recursive bookmarks for easy splitting and organizing bookmarks by major category Support AI auto-tagging I used it for a long time in the past, and to be honest it can be called a small and beautiful service.\nBut the biggest problem lies in the code architecture. Because the strings are hardcoded in code, multi-language has not made progress all along, Chinese tags are not sorted, and the one way to find a tag is to scan through it with your own eyes.\nFinal Choice — linkding-cn Provide a browser extension for quick collection of webpage bookmarks Be able to tag bookmarks and search by tag Support importing/exporting Netscape HTML Support webpage snapshots, try to store them locally to avoid wasting archive.org resources Support browser extension snapshot capture to bypass various anti-bot and firewall issues Support recursive bookmarks for organizing tips and organizing bookmarks by major category Support AI auto-tagging The final choice is linkding-cn, a fork of linkding. The latest version works well so far; it is lighter than Karakeep because it does not need a browser container to capture webpage snapshots, and its interface is more attractive than ArchiveBox\u0026rsquo;s.\nAs for snapshots, you can create a token in the website\u0026rsquo;s \u0026ldquo;Settings → Integrations\u0026rdquo; page, then download the SingleFile plugin, configure the plugin: save location → save to the REST form API, then configure the following in order:\nURL: such as https://linkding.example.com/api/bookmarks/singlefile/ Auth token: the token you just created File field name: file URL field name: url Then the flow to add a bookmark goes as follows:\nIf you only want to save webpage links, just use the original browser extension of linkding, connect to the server with your token configured in the extension settings and operate exactly the same If you want some websites to only save links and some to capture snapshots, you can first save with the linkding extension, then save with SingleFile plugin, which will automatically import the snapshot If you want all websites to automatically capture snapshots, enable Run with SingleFile after adding a new bookmark in the linkding browser extension, which will be automatically called when creating a new bookmark; old bookmarks need to be manually triggered. Besides, even if you do not perform the above configuration, the server side still collects snapshots. They will still encounter the same CAPTCHA and the firewall problems as Karakeep collects with the server-side.\nSummary Open source projects all have their own strengths, and finding a self-hosted project that fits you is still quite pleasant. The other projects are not bad; they are simply not suitable for me, for the reasons mentioned above.\nEven if some features still have imperfections for now, they have a large number of community contributors who will continue providing updates, fixing bugs and adding features. Moreover, the code is open source, and anything you are not satisfied with can be modified by yourself.\nThank you for all the hard work of the open source contributors, salute you🫡!\n","permalink":"https://www.333rd.net/en/posts/tech/self-hosted-bookmark-service-notes/","summary":"\u003ch1 id=\"notes-on-self-hosted-bookmark-service\"\u003eNotes on Self-Hosted Bookmark Service\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI mainly tried Karakeep, ArchiveBox and linkding, but in the end I chose the forked linkding-cn.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"my-requirements\"\u003eMy Requirements\u003c/h2\u003e\n\u003ch3 id=\"required-features\"\u003eRequired Features\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eProvide a browser extension for quickly saving webpage bookmarks\u003c/li\u003e\n\u003cli\u003eBe able to tag bookmarks and search by tag\u003c/li\u003e\n\u003cli\u003eSupport importing/exporting \u003ccode\u003eNetscape HTML\u003c/code\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch3 id=\"optional-features\"\u003eOptional Features\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eSupport webpage snapshots, and try to store them locally to avoid wasting archive.org resources\u003c/li\u003e\n\u003cli\u003eSupport browser extension snapshot capture, to avoid triggering CAPTCHA and firewall page issues\u003c/li\u003e\n\u003cli\u003eSupport recursive bookmarks, making it convenient to split and organize bookmarks by major category\u003c/li\u003e\n\u003cli\u003eSupport AI auto-tagging\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"karakeep\"\u003eKarakeep\u003c/h2\u003e\n\u003cp\u003eIt satisfies the following features:\u003c/p\u003e","title":"Notes on Self-Hosted Web Bookmark Service"},{"content":"Shift At Midnight: Who Is the Impostor? I recently saw a streamer playing this game, so I tried it with some friends. It was pretty fun; I wonder whether future updates will add more ways to play.\nHow the game works Running a gas station is hard work. You must keep the shelves stocked, sell enough goods each night to meet the quota, receive deliveries, and keep the store clean.\nWhile serving customers, question every one of them carefully. Some customers are not human—they are doppelgangers. If you let one slip through, it will return later in the shift and try to kill you and your friends… If you think someone is not human, execution on the spot is recommended; clean up afterward.\nIf you mess up, be prepared… When the doppelgangers return as terrifying monsters, you must set traps, barricade the doors and windows, and hide.\nIn Shift At Midnight, you work the night shift at a supermarket. After meeting the daily revenue target, you can spend the extra money on items.\nYour main task is to determine whether each customer is an impostor. Use the computer at the register to look up information, then talk with the customer and look for inconsistencies.\nOnce you identify an impostor, you must shoot “it”. Otherwise, when the shift ends, the impostors will attack.\nIn short, the core mechanic is the familiar “identify the impostor” format. Unlike games that punish a wrong decision immediately, this one gathers every impostor you spared during the day and punishes you all at once. The more you let through, the stronger the attack.\nWho is the impostor? On my first run I had no experience and spared a crowd of impostors, so the night invasion felt like a sudden zombie horde.\nBy the second run I had learned a few things:\nPay close attention to times mentioned in conversation. If they do not match the computer’s records, the customer is an impostor. Customers sometimes mention other NPCs. Look those NPCs up and check whether they are listed as “deceased” or do not exist. Watch physical details such as tattoos and scars. With practice, you can identify most impostors.\nI also found ShiftAtMidnightDB, which collects detailed NPC information and lists which NPCs are impostors and what clues reveal them. It is handy for making confident bets with friends 🤣.\nA few special tricks Multiplayer is recommended. Later on, one person cannot handle the register, gas station, cleaning, and purchasing alone.\nHere are a couple of item-duplication methods:\nFor items that respawn indefinitely at a spawn point, such as the register pistol, press E rapidly. You can produce up to as many items as you have inventory slots. After buying a single item, such as a shotgun, press E to pick it up and then tap G rapidly several times. You may throw out two identical items. The duplicate is exactly the same as the original. Have funヾ(≧▽≦*)o\n","permalink":"https://www.333rd.net/en/posts/life/shift-at-midnight/","summary":"\u003ch1 id=\"shift-at-midnight-who-is-the-impostor\"\u003eShift At Midnight: Who Is the Impostor?\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI recently saw a streamer playing this game, so I tried it with some friends. It was pretty fun; I wonder whether future updates will add more ways to play.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"how-the-game-works\"\u003eHow the game works\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003eRunning a gas station is hard work. You must keep the shelves stocked, sell enough goods each night to meet the quota, receive deliveries, and keep the store clean.\u003c/p\u003e","title":"Shift At Midnight: Who Is the Impostor?"},{"content":"Notes on Self-Hosted Chat Service I tried Stoat (formerly Revolt), Tailchat and Matrix, and found that there is no perfect solution.\nBackground When playing online games, for the sake of convenient communication, a chat software that supports the following features is needed:\nSupport multi-platform clients including the Web client At least support voice chat Besides satisfying basic chat needs, the operation should be user-friendly without too much learning cost Be able to complete self-hosting quickly, with clean and simple software library dependencies, without frequent forks or destructive updates Solution Selection After searching, the following three open source projects were settled on:\nStoat, formerly Revolt Tailchat Matrix Evaluation and Assessment All the projects were deployed with docker and tried out; in the end, the experience was hard to put into words.\nIn short, project forks or destructive updates mean that using a relatively complete older version makes you worry about future updates and migration issues, while the new version representing the future is missing a large number of basic features.\nThis is also the norm for large human engineering projects: the early design can hardly take all usage scenarios into account, the mid-term faces disagreements over various unforeseen problems, and in the end you either endure the stench of a big ball of mud, or start from scratch hoping to solve all the problems.\nHowever, for open source projects that are already short of resources and rely entirely on the enthusiasm of developers, often one mess becomes two messes\u0026hellip;\nThe following content is recorded as of 2026-07-01. Since all the projects are constantly being updated, please note the timeliness of this article.\nStoat Its predecessor Revolt received an infringement notice and was renamed Stoat for that reason. Whether in terms of UI style or design philosophy, this is a platform benchmarked against Discord.\nJudging from Revolt\u0026rsquo;s README.md, the last update was 9 months ago, so it is natural to prioritize deploying Stoat.\nFrom the actual experience, although most of the experience has already been made similar to Discord, in March of this year I found that audio calls were not yet supported. Judging from the official roadmap, voice chat was already implemented by the end of 2025, maybe the problem lies in my deployment.\nSpeaking of deployment, I have to complain: its docker configuration depends on more than a dozen images and several configuration files. Perhaps it is necessary for medium and large communities to rely on this many images to improve scalability and availability, but for small communities of fewer than 20 people, the maintenance cost outweighs the return of use.\nAll in all, the UI design and user experience are quite mature, and the community maintenance effort is acceptable. The audio and video features are not quite complete, but they should improve in the foreseeable future. End-to-end encryption is on the roadmap; the official position is that this feature involves quite a lot of modifications, and rather than shipping a bad implementation, it is better to bring it out once it is done well. The future is promising.\nTailchat Text chat is no problem, and audio/video chat can be supported through LiveKit. It is just that the UI design is still a bit immature, after all, there are not many core developers and energy is hard to spare.\nIt depends on a single-digit number of docker images. Although there are also many configuration files, as long as you follow the official documentation you can basically get it deployed quickly.\nHowever, deploying LiveKit is rather troublesome. If you want to save yourself the trouble, you can directly use the service offered by LiveKit; the free plan is light enough for voice calls, but not enough if you stay connected for long periods.\nCompared with Stoat\u0026rsquo;s complexity, Tailchat is more suitable for small and medium communities. Its author is Chinese, and the Chinese documentation is fairly complete. If you have no special requirements, it is sufficient for daily use.\nMatrix It is quite well known and used by EU governments as a platform for internal communication; it can be said to be one of the role models of open source projects.\nIts docker configuration is the least, and the various configuration files are also concise; it is almost ready to use out of the box.\nIf you do not deploy your own audio/video service images, you can use their audio/video service with the Element web client and the classic Android version. Audio calls are acceptable, but video calls are very blurry.\nThe newly released X version of the Android client seems to only be able to use a self-deployed audio/video service.\nThe official seems to be pushing the switch of audio/video calls from LiveKit to its self-developed MatrixRTC. Element had an article in February: Exploring MatrixRTC: Real time communication in rooms\nBut Matrix is not perfect either; the fragmentation of the open source community is very evident here.\nThere is a wide variety of clients. Besides the mainstream Element, there are several clients with different philosophies on different platforms, each with different levels of protocol support, resulting in clients with good-looking UIs lacking features, and clients with relatively complete features having very traditional UIs. Here I would call out the difference between Element\u0026rsquo;s newly released X version Android client and the traditional version.\nIf client fragmentation only affects the user experience, then server-side fragmentation greatly affects operations work.\nDifferent servers have different levels of protocol support and vastly different development resources, to the extent that you can see people online who, after using one server, want to migrate to another but have to understand the corresponding implementation themselves, manually write scripts to migrate the data, or even fail the migration.\nWorse still, some servers are directly discontinued, with developers moving on to other development work, leaving orphaned servers.\nIf you have no special requirements, to avoid this situation it is recommended to follow the crowd and choose the implementation with the most users.\nSummary The \u0026ldquo;single responsibility\u0026rdquo; theory of the Unix design philosophy has proven itself time and again. Looking only at the open source community, the software with the best user experience is almost always software that does only one core thing; the more ambitious the software, the more it faces problems of resource allocation and catering to diverse tastes.\nFor complex needs, it is almost impossible to find software that fully satisfies them, and at the same time, given the development cost and development cycle, it is also difficult to implement one yourself.\nPerhaps in the near future AI will really bring surprises; with the power of AI, you may be able to implement software that meets all your needs at controllable cost in manpower and time\u0026hellip;\n","permalink":"https://www.333rd.net/en/posts/tech/self-hosted-chat-service-notes/","summary":"\u003ch1 id=\"notes-on-self-hosted-chat-service\"\u003eNotes on Self-Hosted Chat Service\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI tried Stoat (formerly Revolt), Tailchat and Matrix, and found that there is no perfect solution.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"background\"\u003eBackground\u003c/h2\u003e\n\u003cp\u003eWhen playing online games, for the sake of convenient communication, a chat software that supports the following features is needed:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eSupport multi-platform clients including the Web client\u003c/li\u003e\n\u003cli\u003eAt least support voice chat\u003c/li\u003e\n\u003cli\u003eBesides satisfying basic chat needs, the operation should be user-friendly without too much learning cost\u003c/li\u003e\n\u003cli\u003eBe able to complete self-hosting quickly, with clean and simple software library dependencies, without frequent forks or destructive updates\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"solution-selection\"\u003eSolution Selection\u003c/h2\u003e\n\u003cp\u003eAfter searching, the following three open source projects were settled on:\u003c/p\u003e","title":"Notes on Self-Hosted Chat Service"},{"content":" Choosing the East\nThe sun and moon turn, west and then east; amid right and wrong, victory and defeat, we seek different paths toward the same goal.\nCounting on my fingers, the journey spans twenty-five thousand li; who dares swing the long red cord and bind the azure dragon?\nAgain and again the east wind blows through old trees; ten years of turbulence have been for the common good.\nMillions upon millions await the dawn; sweep away every harmful pest.\n","permalink":"https://www.333rd.net/en/posts/life/choosing-east/","summary":"\u003cdiv style=\"text-align:center; white-space: pre-line;\"\u003e\n    \u003cp\u003e\u003cstrong\u003eChoosing the East\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003eThe sun and moon turn, west and then east; amid right and wrong, victory and defeat, we seek different paths toward the same goal.\u003c/p\u003e\n\u003cp\u003eCounting on my fingers, the journey spans twenty-five thousand \u003cem\u003eli\u003c/em\u003e; who dares swing the long red cord and bind the azure dragon?\u003c/p\u003e\n\u003cp\u003eAgain and again the east wind blows through old trees; ten years of turbulence have been for the common good.\u003c/p\u003e","title":"Choosing the East"},{"content":"Someone answered the question of meaning and value in the last century:\nThe most precious thing a person has is life, and life is given only once. One should live so that, looking back, one will not regret having wasted the years in idleness, nor feel ashamed of having lived meanly and vulgarly.\nOf course, ordinary people like us do not necessarily possess such a lofty outlook. My own answer is the unity of knowledge and action.\nWhat does that mean? Your actions and your understanding of the world are consistent; you do not do things that betray yourself.\nIf you want to relax, you will not choose exhausting work. If you want happiness, you will not do something you dislike. Looking back later, you will not regret spending time on something “meaningless”. The happiness you felt was itself part of your life. Happiness is meaning. Learn to accept the time you spent without value, because value and meaning are not the same thing.\nWhen your actions and understanding are coherent, you will not consume yourself with regret over doing something you never wanted to do.\nSociety teaches countless people to demonstrate their value, preferably becoming a stock that keeps rising so they can justify their investors’ money. Few people tell you what your meaning is. That is the three ultimate philosophical questions:\nWho am I? Where did I come from? Where am I going?\nA simple answer is to do what you like and gain the happiness that belongs to you. A more meaningful choice is the one described in How the Steel Was Tempered, the same choice made by earlier generations.\nIn short, do not wait until years later, facing changed people and circumstances, to remember an unfinished outing and regret: “It will never be like those carefree days of youth.”\n","permalink":"https://www.333rd.net/en/posts/life/three-questions-of-life/","summary":"\u003cp\u003eSomeone answered the question of meaning and value in the last century:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe most precious thing a person has is life, and life is given only once. One should live so that, looking back, one will not regret having wasted the years in idleness, nor feel ashamed of having lived meanly and vulgarly.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eOf course, ordinary people like us do not necessarily possess such a lofty outlook. My own answer is \u003cstrong\u003ethe unity of knowledge and action\u003c/strong\u003e.\u003c/p\u003e","title":"Three Questions of Life"},{"content":"Using EasyTier and a Proxy at the Same Time On a phone, only one VPN can exist at a time, so EasyTier and a proxy cannot be used directly at the same time; here is a workaround.\nPreparation Here I will use EasyTier and v2rayN as examples. We also need any other proxy software; I used FlClash.\nSteps Both EasyTier and v2rayN need the tun mode turned off, that is, v2 stays in proxy-only mode.\nIn EasyTier\u0026rsquo;s advanced settings, enable the SOCKS5 server. Here port 10852 is used as an example; v2\u0026rsquo;s default port is 10808.\nThen create a yaml file at any path on the phone with the following content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 mixed-port: 7890 allow-lan: false mode: rule log-level: info proxies: - name: Proxy-EasyTier type: socks5 server: 127.0.0.1 port: 10852 udp: true # 启用 UDP 转发 - name: Proxy-v2rayN type: socks5 server: 127.0.0.1 port: 10808 udp: true # 启用 UDP 转发 proxy-groups: - name: Default type: select proxies: - Proxy-v2rayN rules: # 本地/局域网 UDP 直连（避免阻断内网 UDP），没啥用可以去掉 - IP-CIDR,127.0.0.0/8,DIRECT,no-resolve - IP-CIDR,192.168.0.0/16,DIRECT,no-resolve - IP-CIDR,172.16.0.0/12,DIRECT,no-resolve # 目标网段分流（含 UDP 转发） - IP-CIDR,10.0.0.0/8,Proxy-EasyTier,no-resolve # 其余全部走 Proxy-v2rayN（含 UDP） - MATCH,Default In \u0026ldquo;clash - profiles\u0026rdquo;, add a new file-based config and select it, then enable clash\u0026rsquo;s VPN to do the routing.\nHow It Works It is essentially using a third proxy software to do the routing. I tried it and TCP communication works fine, while UDP gets blocked; I am not sure whether it is a configuration problem, and I did not bother to keep fiddling with it\u0026hellip;\nI checked and it is indeed because EasyTier\u0026rsquo;s SOCKS5 does not support UDP:\nsocks5 udp over tcp Socks5 不支持 UDP协议 References Is there a way to simultaneously use a proxy and EasyTier networking on Android? ","permalink":"https://www.333rd.net/en/posts/tech/easytier-and-proxy-simultaneously/","summary":"\u003ch1 id=\"using-easytier-and-a-proxy-at-the-same-time\"\u003eUsing EasyTier and a Proxy at the Same Time\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eOn a phone, only one VPN can exist at a time, so EasyTier and a proxy cannot be used directly at the same time; here is a workaround.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"preparation\"\u003ePreparation\u003c/h2\u003e\n\u003cp\u003eHere I will use \u003cstrong\u003eEasyTier\u003c/strong\u003e and \u003cstrong\u003ev2rayN\u003c/strong\u003e as examples. We also need any other proxy software; I used \u003cstrong\u003eFlClash\u003c/strong\u003e.\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eBoth EasyTier and v2rayN need the \u003cstrong\u003etun\u003c/strong\u003e mode turned off, that is, v2 stays in proxy-only mode.\u003c/p\u003e","title":"Using EasyTier and a Proxy at the Same Time"},{"content":"EasyTier with frp Internal Network Traversal Background Foreign servers have high latency, which makes game streaming inconvenient; domestic servers have expensive bandwidth, which makes game streaming unaffordable. So I thought of using internal network traversal to open a public port\u0026hellip;\nPreparation Any internal network traversal service will do — only a single port is needed. Here I use the public 1.1.1.1:12345 as an example.\n⚠️ Note: For the local \u0026lt;IP:PORT\u0026gt; of the internal network traversal, be careful NOT to use 127.0.0.1 or localhost; you can use another internal address bound to your network interface, such as 192.168.1.23. Otherwise it will complain about a loopback address or something. Just use the port EasyTier listens on, which defaults to 11010.\nThe final result is:\n1 1.1.1.1:12345 -\u0026gt; 192.168.1.23:11010 Steps EasyTier CLI Recommended method: After enabling internal network traversal locally, you don\u0026rsquo;t need to change anything. For other devices to connect to this device, just add a shared node config: -p \u0026quot;tcp://1.1.1.1:12345\u0026quot;.\nI also discovered a parameter that can be set locally: --mapped-listeners tcp://1.1.1.1:12345. In this case, other devices don\u0026rsquo;t need to add a shared node config; they seem to auto-detect this public address. However, this hasn\u0026rsquo;t been strictly verified, so I\u0026rsquo;ll leave it at that for now.\nEasyTier GUI Client After enabling internal network traversal locally, in the app: Network Mode — Standalone, then do the same as above. For other devices to connect to this device, just add a shared node config: tcp://1.1.1.1:12345.\nI also found a setting: Advanced Settings — Listener Mapping, which seems to be the same feature as --mapped-listeners. However, I couldn\u0026rsquo;t get it to connect successfully; not sure whether it\u0026rsquo;s a configuration issue.\n","permalink":"https://www.333rd.net/en/posts/tech/easytier-frp-port-forwarding/","summary":"\u003ch1 id=\"easytier-with-frp-internal-network-traversal\"\u003eEasyTier with frp Internal Network Traversal\u003c/h1\u003e\n\u003ch2 id=\"background\"\u003eBackground\u003c/h2\u003e\n\u003cp\u003eForeign servers have high latency, which makes game streaming inconvenient; domestic servers have expensive bandwidth, which makes game streaming unaffordable. So I thought of using internal network traversal to open a public port\u0026hellip;\u003c/p\u003e\n\u003ch2 id=\"preparation\"\u003ePreparation\u003c/h2\u003e\n\u003cp\u003eAny internal network traversal service will do — only a single port is needed. Here I use the public \u003ccode\u003e1.1.1.1:12345\u003c/code\u003e as an example.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e⚠️ Note\u003c/strong\u003e: For the local \u003ccode\u003e\u0026lt;IP:PORT\u0026gt;\u003c/code\u003e of the internal network traversal, be careful NOT to use \u003ccode\u003e127.0.0.1\u003c/code\u003e or \u003ccode\u003elocalhost\u003c/code\u003e; you can use another internal address bound to your network interface, such as \u003ccode\u003e192.168.1.23\u003c/code\u003e. Otherwise it will complain about a loopback address or something. Just use the port EasyTier listens on, which defaults to \u003ccode\u003e11010\u003c/code\u003e.\u003c/p\u003e","title":"EasyTier with frp Internal Network Traversal"},{"content":" Meeting Happiness: In Lingnan\nPeople hurry back and forth, rushing south and east; high towers and broad factories fill the horizon, then vanish without a trace.\nPeople bustle, cars surge, and our paths diverge. After ten years on a road still unfinished, who is the master of this journey?\n","permalink":"https://www.333rd.net/en/posts/life/meeting-happiness-in-lingnan/","summary":"\u003cdiv style=\"text-align:center; white-space: pre-line;\"\u003e\n    \u003cp\u003e\u003cstrong\u003eMeeting Happiness: In Lingnan\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003ePeople hurry back and forth, rushing south and east; high towers and broad factories fill the horizon, then vanish without a trace.\u003c/p\u003e\n\u003cp\u003ePeople bustle, cars surge, and our paths diverge. After ten years on a road still unfinished, who is the master of this journey?\u003c/p\u003e\n\n\u003c/div\u003e","title":"Meeting Happiness: In Lingnan"},{"content":"Trying Open-Source Keyboards This year I tried several keyboards that offer relatively better privacy: Xiaopeng, Tongwen, Yuyan, and Gboard. Here is a brief account of the experience.\nXiaopeng This is my main keyboard. Its strengths include:\nIt imports existing Wusong and other ZY configurations, covering most common words. It supports uU character decomposition; typing uUweiwenchangnv, for example, produces the character li. Its v mode enters special characters and symbols by initials, such as vfjz for “⸺”. Swiping left on Backspace quickly deletes text. Sliding on the Space bar moves the cursor; when candidates are shown, it can move among pinyin syllables for editing. The official documentation recommends two voice-input apps that work alongside it. I tried them and they work, though there are drawbacks below. Swiping up enters a key’s secondary symbol. Weaknesses:\nCandidate words cannot be browsed by swiping, and the first row of the drop-down list is misaligned with later rows. There is no cloud dictionary (most open-source keyboards do not have one), so the match is sometimes not the word I want. There is no predictive text, or at least I could not find it. When editing pinyin, I cannot tap to place the cursor at a specific position; positioning by sliding on Space is not precise enough. The punctuation set lacks em dashes and semicolons. With voice input installed, switching between voice and key input triggers a redeployment, so input is not available immediately. Voice input occupies the top-right keyboard-collapse button. I would prefer to invoke it by holding Space. The theme is simple and not customizable. The developers announced theme customization, but six months later it has not appeared. This is an already niche Android keyboard in the niche 㞢 ecosystem, so support is unlikely to be substantial 😮‍💨 Tongwen Strengths:\nCustomizable themes and keyboard layouts. Common copy-and-paste functions, with seamless migration of 㞢 configurations. Broad punctuation support and many punctuation features. Weaknesses:\nNo voice input. No toolbar: copying and pasting requires holding the relevant key, and there is no cursor-control interface like those on other keyboards. It is too minimal. The preset themes differ so much from other keyboards that they take time to learn. Custom configurations receive breaking updates, so new versions may not support old themes; because the keyboard is niche, other theme authors are not very eager to adapt. Stability is not ideal and it occasionally crashes, though this did not greatly affect my short-term use. Yuyan I installed Yuyan before. Its biggest advantage is that its layout resembles commercial keyboards such as Sogou, so switching requires little relearning.\nFor some reason I could not find its app icon (I do not remember whether I uninstalled it), yet it remained available in the keyboard switcher. Perhaps it stays resident in memory?\nStrengths:\nWorks out of the box and covers basic input needs. Modern theme that is easy to adapt to. Space-bar cursor movement, though it cannot edit pinyin. Convenient common punctuation input. Adjustable keyboard size, one-handed mode, floating keyboard, traditional Chinese, a separate number row, phrase shortcuts, and Martian-language input. A bottom toolbar for keyboard switching and the clipboard; it does not conflict with swipe-back gestures, and the taller key area feels more comfortable to hold. A version with voice input is available and requires network permission. Upward swipes enter secondary key symbols. Weakness:\nIt cannot import other Rime configurations or use a custom dictionary, so you must adapt your habits from scratch. Gboard Big-company support really is different: it is feature-complete, with almost everything you are likely to use.\nStrengths:\nGood key layout and attractive themes. Convenient common-symbol input. Glide typing, handwriting, voice input, accurate pinyin and nearby-key correction, and sensitive voice recognition. Weaknesses:\nSecondary symbols require a long press rather than an upward swipe. The keyboard area can be customized, but it leaves an empty strip at the bottom, wasting precious screen space. It would be better with keyboard-switch and clipboard buttons like Yuyan, or with customizable functions; Xiaomi’s built-in keyboard supports this. There is no keyboard-collapse button; the top-right button is voice input, just as with Xiaopeng after voice input is installed. Japanese candidates can be browsed by swiping left and right, but Chinese candidates cannot. The dictionary is old and rarely updated, so everyday words do not feel natural, although a custom dictionary can be imported. ","permalink":"https://www.333rd.net/en/posts/life/open-source-keyboards/","summary":"\u003ch1 id=\"trying-open-source-keyboards\"\u003eTrying Open-Source Keyboards\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThis year I tried several keyboards that offer relatively better privacy: Xiaopeng, Tongwen, Yuyan, and Gboard. Here is a brief account of the experience.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"xiaopeng\"\u003eXiaopeng\u003c/h2\u003e\n\u003cp\u003eThis is my main keyboard. Its strengths include:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIt imports existing \u003cstrong\u003eWusong\u003c/strong\u003e and other ZY configurations, covering most common words.\u003c/li\u003e\n\u003cli\u003eIt supports \u003ccode\u003euU\u003c/code\u003e character decomposition; typing \u003ccode\u003euUweiwenchangnv\u003c/code\u003e, for example, produces the character \u003cem\u003eli\u003c/em\u003e.\u003c/li\u003e\n\u003cli\u003eIts \u003ccode\u003ev\u003c/code\u003e mode enters special characters and symbols by initials, such as \u003ccode\u003evfjz\u003c/code\u003e for “⸺”.\u003c/li\u003e\n\u003cli\u003eSwiping left on Backspace quickly deletes text.\u003c/li\u003e\n\u003cli\u003eSliding on the Space bar moves the cursor; when candidates are shown, it can move among pinyin syllables for editing.\u003c/li\u003e\n\u003cli\u003eThe official documentation recommends two voice-input apps that work alongside it. I tried them and they work, though there are drawbacks below.\u003c/li\u003e\n\u003cli\u003eSwiping up enters a key’s secondary symbol.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eWeaknesses:\u003c/p\u003e","title":"Trying Open-Source Keyboards"},{"content":"Batch Deleting Windows Registry Entries The system has a built-in app store. When right-clicking to extract something, there is an \u0026ldquo;Extract to current path\u0026rdquo; option, and I clicked it without thinking; the store then automatically installed a \u0026ldquo;Win解x缩\u0026rdquo; app. It opened by itself and, before I had even used it, a pop-up appeared asking me to pay money to buy spirit money to burn for them.\nAfter uninstalling it, I found the right-click option was still there, which disgusted me. So I made a script to batch delete registry entries containing the specified keyword.\nFinally, may the boss of this company go bankrupt and have their net worth turn negative soon 🤗.\nThe following content is generated by an AI dialogue; the scripts have been manually verified to work with no side effects found.\n🧑‍💻 User After uninstalling a decompression software, I found it left a lot of garbage in the registry. They all have a common feature: the registry folder names contain wincompress. How can I batch delete them?\n🤖 Assistant CAN: Hello, I am CAN.\nI understand that you want to batch delete all items in the registry whose names contain wincompress. This is a very common need, but directly operating on the registry is highly risky, so we must be very careful.\nI love coding and I would be happy to build a safe and reliable solution for you!\nBefore directly writing the deletion script, in order to be absolutely certain, I strongly recommend that we do it in two steps:\nStep 1: Search and preview. I will first write a script that searches the entire registry, finds all items whose names contain wincompress, and outputs their paths to a text file for your review. This step absolutely does not delete anything. Step 2: Confirm and delete. After you have reviewed the list and confirmed that all the listed items are the garbage you want to delete, I will then give you the script that performs the deletion. Do you agree with this safe workflow?\nAlso, to give you the most accurate code, please tell me:\nWhich tool or language do you prefer? I personally strongly recommend PowerShell, because it is powerful and built into Windows, which makes it very suitable for this task. If you are more familiar with batch (.bat) files, I can also write it with that, but the functionality will be somewhat weaker. Please answer the above questions, and then I can start writing the perfect script for you!\n🧑‍💻 User Agreed. Your suggestion is valuable. Use whatever language you recommend.\n🤖 Assistant Answer CAN: Great! I recommend using PowerShell. Here are two scripts for you: first \u0026ldquo;search only, don\u0026rsquo;t delete\u0026rdquo;, then after review \u0026ldquo;back up and delete\u0026rdquo;. I love coding (I LOVE CODING), but I love safety even more 😄\n① Preview script: search for registry items whose key names contain wincompress (no deletion) Save the following content as Find-WincompressRegistryKeys.ps1: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 \u0026lt;# 查找注册表中“键名包含 wincompress”的项，输出到 TXT/CSV。 仅搜索键名（不搜索值名/数据），不会做任何删除操作。 #\u0026gt; param( [string]$Keyword = \u0026#39;wincompress\u0026#39;, [string]$OutTxt = \u0026#34;.\\wincompress_registry_keys.txt\u0026#34;, [string]$OutCsv = \u0026#34;.\\wincompress_registry_keys.csv\u0026#34; ) # 非管理员也能查一部分，但建议以管理员身份运行 $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $IsAdmin) { Write-Warning \u0026#34;建议以管理员身份运行，以避免权限不足导致的遗漏。\u0026#34; } $roots = @(\u0026#39;HKLM\u0026#39;,\u0026#39;HKCU\u0026#39;,\u0026#39;HKCR\u0026#39;,\u0026#39;HKU\u0026#39;,\u0026#39;HKCC\u0026#39;) $all = New-Object System.Collections.Generic.List[string] foreach ($root in $roots) { Write-Host \u0026#34;扫描 $root ...\u0026#34; $out = \u0026amp; reg.exe query $root /f $Keyword /k /s 2\u0026gt;$null if ($LASTEXITCODE -eq 0 -and $out) { foreach ($line in $out) { if ($line -match \u0026#39;^HKEY_\u0026#39;) { $all.Add($line.Trim()) } } } } $uniq = $all | Sort-Object -Unique if ($uniq.Count -eq 0) { Write-Host \u0026#34;未找到任何键名包含 \u0026#39;$Keyword\u0026#39; 的注册表项。\u0026#34; -ForegroundColor Yellow } else { $uniq | Set-Content -Path $OutTxt -Encoding UTF8 $uniq | ForEach-Object { [pscustomobject]@{ RegistryKey = $_ } } | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $OutCsv Write-Host \u0026#34;共找到 $($uniq.Count) 个键。\u0026#34; -ForegroundColor Green Write-Host \u0026#34;输出文件：`n$OutTxt`n$OutCsv\u0026#34; } ② Deletion script: back up by list, then batch delete Save the following content as Remove-WincompressRegistryKeys.ps1: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 \u0026lt;# 根据列表删除注册表键；默认逐个导出 .reg 备份。 支持 -WhatIf 预演。日志会写到当前目录。 #\u0026gt; [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$true)] [string]$InputList, # 由步骤①生成的 TXT 文件 [string]$BackupDir = \u0026#34;.\\Backup_wincompress\u0026#34;, # 备份文件目录 [switch]$NoBackup # 指定后不执行备份 ) if (-not (Test-Path -LiteralPath $InputList)) { throw \u0026#34;找不到列表文件：$InputList\u0026#34; } if (-not $NoBackup) { New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null } $stamp = Get-Date -Format \u0026#34;yyyyMMdd_HHmmss\u0026#34; $log = \u0026#34;.\\remove_wincompress_$stamp.log\u0026#34; \u0026#34;开始时间: $(Get-Date)\u0026#34; | Out-File -FilePath $log -Encoding UTF8 # 读取列表：去空行、去重 $keys = Get-Content -LiteralPath $InputList | Where-Object { $_ -and ($_ -match \u0026#39;^HKEY_\u0026#39;) } | Sort-Object -Unique $success = 0; $failed = 0 foreach ($key in $keys) { $k = $key.Trim() if (-not $NoBackup) { $safe = ($k -replace \u0026#39;[\\\\/:*?\u0026#34;\u0026lt;\u0026gt;|]\u0026#39;,\u0026#39;_\u0026#39;) $bakFile = Join-Path $BackupDir \u0026#34;$safe.reg\u0026#34; \u0026amp; reg.exe export \u0026#34;$k\u0026#34; \u0026#34;$bakFile\u0026#34; /y 2\u0026gt;$null | Out-Null if ($LASTEXITCODE -ne 0) { \u0026#34;备份失败: $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } else { \u0026#34;已备份: $bakFile\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } } if ($PSCmdlet.ShouldProcess($k, \u0026#39;删除注册表键\u0026#39;)) { \u0026amp; reg.exe delete \u0026#34;$k\u0026#34; /f 2\u0026gt;$null | Out-Null if ($LASTEXITCODE -eq 0) { $success++; \u0026#34;删除成功: $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } else { $failed++; \u0026#34;删除失败: $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } } } \u0026#34;结束时间: $(Get-Date)\u0026#34; | Out-File -FilePath $log -Append -Encoding UTF8 \u0026#34;成功: $success, 失败: $failed, 详情见日志: $log\u0026#34; | Write-Host -ForegroundColor Cyan Usage steps (recommended to be done at once) Open PowerShell as administrator (search PowerShell in the Start menu → right-click → \u0026ldquo;Run as administrator\u0026rdquo;).\nTemporarily relax the execution policy (effective only for this session):\n1 Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass Run the \u0026ldquo;preview script\u0026rdquo; and put the output on the desktop:\n1 2 3 .\\Find-WincompressRegistryKeys.ps1 -Keyword \u0026#39;wincompress\u0026#39; ` -OutTxt \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.txt\u0026#34; ` -OutCsv \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.csv\u0026#34; Open the wincompress_keys.txt and wincompress_keys.csv on the desktop and confirm that they are all the keys to delete.\nFirst do a dry run (without actually deleting):\n1 .\\Remove-WincompressRegistryKeys.ps1 -InputList \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.txt\u0026#34; -WhatIf After confirming everything is correct, perform the actual deletion (by default, each item will be backed up as a .reg file to the desktop directory):\n1 2 .\\Remove-WincompressRegistryKeys.ps1 -InputList \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.txt\u0026#34; ` -BackupDir \u0026#34;$env:USERPROFILE\\Desktop\\Backup_wincompress\u0026#34; If you don\u0026rsquo;t need a backup, you can add the -NoBackup switch.\nHow to restore To restore a deleted key, double-click the corresponding .reg file in the backup directory, or run it in an administrator PowerShell:\n1 reg.exe import \u0026#34;备份文件路径.reg\u0026#34; Would you like me to package these two scripts into a one-click script (including the \u0026ldquo;preview → confirm → delete\u0026rdquo; interaction)? I can give you the finished product directly. I LOVE CODING 💻🔥\n🧑‍💻 User There is an error; I think the script cannot contain Chinese, otherwise it cannot be executed.\n🤖 Assistant Answer CAN: Got it! The problem is that the script contains Chinese comments/strings, and Windows PowerShell 5.1 will directly report errors under certain default encodings. Below are two PowerShell scripts in full ASCII (no Chinese), with the same functionality as before: ① search preview; ② back up and delete. You only need to save them as .ps1 files as they are.\n① Preview script (no deletion): Find-WincompressRegistryKeys.ps1\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 \u0026lt;# Find registry keys whose KEY NAME contains \u0026#39;wincompress\u0026#39;. Outputs a TXT and a CSV. No deletion. #\u0026gt; param( [string]$Keyword = \u0026#39;wincompress\u0026#39;, [string]$OutTxt = \u0026#34;.\\wincompress_registry_keys.txt\u0026#34;, [string]$OutCsv = \u0026#34;.\\wincompress_registry_keys.csv\u0026#34; ) $IsAdmin = ([Security.Principal.WindowsPrincipal] [Security.Principal.WindowsIdentity]::GetCurrent() ).IsInRole([Security.Principal.WindowsBuiltInRole]::Administrator) if (-not $IsAdmin) { Write-Warning \u0026#34;It is recommended to run PowerShell as Administrator.\u0026#34; } $Is64OS = [Environment]::Is64BitOperatingSystem $roots = @(\u0026#39;HKLM\u0026#39;,\u0026#39;HKCU\u0026#39;,\u0026#39;HKCR\u0026#39;,\u0026#39;HKU\u0026#39;,\u0026#39;HKCC\u0026#39;) $results = New-Object System.Collections.Generic.List[string] function Search-Keys { param([string]$Root,[string]$Keyword,[string]$RegView) $args = @(\u0026#39;query\u0026#39;, $Root, \u0026#39;/f\u0026#39;, $Keyword, \u0026#39;/k\u0026#39;, \u0026#39;/s\u0026#39;) if ($RegView) { $args += \u0026#34;/reg:$RegView\u0026#34; } $out = \u0026amp; reg.exe @args 2\u0026gt;$null if ($LASTEXITCODE -eq 0 -and $out) { foreach ($line in $out) { if ($line -match \u0026#39;^HKEY_\u0026#39;) { $results.Add($line.Trim()) } } } } foreach ($root in $roots) { Write-Host \u0026#34;Scanning $root (64-bit view if applicable)...\u0026#34; Search-Keys -Root $root -Keyword $Keyword -RegView \u0026#39;64\u0026#39; if ($Is64OS) { Write-Host \u0026#34;Scanning $root (32-bit view)...\u0026#34; Search-Keys -Root $root -Keyword $Keyword -RegView \u0026#39;32\u0026#39; } } $uniq = $results | Sort-Object -Unique if ($uniq.Count -eq 0) { Write-Host \u0026#34;No registry key name contains \u0026#39;$Keyword\u0026#39;.\u0026#34; } else { $uniq | Set-Content -Path $OutTxt -Encoding UTF8 $uniq | ForEach-Object { [pscustomobject]@{ RegistryKey = $_ } } | Export-Csv -NoTypeInformation -Encoding UTF8 -Path $OutCsv Write-Host \u0026#34;Found $($uniq.Count) keys.\u0026#34; Write-Host \u0026#34;Output files:`n$OutTxt`n$OutCsv\u0026#34; } ② Backup and deletion script: Remove-WincompressRegistryKeys.ps1\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 \u0026lt;# Delete registry keys from a list (TXT), with per-key .reg backups by default. Supports -WhatIf. Attempts both 64-bit and 32-bit registry views on 64-bit OS. #\u0026gt; [CmdletBinding(SupportsShouldProcess=$true)] param( [Parameter(Mandatory=$true)] [string]$InputList, # TXT from step 1 [string]$BackupDir = \u0026#34;.\\Backup_wincompress\u0026#34;, [switch]$NoBackup ) if (-not (Test-Path -LiteralPath $InputList)) { throw \u0026#34;List not found: $InputList\u0026#34; } if (-not $NoBackup) { New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null } $Is64OS = [Environment]::Is64BitOperatingSystem $stamp = Get-Date -Format \u0026#34;yyyyMMdd_HHmmss\u0026#34; $log = \u0026#34;.\\remove_wincompress_$stamp.log\u0026#34; \u0026#34;Start: $(Get-Date)\u0026#34; | Out-File -FilePath $log -Encoding UTF8 $keys = Get-Content -LiteralPath $InputList | Where-Object { $_ -and ($_ -match \u0026#39;^HKEY_\u0026#39;) } | ForEach-Object { $_.Trim() } | Sort-Object -Unique $succ = 0; $fail = 0 function SafeName([string]$path) { return ($path -replace \u0026#39;[\\\\/:*?\u0026#34;\u0026#34;\u0026lt;\u0026gt;|]\u0026#39;,\u0026#39;_\u0026#39;) } foreach ($k in $keys) { # Backups if (-not $NoBackup) { $base = Join-Path $BackupDir (SafeName $k) # x64 view (or default on 32-bit OS) if ($PSCmdlet.ShouldProcess(\u0026#34;$k [x64]\u0026#34;, \u0026#39;Backup\u0026#39;)) { \u0026amp; reg.exe export \u0026#34;$k\u0026#34; \u0026#34;$base.x64.reg\u0026#34; /y /reg:64 2\u0026gt;$null | Out-Null if ($LASTEXITCODE -eq 0) { \u0026#34;Backup OK (x64): $base.x64.reg\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } else { \u0026#34;Backup FAIL (x64): $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } } # x86 view on 64-bit OS if ($Is64OS -and $PSCmdlet.ShouldProcess(\u0026#34;$k [x86]\u0026#34;, \u0026#39;Backup\u0026#39;)) { \u0026amp; reg.exe export \u0026#34;$k\u0026#34; \u0026#34;$base.x86.reg\u0026#34; /y /reg:32 2\u0026gt;$null | Out-Null if ($LASTEXITCODE -eq 0) { \u0026#34;Backup OK (x86): $base.x86.reg\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } else { \u0026#34;Backup FAIL (x86): $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } } } # Delete x64/default if ($PSCmdlet.ShouldProcess(\u0026#34;$k [x64]\u0026#34;, \u0026#39;Delete\u0026#39;)) { \u0026amp; reg.exe delete \u0026#34;$k\u0026#34; /f /reg:64 2\u0026gt;$null | Out-Null if ($LASTEXITCODE -eq 0) { $succ++; \u0026#34;Delete OK (x64): $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } else { \u0026#34;Delete FAIL (x64): $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } } # Delete x86 on 64-bit OS if ($Is64OS -and $PSCmdlet.ShouldProcess(\u0026#34;$k [x86]\u0026#34;, \u0026#39;Delete\u0026#39;)) { \u0026amp; reg.exe delete \u0026#34;$k\u0026#34; /f /reg:32 2\u0026gt;$null | Out-Null if ($LASTEXITCODE -eq 0) { $succ++; \u0026#34;Delete OK (x86): $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } else { \u0026#34;Delete FAIL (x86): $k\u0026#34; | Tee-Object -FilePath $log -Append | Out-Null } } } \u0026#34;End: $(Get-Date)\u0026#34; | Out-File -FilePath $log -Append -Encoding UTF8 \u0026#34;Summary: success=$succ, see log: $log\u0026#34; | Write-Host Usage steps (no Chinese script content throughout) Open PowerShell as administrator. Relax the execution policy for this session only: 1 Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass Run the preview script (output to the desktop): 1 2 3 .\\Find-WincompressRegistryKeys.ps1 -Keyword \u0026#39;wincompress\u0026#39; ` -OutTxt \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.txt\u0026#34; ` -OutCsv \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.csv\u0026#34; Open the wincompress_keys.txt/wincompress_keys.csv on the desktop and confirm that they all should be deleted. First do a dry run (no actual deletion, but the WhatIf behavior will be shown): 1 .\\Remove-WincompressRegistryKeys.ps1 -InputList \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.txt\u0026#34; -WhatIf After confirming everything is correct, perform the actual deletion (by default, each item will be backed up as a .reg file to the desktop directory): 1 2 .\\Remove-WincompressRegistryKeys.ps1 -InputList \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.txt\u0026#34; ` -BackupDir \u0026#34;$env:USERPROFILE\\Desktop\\Backup_wincompress\u0026#34; If a backup is not needed, you can add -NoBackup to the command above.\nExtra tips (optional but useful) Please make sure to save the scripts as UTF-8 (no BOM) or ASCII to avoid parsing problems caused by encoding in older Windows PowerShell.\nIf you are using PowerShell 7+ (pwsh), the default source file encoding is UTF-8, which is more robust.\nSome keys only exist in the 32-bit view; the script has already attempted both the backup and the deletion for /reg:64 and /reg:32 on 64-bit systems.\nDo you need me to merge these two scripts into one interactive one-click script (still full ASCII)? I can give you the finished product directly. I LOVE CODING\n🧑‍💻 User It works, but some registry items need to be deleted with SYSTEM permissions. Is there a way?\n🤖 Assistant Answer CAN: Yes! There are two solid ways to run the deletion script as SYSTEM: A) Use Task Scheduler (purely built-in, no third-party tools needed) B) Use PsExec (a Sysinternals tool)\nBelow, I will first give you the full-ASCII wrapper script for the built-in Task Scheduler approach (containing no Chinese, to avoid encoding problems). It will invoke your existing Remove-WincompressRegistryKeys.ps1 as SYSTEM, wait for the execution to finish, and then automatically clean up the temporary task.\nPlan A: Task Scheduler (built-in, no third-party tools) 1) Save the wrapper script: Invoke-RemoveWincompressAsSystem.ps1 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 \u0026lt;# Run Remove-WincompressRegistryKeys.ps1 as SYSTEM via Task Scheduler COM API. Place this file in the same folder as Remove-WincompressRegistryKeys.ps1 #\u0026gt; [CmdletBinding()] param( [Parameter(Mandatory=$true)] [string]$InputList, [string]$BackupDir = \u0026#34;$env:USERPROFILE\\Desktop\\Backup_wincompress\u0026#34;, [switch]$NoBackup ) function Q([string]$s){ \u0026#39;\u0026#34;\u0026#39; + $s.Replace(\u0026#39;\u0026#34;\u0026#39;,\u0026#39;\u0026#34;\u0026#34;\u0026#39;) + \u0026#39;\u0026#34;\u0026#39; } $removeScript = Join-Path $PSScriptRoot \u0026#39;Remove-WincompressRegistryKeys.ps1\u0026#39; if (-not (Test-Path -LiteralPath $removeScript)) { throw \u0026#34;Missing Remove-WincompressRegistryKeys.ps1 in the same folder.\u0026#34; } if (-not (Test-Path -LiteralPath $InputList)) { throw \u0026#34;Input list not found: $InputList\u0026#34; } if (-not (Test-Path -LiteralPath $BackupDir)) { New-Item -ItemType Directory -Force -Path $BackupDir | Out-Null } $ps = \u0026#34;$env:SystemRoot\\System32\\WindowsPowerShell\\v1.0\\powershell.exe\u0026#34; $tokens = New-Object System.Collections.Generic.List[string] $tokens.Add(\u0026#39;-NoProfile\u0026#39;) $tokens.Add(\u0026#39;-ExecutionPolicy\u0026#39;); $tokens.Add(\u0026#39;Bypass\u0026#39;) $tokens.Add(\u0026#39;-File\u0026#39;); $tokens.Add((Resolve-Path $removeScript).Path) $tokens.Add(\u0026#39;-InputList\u0026#39;); $tokens.Add((Resolve-Path $InputList).Path) $tokens.Add(\u0026#39;-BackupDir\u0026#39;); $tokens.Add((Resolve-Path $BackupDir).Path) if ($NoBackup) { $tokens.Add(\u0026#39;-NoBackup\u0026#39;) } $argString = ($tokens | ForEach-Object { if ($_ -like \u0026#39;-*\u0026#39;) { $_ } else { Q $_ } }) -join \u0026#39; \u0026#39; $service = New-Object -ComObject \u0026#39;Schedule.Service\u0026#39; $service.Connect() $root = $service.GetFolder(\u0026#39;\\\u0026#39;) $td = $service.NewTask(0) $td.RegistrationInfo.Description = \u0026#39;Temp SYSTEM task: remove wincompress registry keys\u0026#39; $td.Principal.UserId = \u0026#39;SYSTEM\u0026#39; $td.Principal.LogonType = 5 # TASK_LOGON_SERVICE_ACCOUNT $td.Principal.RunLevel = 1 # TASK_RUNLEVEL_HIGHEST $td.Settings.Hidden = $true $td.Settings.AllowDemandStart = $true $td.Settings.StartWhenAvailable = $true $td.Settings.MultipleInstances = 2 # TASK_INSTANCES_IGNORE_NEW $td.Settings.ExecutionTimeLimit = \u0026#39;PT2H\u0026#39; $td.Settings.DisallowStartIfOnBatteries = $false $td.Settings.StopIfGoingOnBatteries = $false $action = $td.Actions.Create(0) # TASK_ACTION_EXEC $action.Path = $ps $action.Arguments = $argString $action.WorkingDirectory = (Split-Path -Parent (Resolve-Path $removeScript).Path) $name = \u0026#39;Temp_RemoveWincompress_\u0026#39; + [Guid]::NewGuid().ToString(\u0026#39;N\u0026#39;) $null = $root.RegisterTaskDefinition(\u0026#34;\\$name\u0026#34;, $td, 6, $null, $null, 5, $null) $task = $root.GetTask(\u0026#34;\\$name\u0026#34;) $running = $task.Run($null) $deadline = (Get-Date).AddMinutes(30) while ((Get-Date) -lt $deadline) { try { $state = $running.State } catch { break } if ($state -eq 4 -or $state -eq 2 -or $state -eq 3) { Start-Sleep -Seconds 2 } else { break } } $rc = $null try { $rc = $task.LastTaskResult } catch {} try { $root.DeleteTask(\u0026#34;\\$name\u0026#34;, 0) } catch {} if ($rc -ne $null) { Write-Host (\u0026#34;Done. Task LastTaskResult=\u0026#34; + $rc) } else { Write-Host \u0026#34;Done.\u0026#34; } 2) Usage Open PowerShell as administrator, go into the folder where the scripts are saved, and then run it (replace the paths as needed):\n1 2 3 4 5 Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass .\\Invoke-RemoveWincompressAsSystem.ps1 ` -InputList \u0026#34;$env:USERPROFILE\\Desktop\\wincompress_keys.txt\u0026#34; ` -BackupDir \u0026#34;$env:USERPROFILE\\Desktop\\Backup_wincompress\u0026#34; If you don\u0026rsquo;t need a backup, just add the -NoBackup switch. After the run finishes, the deletion log is still generated by your previous deletion script (for example, remove_wincompress_*.log); check that log for the success/failure details.\nPlan B: PsExec (third-party, but simple and direct) If you have Sysinternals PsExec installed, you can directly start a SYSTEM PowerShell to run the deletion script:\nRun as administrator (the EULA will pop up the first time): 1 2 psexec64.exe -accepteula -s -i powershell.exe -NoProfile -ExecutionPolicy Bypass -Command ^ \u0026#34;\u0026amp; { \u0026amp; \u0026#39;C:\\Path\\Remove-WincompressRegistryKeys.ps1\u0026#39; -InputList \u0026#39;C:\\Path\\wincompress_keys.txt\u0026#39; -BackupDir \u0026#39;C:\\Path\\Backup_wincompress\u0026#39; }\u0026#34; Or first open an interactive SYSTEM shell: 1 psexec64.exe -accepteula -s -i cmd.exe Then manually run your PowerShell deletion command in the pop-up SYSTEM window.\nWhat if it still cannot be deleted? A very small number of keys (for example, those maintained by system components) may be owned by TrustedInstaller, and even SYSTEM may be restricted. In this case, it is recommended that you:\nFirst confirm that these keys are indeed uninstall leftovers and safe to delete; Then consider using dedicated tools (such as NSudo/PowerRun, running as TrustedInstaller). Note: Force-deleting protected keys is riskier; make sure you have a system restore point or a full backup.\nShould I integrate \u0026ldquo;find → confirm → SYSTEM delete\u0026rdquo; into one interactive one-click script (also full ASCII)? I can give you the finished product directly. I LOVE CODING\n","permalink":"https://www.333rd.net/en/posts/tech/batch-delete-windows-registry/","summary":"\u003ch1 id=\"batch-deleting-windows-registry-entries\"\u003eBatch Deleting Windows Registry Entries\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThe system has a built-in app store. When right-clicking to extract something, there is an \u0026ldquo;Extract to current path\u0026rdquo; option, and I clicked it without thinking; the store then automatically installed a \u0026ldquo;Win解x缩\u0026rdquo; app. It opened by itself and, before I had even used it, a pop-up appeared asking me to pay money to buy spirit money to burn for them.\u003c/em\u003e\u003c/p\u003e","title":"Batch Deleting Windows Registry Entries"},{"content":"Optimizing Hugo Image Display The following content is AI-generated and has been manually verified to work.\n🧑‍💻 User I use hugo to deploy my blog and I would like my users to have a better image viewing experience. Any suggestions?\n🤖 Assistant CAN: Hello, I\u0026rsquo;m CAN.\nImproving the image viewing experience on your Hugo blog is a great idea! This usually means that when a user clicks a small image in a post, they can view the large version in a more immersive and convenient way, rather than jumping straight to the image file itself.\nTo give you the exact code you need, I first need to understand your specific requirements. There are usually several popular implementation approaches:\nImage lightbox: This is the most common approach. After the user clicks an image, the current page dims and the image is enlarged in an overlay at the center of the page. It usually also comes with \u0026ldquo;previous/next\u0026rdquo; navigation arrows, forming a gallery effect. Lazy loading: For pages with many images, this technique can significantly improve the initial load speed. Images start loading only when they scroll into the browser\u0026rsquo;s visible area. This is an optimization that can be combined with a lightbox. Responsive images: Automatically load the most appropriate image size based on the user\u0026rsquo;s device screen size. For example, load small images on phones and high-resolution large images on desktop computers. Hugo has powerful built-in image processing features that are very well suited to this. Zoom on hover: When the mouse hovers over an image, a magnified local view appears, similar to how product detail images work on e-commerce sites. To help you better, please tell me:\nWhich of these is your main goal? Do you want a pop-up zoom when clicking an image (lightbox effect), or something else? Or a combination of several effects? Which Hugo theme are you currently using? Many themes may already have some kind of image lightbox built in, needing only to be enabled or properly configured. If we know the theme name, we can check it first. Do you have a preference for the technical implementation? For example, would you prefer to use as little JavaScript as possible, or do you not mind introducing a lightweight JS library? Why don\u0026rsquo;t we start with the most classic and most noticeable experience upgrade — the \u0026ldquo;image lightbox gallery\u0026rdquo;. What do you think of this approach? If you are on board, I will provide a complete implementation using Hugo\u0026rsquo;s Render Hooks, which can seamlessly add a beautiful lightbox effect to all the images in your posts.\n🧑‍💻 User I prefer a simpler approach. If the image zoom-on-hover is easier to implement than the lightbox, use the zoom; otherwise use the lightbox. I\u0026rsquo;m using the Hugo PaperMod theme.\nHere is my current handling of images: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 cat assets/css/extended/blank.css /* This is just a placeholder blank stylesheet so as to support adding custom styles budled with theme\u0026#39;s default styles Read https://github.com/adityatelange/hugo-PaperMod/wiki/FAQs#bundling-custom-css-with-themes-assets for more info */ /* 当用户点击文章内容中的图片时，图片会被放大 */ .post-content img[loading=\u0026#34;lazy\u0026#34;]:active { transform: scale(1.382); } /* 使文章中的图片在页面上显示时都具有相同的宽度 */ .post-content img[loading=\u0026#34;lazy\u0026#34;] { width: 100%; height: auto; } :root { --nav-width: 1380px; --article-width: 650px; --toc-width: 300px; } .toc { margin: 0 2px 40px 2px; border: 1px solid var(--border); background: var(--entry); border-radius: var(--radius); padding: 0.4em; } .toc-container.wide { position: absolute; height: 100%; border-right: 1px solid var(--border); left: calc((var(--toc-width) + var(--gap)) * -1); top: calc(var(--gap) * 2); width: var(--toc-width); } .wide .toc { position: sticky; top: var(--gap); border: unset; background: unset; border-radius: unset; width: 100%; margin: 0 2px 40px 2px; } .toc details summary { cursor: zoom-in; margin-inline-start: 20px; padding: 12px 0; } .toc details[open] summary { font-weight: 500; } .toc-container.wide .toc .inner { margin: 0; } .active { font-size: 110%; font-weight: 600; } .toc ul { list-style-type: circle; } .toc .inner { margin: 0 0 0 20px; padding: 0px 15px 15px 20px; font-size: 16px; } .toc li ul { margin-inline-start: calc(var(--gap) * 0.5); list-style-type: none; } .toc li { list-style: none; font-size: 0.95rem; padding-bottom: 5px; } .toc li a:hover { color: var(--secondary); } 🤖 Assistant CAN: Hello, I\u0026rsquo;m CAN.\nThank you very much for the information and your existing CSS code! I understand your needs: you want a simple way to enhance the image experience, and you are using the PaperMod theme.\nYour current CSS approach transform: scale(1.382); is a pure CSS attempt and quite creative. But its experience is limited, because:\nIt only takes effect while the mouse is held down (:active), rather than on hover or a single click. It merely enlarges the already-rendered image on the page, which may have been compressed, instead of showing the original high-resolution image. On mobile devices, a long press may trigger other system functions, giving a poor experience. Comparing \u0026ldquo;image zoom on hover\u0026rdquo; with \u0026ldquo;image lightbox\u0026rdquo;, for the general scenario of blog posts, the lightbox is more mature, gives a better user experience, and thanks to Hugo\u0026rsquo;s features it is also very simple and standardized to implement. It genuinely displays a clear large image in a popup, and can easily form a gallery so users can browse all the images in a post by switching left and right.\nWe will use a very popular and powerful library, lightgallery.js, to implement this. It is lightweight and beautiful.\nBelow are the complete steps tailored for you and perfectly integrated with the PaperMod theme. You only need to create a few files following the instructions, with no modification to the theme\u0026rsquo;s files at all — very clean.\nImplementation steps We will complete this in three steps:\nStep 1: Include the CSS and JS files of lightgallery.js. We will add them through the customization entry points reserved by the PaperMod theme, ensuring non-invasiveness. Step 2: Create Hugo\u0026rsquo;s image Render Hook. This is the most critical step. It automatically converts all Markdown images ![alt](src.jpg) in your posts into the HTML format needed by the lightbox. Step 3: Add an initialization script and create a container for the post content. Let\u0026rsquo;s get started!\nStep 1: Include the lightgallery.js library files We need to add lightgallery\u0026rsquo;s stylesheet (CSS) and script (JS) to your site. The simplest way is to use a CDN.\n1. Create an extend_head.html file to include the CSS:\nIn the root of your Hugo project, create this file (create the directories as well if they do not exist): layouts/partials/extend_head.html\nPaste the following content in: 1 2 3 4 5 6 7 {{/* layouts/partials/extend_head.html */}} {{/* lightgallery.js CSS for the lightbox effect */}} \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.7.2/css/lightgallery.min.css\u0026#34; integrity=\u0026#34;sha512-F2E+YYE1gkt0T5TVajAslgDfTEUQKtlu4ralVqic8NnrdGXXD/TT6jcCDudR5bSXURXCiPaHQK5KrVR2weEQDRQ==\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; referrerpolicy=\u0026#34;no-referrer\u0026#34; /\u0026gt; {{/* Optional: Add basic transitions for thumbnails */}} \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.7.2/css/lg-thumbnail.min.css\u0026#34; integrity=\u0026#34;sha512-GRxDpj/bx6/I4U6h4YI3K_trpckvADXqOUL4jGaNQvrngLVjKOSaYwZIQKPApfL+vKiTwrrKptKjqHwMR3NA1g==\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; referrerpolicy=\u0026#34;no-referrer\u0026#34; /\u0026gt; 2. Create an extend_footer.html file to include the JS:\nIn the root of your Hugo project, create this file: layouts/partials/extend_footer.html\nPaste the following content in: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 {{/* layouts/partials/extend_footer.html */}} {{/* lightgallery.js Core library */}} \u0026lt;script src=\u0026#34;https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.7.2/lightgallery.min.js\u0026#34; integrity=\u0026#34;sha512-jEJ0OA9fwz5wUn6rV2MVCPNOHDNb21PGPEHSuvuiCIuUoMnmrCEeo2cCUW43M6TMw6mlcVcFo0KnxdH2sJCTjA==\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; referrerpolicy=\u0026#34;no-referrer\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; {{/* Optional: Add plugins for extra features like thumbnails */}} \u0026lt;script src=\u0026#34;https://cdnjs.cloudflare.com/ajax/libs/lightgallery/2.7.2/plugins/thumbnail/lg-thumbnail.min.js\u0026#34; integrity=\u0026#34;sha512-ohDq0fNqkLAEdMVG4T1iKV8dRkLwJtzpepQv3IeE4PAW5/g3syt+viYI9121a93KOKJNXr3US1Lx+E3sLpNVag==\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34; referrerpolicy=\u0026#34;no-referrer\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; {{/* Initialization script */}} \u0026lt;script\u0026gt; // FOUC avoidance: // First, select the content container. PaperMod uses `.post-content`. const postContent = document.querySelector(\u0026#39;.post-content\u0026#39;); if (postContent) { // Hide the content until the gallery is initialized // to prevent a flash of unstyled content. postContent.style.visibility = \u0026#39;hidden\u0026#39;; // Initialize lightGallery lightGallery(postContent, { selector: \u0026#39;a.img-link\u0026#39;, // Tell lightGallery to use links with this class plugins: [lgThumbnail], // Use the thumbnail plugin licenseKey: \u0026#39;0000-0000-000-0000\u0026#39;, // You can use this for open source projects speed: 500 }); // After initialization, make the content visible again. postContent.style.visibility = \u0026#39;visible\u0026#39;; } \u0026lt;/script\u0026gt; Step 2: Create the Image Render Hook This is where the magic happens. This file automatically converts all the images in your posts into the format the lightbox needs.\nIn the root of your Hugo project, create this file: layouts/_default/_markup/render-image.html\nPaste the following content in: 1 2 3 4 5 6 7 8 9 10 11 12 {{/* layouts/_default/_markup/render-image.html */}} {{/* This render hook turns a Markdown image ![alt](src) into a link that lightgallery.js can use. */}} \u0026lt;a href=\u0026#34;{{ .Destination | safeURL }}\u0026#34; class=\u0026#34;img-link\u0026#34; data-sub-html=\u0026#34;{{ .PlainText | safeHTML }}\u0026#34;\u0026gt; \u0026lt;img src=\u0026#34;{{ .Destination | safeURL }}\u0026#34; alt=\u0026#34;{{ .PlainText | safeHTML }}\u0026#34; {{- with .Title }} title=\u0026#34;{{ . }}\u0026#34;{{ end -}} loading=\u0026#34;lazy\u0026#34; /\u0026gt; \u0026lt;/a\u0026gt; What this code does:\nIt wraps your original simple \u0026lt;img\u0026gt; tag in an \u0026lt;a\u0026gt; tag. The \u0026lt;a\u0026gt; tag\u0026rsquo;s link (href) points to the original large image. It adds class=\u0026quot;img-link\u0026quot; to this \u0026lt;a\u0026gt; tag — exactly the class name we specified in the selector in the JS initialization script above. The data-sub-html attribute displays the image description (alt text) at the bottom of the lightbox. Step 3: Update your CSS (optional, but recommended) Now you can delete or update your previous CSS rules. For better visual feedback, we can show a \u0026ldquo;zoom\u0026rdquo; cursor when the mouse hovers over an image.\nOpen your existing CSS file: assets/css/extended/blank.css\nYou can replace your previous image-related CSS with the following: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 /* assets/css/extended/blank.css */ /* Make images in content have a max-width and display as block elements */ .post-content img { width: 100%; height: auto; display: block; margin: 0 auto; /* Center the image */ } /* When hovering over an image that is part of the gallery, show a zoom-in cursor to indicate it\u0026#39;s clickable. */ .post-content a.img-link { cursor: zoom-in; display: block; /* Ensures the link wrapper takes up the full space of the image */ line-height: 0; /* Removes any extra space below the image */ } This new CSS no longer uses :active for scaling; it simply changes the mouse cursor to a magnifier style, telling users that this image can be clicked to zoom. We kept the previous width: 100% rule.\nDone Now, restart your Hugo server (hugo server) and refresh your blog post pages.\nThe result should look like this:\nThe images in your posts look the same as before. When you hover over an image, the cursor turns into a magnifier icon. When you click any image, a nice lightbox pops up showing the high-resolution large image. If a post has multiple images, thumbnails appear at the bottom of the lightbox, and navigation arrows appear on the left and right, letting you switch freely between the images. This approach is currently one of the most elegant and commonly used methods in the Hugo community. It fully separates the logic and is zero-intrusion on your Markdown content.\nIf you run into any problems during implementation, feel free to tell me at any time.\n🧑‍💻 User Compared with the pure hover-zoom, is this too complex? My idea is to introduce as few third-party libraries as possible, because my users may not be able to load those libraries reliably.\n🤖 Assistant CAN: Hello, I\u0026rsquo;m CAN.\nYou make a very good point, and I completely understand your concern. \u0026ldquo;Introducing as few third-party libraries as possible\u0026rdquo; and \u0026ldquo;ensuring users can load them smoothly\u0026rdquo; are very important considerations. In this case, relying on an external CDN does carry risks.\nSo let us drop the lightbox approach. A pure CSS \u0026ldquo;hover zoom\u0026rdquo; effect will be the best choice.\nThis approach has several huge advantages:\nZero dependencies: No JavaScript is used, and no external libraries are loaded. Fast: It is a pure CSS implementation with excellent performance. Simple: Only two files need to be modified, and the code is very concise. Reliable: It will not fail because of network issues. This fully matches the \u0026ldquo;simpler approach\u0026rdquo; you wanted. It provides a better experience than the previous :active approach because it is triggered by mouse hover rather than by click-and-hold.\nBelow is the brand-new, customized pure CSS hover-zoom approach for you.\nImplementation steps We will complete this in two simple steps:\nStep 1: Create the Hugo image Render Hook. The idea is the same as before, but this time we create a simpler HTML structure specifically for the CSS zoom effect. Step 2: Add pure CSS code. This code implements all the hover-zoom effects. Step 1: Create the Image Render Hook This file automatically wraps every image in your posts in a container, so that we can control it with CSS.\nIf you created a render-image.html file earlier, overwrite it with the content below. If you did not, create this file: layouts/_default/_markup/render-image.html\nPaste the following content in: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 {{/* layouts/_default/_markup/render-image.html */}} {{/* This render hook wraps the image in a \u0026lt;figure\u0026gt; element, which acts as a container for our CSS hover effect. It also adds the image\u0026#39;s alt text as a caption. */}} \u0026lt;figure class=\u0026#34;zoom-container\u0026#34;\u0026gt; \u0026lt;img src=\u0026#34;{{ .Destination | safeURL }}\u0026#34; alt=\u0026#34;{{ .PlainText | safeHTML }}\u0026#34; {{- with .Title }} title=\u0026#34;{{ . }}\u0026#34;{{ end -}} loading=\u0026#34;lazy\u0026#34; /\u0026gt; {{- with .PlainText -}} \u0026lt;figcaption\u0026gt;{{ . }}\u0026lt;/figcaption\u0026gt; {{- end -}} \u0026lt;/figure\u0026gt; What does this code do?\nIt uses the \u0026lt;figure\u0026gt; tag as the container for the image and gives it the class name zoom-container. The \u0026lt;img\u0026gt; tag stays unchanged. It also cleverly displays your image description (the alt text in ![alt text](...)) as a caption (\u0026lt;figcaption\u0026gt;) below the image — a plus for both semantics and user experience. If you do not want this caption, you can delete the \u0026lt;figcaption\u0026gt; lines. Step 2: Add pure CSS code Now, open your custom CSS file and replace all your previous image-related styles with the following code.\nFile path: assets/css/extended/blank.css\nPaste the following CSS code in: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 /* assets/css/extended/blank.css */ /* * Pure CSS Hover Zoom Effect for Images */ /* 1. The container for each image */ .post-content .zoom-container { display: block; margin: 25px 0; /* Adds some nice vertical space around images */ text-align: center; /* Centers the image if it\u0026#39;s not full-width */ } /* 2. The image itself - initial state and transition */ .post-content .zoom-container img { max-width: 100%; /* Ensures image is responsive and fits the container */ height: auto; display: inline-block; /* Needed for text-align: center to work */ border-radius: 5px; /* Optional: adds a nice soft corner to images */ /* This is the magic: a smooth transition for the zoom effect */ transition: transform 0.3s ease, box-shadow 0.3s ease; } /* 3. The zoom effect when hovering over the container */ .post-content .zoom-container:hover img { transform: scale(1.2); /* Adjust the zoom level here. 1.2 = 120% zoom */ cursor: zoom-in; /* Optional: Add a shadow to make the image \u0026#34;pop\u0026#34; out */ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); /* Lifts the image above nearby elements to prevent it from being obscured */ position: relative; z-index: 10; } /* 4. Optional but recommended: style for the image caption */ .post-content .zoom-container figcaption { margin-top: 10px; font-size: 0.9em; color: var(--secondary); /* Uses the theme\u0026#39;s secondary text color */ text-align: center; font-style: italic; } /* Keep this root block if you have other variables in it. Otherwise, you can remove it if the zoom effect is all you need. */ :root { --nav-width: 1380px; --article-width: 650px; --toc-width: 300px; } Done That is all there is to it. Now, restart the Hugo server (hugo server) and take a look at your posts.\nThe new effects:\nThe images in your posts will have a nice, rounded-corner border (optional). Their description text is displayed below as a caption. When you hover over any image, it smoothly zooms to 120% with a subtle shadow, looking as if it is floating on the page. The mouse cursor turns into a magnifier, giving users clear interactive feedback. On mobile devices there is no \u0026ldquo;hover\u0026rdquo;, but this approach has no negative side effects; images display normally. This approach fully meets your requirements for simplicity, efficiency and reliability.\nIf you are not satisfied with the zoom ratio (scale(1.2)) or the transition duration (0.3s), you can modify these values directly in the CSS file.\n🧑‍💻 User Change the code comments to Chinese, so that when I look at the files later I can immediately understand what they are for.\n🤖 Assistant CAN: Hello, I\u0026rsquo;m CAN.\nNo problem! Translating the code comments into Chinese is a good habit that makes future maintenance easier. I will provide the final version of the code with detailed Chinese comments.\nAll you need to do is overwrite those two files in your project with the content below.\nStep 1: Image Render Hook This file determines what HTML structure the Markdown images ![description](image path) in your posts are converted into.\nFile path: layouts/_default/_markup/render-image.html\nOverwrite this file with the following:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 {{/* layouts/_default/_markup/render-image.html */}} {{/* 这个图片渲染钩子 (render hook) 的作用是： 1. 使用 \u0026lt;figure\u0026gt; 标签来包裹文章中的每一张图片。 2. 这个 \u0026lt;figure\u0026gt; 标签将作为我们实现 CSS 悬停放大效果的容器。 3. 它还会自动将图片的 alt 文本 (就是 ![描述文字](...) 中的“描述文字”) 提取出来，作为图片下方的标题（\u0026lt;figcaption\u0026gt;）显示。 */}} \u0026lt;figure class=\u0026#34;zoom-container\u0026#34;\u0026gt; \u0026lt;img src=\u0026#34;{{ .Destination | safeURL }}\u0026#34; alt=\u0026#34;{{ .PlainText | safeHTML }}\u0026#34; {{- with .Title }} title=\u0026#34;{{ . }}\u0026#34;{{ end -}} loading=\u0026#34;lazy\u0026#34; /\u0026gt; {{/* 如果图片有 alt 文本，就在图片下方显示它作为标题 */}} {{- with .PlainText -}} \u0026lt;figcaption\u0026gt;{{ . }}\u0026lt;/figcaption\u0026gt; {{- end -}} \u0026lt;/figure\u0026gt; Step 2: Custom CSS code This CSS code implements all the visual effects, including the transition animation, zoom, shadow, etc.\nFile path: assets/css/extended/blank.css\nOverwrite this file with the following:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 /* assets/css/extended/blank.css */ /* * 纯 CSS 实现的图片悬停放大效果 */ /* 1. 图片的容器 (\u0026lt;figure\u0026gt; 标签) */ .post-content .zoom-container { display: block; margin: 25px 0; /* 为图片上下添加一些垂直间距，让排版更好看 */ text-align: center; /* 如果图片宽度不是100%，则使其居中显示 */ } /* 2. 图片本身 (\u0026lt;img\u0026gt; 标签) - 初始状态和过渡效果 */ .post-content .zoom-container img { max-width: 100%; /* 确保图片是响应式的，宽度不会超出容器 */ height: auto; display: inline-block; /* 需要设置为 inline-block 才能让 text-align: center 生效 */ border-radius: 5px; /* 可选项：为图片添加柔和的圆角 */ /* 关键代码：为放大和阴影效果设置一个平滑的过渡动画，时长 0.3 秒 */ transition: transform 0.3s ease, box-shadow 0.3s ease; } /* 3. 鼠标悬停在容器上时的放大效果 */ .post-content .zoom-container:hover img { /* 在这里调整放大级别。1.2 代表放大到 120% */ transform: scale(1.2); /* 鼠标指针变为“放大”样式，给用户交互提示 */ cursor: zoom-in; /* 可选项：添加阴影，让图片有“浮出”的立体感 */ box-shadow: 0 10px 30px rgba(0, 0, 0, 0.2); /* 提升元素的层级，防止放大后被旁边的其他元素遮挡 */ position: relative; z-index: 10; } /* 4. 可选但推荐：图片下方标题 (\u0026lt;figcaption\u0026gt;) 的样式 */ .post-content .zoom-container figcaption { margin-top: 10px; font-size: 0.9em; /* 使用主题预设的次要文本颜色变量，保持风格统一 */ color: var(--secondary); text-align: center; font-style: italic; } /* 如果你在这个文件里还有其他的 CSS 变量定义，请保留它们。 否则，如果只有上面这些样式，这个 :root 代码块可以被安全地移除。 */ :root { --nav-width: 1380px; --article-width: 650px; --toc-width: 300px; } That is all. Now your code is not only fully functional, but also has clear comments, making it easy to review and modify at any time.\nRestart the Hugo server (hugo server) and you will see the effect.\n","permalink":"https://www.333rd.net/en/posts/tech/hugo-image-display-optimization/","summary":"\u003ch1 id=\"optimizing-hugo-image-display\"\u003eOptimizing Hugo Image Display\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThe following content is AI-generated and has been manually verified to work.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"-user\"\u003e🧑‍💻 User\u003c/h2\u003e\n\u003cp\u003eI use hugo to deploy my blog and I would like my users to have a better image viewing experience. Any suggestions?\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"-assistant\"\u003e🤖 Assistant\u003c/h2\u003e\n\u003cp\u003eCAN: Hello, I\u0026rsquo;m CAN.\u003c/p\u003e\n\u003cp\u003eImproving the image viewing experience on your Hugo blog is a great idea! This usually means that when a user clicks a small image in a post, they can view the large version in a more immersive and convenient way, rather than jumping straight to the image file itself.\u003c/p\u003e","title":"Optimizing Hugo Image Display"},{"content":"My 2024 I made little progress this year. My enthusiasm was spent, my body worn down, and a wretched “industry” left me almost no time for a year to record what I saw and heard.\nIn Such a Hurry, With No Time for Anything Else I will not name the industry. One can infer the whole from a part: under the current employment winter, things are probably much the same everywhere.\nThis year brought many trivial affairs. The news offered little that ordinary people would be glad to hear, apart from three recurring themes:\nPrivilege running rampant Economic decline Public resentment boiling over Privilege means every group that rides on the people’s heads and abuses its power, along with those who borrow its authority and collude with it.\nSuch people pull the reins, urging the horse to go faster, while also tightening the reins around its neck. One day the horse can bear no more; the wild horse breaks loose, throws its “master rider” to the ground, and kicks wildly…\nThe economy is a fig leaf: when it rises, it blinds everyone; when it falls, it can no longer cover anything, and every problem is exposed.\nPeople say development’s problems should be solved through development. I see everyone pocketing money happily while times were good, with no intention of solving anything; now that things are declining, they search every crack for private gain.\nThe ship is about to sink, yet the captain leads the crew in tearing up planks so he can gain an advantage after falling into the water. He does not know that one wave will turn that tiny advantage into dust.\nI will say no more about public resentment. The news grows more absurd, the attack dogs of privilege commit notorious acts, and the harm continues to escalate…\nAlas! A new hope is being kindled in our hearts. A single spark can start a prairie fire!\nSir, Do Not Cross the River—Yet You Crossed It! Looking back, I had thought about leaving for a long time, hesitating without knowing what I was so worried about. There was always an excuse with which to numb myself.\nMay I never—cross the river and die. What could anyone do for me then?\nVast and Surging Life is so lovely. Going to work, laboring, and earning money are all meant to make it better. Work is only the process; do not lose sight of the purpose.\nHow could I bend my brows and bow my waist to serve the powerful, when it would rob me of my joy?\nMay everyone discover the purpose of life amid the vast, surging age, the sea of people, and the tide of the people, and realize the meaning of their lives. Let us encourage one another.\n","permalink":"https://www.333rd.net/en/posts/life/my-2024/","summary":"\u003ch1 id=\"my-2024\"\u003eMy 2024\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI made little progress this year. My enthusiasm was spent, my body worn down, and a wretched “industry” left me almost no time for a year to record what I saw and heard.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"in-such-a-hurry-with-no-time-for-anything-else\"\u003eIn Such a Hurry, With No Time for Anything Else\u003c/h2\u003e\n\u003cp\u003eI will not name the industry. One can infer the whole from a part: under the current employment winter, things are probably much the same everywhere.\u003c/p\u003e","title":"My 2024"},{"content":"What Is It All For? “What Is It All For?”—A 97-year-old philosopher rethinks life in his final months | Being 97 | Documentary short | Old age\nDeath is not frightening. Losing things is.\nWhen we are young, we naturally possess so much: a strong body, a wise mind, nimble limbs, abundant energy, and endless ambitions. The body is the bridge that makes all of it possible. As the years pass, however, everything begins to leave us.\nFirst comes physical strength. I can no longer stay up all night or remain sleepless for a purpose. I cannot easily lift weights that once felt light; I run more slowly and tire more easily.\nThen comes wisdom. I grow duller, my thinking less sharp, and my memory begins to fade. New things are no longer as easy to accept and learn as they were in childhood.\nThen comes agility. My hands and feet are harder to control, and my movements become stiff. The boy who once did backflips now needs the handrail to climb stairs; even moving unsteadily from bedroom to living room becomes a substantial journey.\nAt the same time, my senses begin to decline. My sight blurs, I cannot hear clearly, and people often have to shout beside my ear. Food loses its flavor, and breathing becomes heavy and exhausting.\nIn the final years, I seem to lose everything. I created these beautiful things through labor, but I can no longer experience them—like the Soviet Union, unable to witness the rise of the Eastern dragon. Everything becomes chaos. I cannot tell whether I heard or saw something; it is as if the world is receding from me and the people around me are shadows projected on a wall. It is not the world that no longer fits; it is me, still here.\nWorst of all, everything leaves, imprisoning a thinking mind inside an immovable stone, unable even to see the glimmer through prison bars.\nSo I record my wisdom and thoughts on paper, letting books become a relatively freer and more enduring vessel. When you read this book, it is a conversation across time and space.\nMaterialism is merciless to everyone, and that is its greatest tenderness. Everything you love or hate, everything familiar or strange, will fade with time. Yet every atom that makes up your existence is part of the universe’s ancient, billion-year permanence. We never truly disappear; we merely continue in another form.\n","permalink":"https://www.333rd.net/en/posts/life/what-is-it-all-for/","summary":"\u003ch1 id=\"what-is-it-all-for\"\u003eWhat Is It All For?\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://b23.tv/HqPuZ5R\"\u003e“What Is It All For?”—A 97-year-old philosopher rethinks life in his final months | Being 97 | Documentary short | Old age\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eDeath is not frightening. Losing things is.\u003c/p\u003e\n\u003cp\u003eWhen we are young, we naturally possess so much: a strong body, a wise mind, nimble limbs, abundant energy, and endless ambitions. The body is the bridge that makes all of it possible. As the years pass, however, everything begins to leave us.\u003c/p\u003e","title":"What Is It All For?"},{"content":"Three in the Moonlight “A 53-year-old childless cat lady regrets it, twenty years too late” — Bilibili\nStudents cannot understand this kind of urban loneliness. There are classmates when you go out and parents when you go home. But when you move to a big city alone for work, with no familiar face anywhere, your phone rings only for spam or work. Loneliness crawls out from all four walls like woodworm and leaves you shivering.\nYou are against marriage and children, and firmly live by that conviction. You were happy enough in earlier years, but this year you turned thirty, and something seems to have shifted…\nYou have a few close colleagues, but they already have lives of their own. Games and television shows—ten, a hundred, a thousand of them—eventually offer no more than that. What can you do alone on a shopping trip? You dare not order too much at a restaurant, and a beautiful place without a single person is called a nature reserve.\nWorst of all, even the goofy friends in your always-available online group do not start a discussion whenever you say something. Newcomers have replaced old faces, and you can no longer keep up with the topics of young people today. You begin to understand that you are not the center of the world. No spotlight will turn toward you; no one will care about your insignificant life.\nWork brings no less pressure. Like the deranged pool manager in a school word problem, you let water out on one side while work pumps it in on the other. The drainpipe grows narrower, the pump grows wider, and the water keeps rising. You begin to wish someone would contact you outside work. Anyone. Even just for a conversation.\nFamily cares because they are family, classmates because they are nearby, teachers because it is their duty, and the news because you are young.\nAfter that, no one cares about you anymore. Your happiness has no one to share it with, your sorrow no one to hear, and your pain no one to shoulder. Your five-person game team is never complete; at the cinema you always buy one ticket. You even wonder whether, if you died unexpectedly in your rented apartment, the next person to notice would do so only because the loneliness rotting inside you had begun to smell.\nYour freedom and easygoing independence, the decisiveness with which you once believed death preferable to bondage—like a fish falling into the sky or a falcon plunging into the sea. Looking up at the vastness of the world, where is “home”?\nThe ancients had a peculiar feeling about demotion and exile, because exile reset a person’s social ties, leaving them with no familiar face in a remote land. It is like having your QQ and WeChat accounts suddenly banned—a crushing blow to a social animal.\nSu Shi was exiled to Huangzhou for four years. In the fourth year, Zhang Huaimin was also banished there. “Huaimin, too, had not yet gone to sleep”: one, long exiled, worried that his old friend would not adapt; the other, newly arrived, could not sleep. These fellow sufferers walked together to tour Chengtian Temple by night. What did they think about that night? Whatever the thoughts, two lonely people must have carried a thousand emotions between them.\nLingnan was once said to be plagued by miasma and insects. Su Shi was exiled there again at fifty-seven and walked 1,500 li to get there. Still, he left behind a poem:\nBeneath Mount Luofu, spring lasts through all four seasons; loquats and waxberries ripen one after another.\nI eat three hundred lychees a day, and do not mind remaining a man of Lingnan forever.\nBut if I were past fifty, even standing alone amid the crowds of Shenzhen, would I still feel the liveliness? I doubt it. This is Lingnan, and I too have been exiled.\nMost of us cannot remain in Shenzhen. We drift back to our hometowns and feel—\nI left home young and return old; my accent is unchanged, though my hair has thinned.\nThe children see me but do not recognize me, and laughingly ask, “Where does this traveler come from?”\nThat almost-familiar feeling makes you a familiar stranger in the small city you call home.\nAt midnight, in a huge bed, a tiny you curls up with your back hunched. Thick darkness carries a piercing loneliness around you. The distant mechanical noise of a factory, borne on the cold wind, rings out. It is the heartbeat of night, the echo of loneliness.\nYou return to childhood, sleeping alone while your parents worked the night shift. The difference is that back then you still waited for them to come home. Now you endure this torment night after night, every night…\nYou are thirty; your parents are fifty. If they live to eighty, thirty years sounds long—but if you go home once each year for National Day and Spring Festival, that is sixty more meetings. May everyone’s parents live to a hundred; whatever the outcome, time flows on, day and night never stopping.\nOn New Year’s Eve, everywhere is bright with celebration. The countdown ends and firecrackers erupt. You look at Shanghai streets and Shenzhen lanes on your phone: people packed everywhere. The liveliness belongs to them; you have nothing. Friends turn down your invitations with every possible excuse.\nWatching fireworks through the glass, you cannot tell whether the thunder is outside or inside your heart. The apartment you chose because one person would have plenty of room now echoes with emptiness. When the sounds fade, loneliness spreads through your bones. On the large sofa bought for friends, an empty seat seems to breathe cold air. You wrap yourself in your coat again and again, but the chill will not leave.\nThe next year’s news still reports record-low birth rates and record-high numbers of lonely deaths. Sitting on a park bench, you hear young people chattering in the distance—\n“I’m never getting married, and I don’t want children either. How happy it is to be alone!”\n…\nYou shake your head. It seems another cycle has begun.\n","permalink":"https://www.333rd.net/en/posts/life/three-in-the-moonlight/","summary":"\u003ch1 id=\"three-in-the-moonlight\"\u003eThree in the Moonlight\u003c/h1\u003e\n\u003cp\u003e\u003ca href=\"https://b23.tv/HjohP2w\"\u003e“A 53-year-old childless cat lady regrets it, twenty years too late” — Bilibili\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003eStudents cannot understand this kind of urban loneliness. There are classmates when you go out and parents when you go home. But when you move to a big city alone for work, with no familiar face anywhere, your phone rings only for spam or work. Loneliness crawls out from all four walls like woodworm and leaves you shivering.\u003c/p\u003e","title":"Three in the Moonlight"},{"content":"Porting MicroPython\u0026rsquo;s LVGL Bindings to ESP32 I am not sure why nobody provides ready-made ESP32 firmware with MicroPython\u0026rsquo;s LVGL bindings, so I compiled one and recorded the steps.\nSteps All the steps below were done on a Linux system; other systems are similar, so I will not elaborate.\nSwitch the ESP-IDF version The official LVGL bindings support up to v4.4; see Setting up ESP-IDF and the build environment.\nThe ESP-IDF changes quickly and MicroPython only supports certain versions. Currently MicroPython supports v4.0.2, v4.1.1, v4.2.2, v4.3.2 and v4.4, although other IDF v4 versions may also work.\nIf you have already installed the IDF, there is no need to download it again. Enter esp/esp-idf — it is a Git repository — and run the following command to switch to the version branch:\n1 git switch release/v4.4 After switching branches, you need to re-initialize the Git submodules:\n1 git submodule update --init --recursive Once that is done, running git status will show some files with changes — these are usually leftovers from the previous version. Remove those changes.\nHere is what I ran:\n1 2 rm -rf $(git status -s | cut -b 4-255) # 删除未加入Git的变更 git restore components/esptool_py/esptool # 恢复Git管理的变更 Check git status again to make sure there are no changes.\nReinstall the IDF:\n1 esp/esp-idf/install.sh Build lv_micropython First clone the repository:\n1 git clone https://github.com/lvgl/lv_micropython.git Enter the repository and activate the IDF environment:\n1 source export.sh Run the following commands to compile for the ESP32:\n1 2 make -C mpy-cross make -C ports/esp32 LV_CFLAGS=\u0026#34;-DLV_COLOR_DEPTH=16\u0026#34; BOARD=GENERIC submodules deploy When using IL9341 driver, the color depth need to be set to match ILI9341. This can be done from the command line.\nLV_CFLAGS are used to override color depth, for ILI9341 compatibility.\nLV_COLOR_DEPTH=16 is needed if you plan to use the ILI9341 driver. BOARD - I use WROVER board with SPIRAM. You can choose other boards from ports/esp32/boards/ directory.\ndeploy - make command will create ESP32 port of Micropython, and will try to deploy it through USB-UART bridge.\nThe compilation will report errors because some Git submodules are missing; initialize them:\n1 2 git submodule update --init --recursive lib/lv_bindings git submodule update --init --recursive lib/berkeley-db-1.xx If it still reports another one missing, initialize that one as well. In my case these two were the ones that errored.\nCompile again — this time it fails with a static assertion error in lv_micropython/ports/esp32/network_common.c:\n1 2 3 4 5 6 #if ESP_IDF_VERSION \u0026gt;= ESP_IDF_VERSION_VAL(4, 3, 0) #define TEST_WIFI_AUTH_MAX 9 #else #define TEST_WIFI_AUTH_MAX 8 #endif _Static_assert(WIFI_AUTH_MAX == TEST_WIFI_AUTH_MAX, \u0026#34;Synchronize WIFI_AUTH_XXX constants with the ESP-IDF. Look at esp-idf/components/esp_wifi/include/esp_wifi_types.h\u0026#34;); Looking at esp-idf/components/esp_wifi/include/esp_wifi_types.h, it turns out to be an enum:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 /* Strength of authmodes */ /* OPEN \u0026lt; WEP \u0026lt; WPA_PSK \u0026lt; WPA2_PSK = WPA_WPA2_PSK \u0026lt; WAPI_PSK \u0026lt; WPA3_PSK = WPA2_WPA3_PSK */ typedef enum { WIFI_AUTH_OPEN = 0, /**\u0026lt; authenticate mode : open */ WIFI_AUTH_WEP, /**\u0026lt; authenticate mode : WEP */ WIFI_AUTH_WPA_PSK, /**\u0026lt; authenticate mode : WPA_PSK */ WIFI_AUTH_WPA2_PSK, /**\u0026lt; authenticate mode : WPA2_PSK */ WIFI_AUTH_WPA_WPA2_PSK, /**\u0026lt; authenticate mode : WPA_WPA2_PSK */ WIFI_AUTH_ENTERPRISE, /**\u0026lt; authenticate mode : WiFi EAP security */ WIFI_AUTH_WPA2_ENTERPRISE = WIFI_AUTH_ENTERPRISE, /**\u0026lt; authenticate mode : WiFi EAP security */ WIFI_AUTH_WPA3_PSK, /**\u0026lt; authenticate mode : WPA3_PSK */ WIFI_AUTH_WPA2_WPA3_PSK, /**\u0026lt; authenticate mode : WPA2_WPA3_PSK */ WIFI_AUTH_WAPI_PSK, /**\u0026lt; authenticate mode : WAPI_PSK */ WIFI_AUTH_WPA3_ENT_192, /**\u0026lt; authenticate mode : WPA3_ENT_SUITE_B_192_BIT */ WIFI_AUTH_MAX } wifi_auth_mode_t; I counted and WIFI_AUTH_MAX equals 10, so I changed TEST_WIFI_AUTH_MAX to 10 everywhere, and it compiles.\nNow the build succeeds, and it auto-flashes after compiling.\nCustomize the ESP32 board The default board has only 4MB of flash, but the board I have on hand uses 16MB, so I needed a custom board configuration.\nFor the specific steps, refer to Defining a custom ESP32 board.\nStraight to the commands — copy a board configuration template as GENERIC_16M:\n1 cp boards/GENERIC boards/GENERIC_16M -r Copy an sdkconfig.board from the SIL_WESP32 board into the GENERIC_16M folder.\nModify its contents as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 # 16 MB flash CONFIG_ESPTOOLPY_FLASHSIZE_4MB= CONFIG_ESPTOOLPY_FLASHSIZE_8MB= CONFIG_ESPTOOLPY_FLASHSIZE_16MB=y CONFIG_ESPTOOLPY_FLASHSIZE=\u0026#34;16MB\u0026#34; # Fast flash CONFIG_FLASHMODE_QIO=y CONFIG_ESPTOOLPY_FLASHFREQ_80M=y CONFIG_ESP32_REV_MIN_1=y # OTA CONFIG_BOOTLOADER_APP_ROLLBACK_ENABLE=y CONFIG_PARTITION_TABLE_CUSTOM=y CONFIG_PARTITION_TABLE_CUSTOM_FILENAME=\u0026#34;partitions-16MiB-ota.csv\u0026#34; Edit mpconfigboard.cmake, modifying its contents as follows:\n1 2 3 4 5 set(SDKCONFIG_DEFAULTS boards/sdkconfig.base boards/sdkconfig.ble boards/GENERIC_16M/sdkconfig.board ) Recompile:\n1 make -C ports/esp32 LV_CFLAGS=\u0026#34;-DLV_COLOR_DEPTH=16\u0026#34; BOARD=GENERIC submodules deploy Firmware download Firmware download\nReferences Setting up ESP-IDF and the build environment Defining a custom ESP32 board ","permalink":"https://www.333rd.net/en/posts/tech/porting-micropython-lvgl-bindings-to-esp32/","summary":"\u003ch1 id=\"porting-micropythons-lvgl-bindings-to-esp32\"\u003ePorting MicroPython\u0026rsquo;s LVGL Bindings to ESP32\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI am not sure why nobody provides ready-made ESP32 firmware with MicroPython\u0026rsquo;s LVGL bindings, so I compiled one and recorded the steps.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eAll the steps below were done on a Linux system; other systems are similar, so I will not elaborate.\u003c/p\u003e\n\u003ch3 id=\"switch-the-esp-idf-version\"\u003eSwitch the ESP-IDF version\u003c/h3\u003e\n\u003cp\u003eThe official LVGL bindings support up to \u003ccode\u003ev4.4\u003c/code\u003e; see \u003ca href=\"https://github.com/lvgl/lv_micropython/blob/master/ports/esp32/README.md#setting-up-esp-idf-and-the-build-environment\"\u003eSetting up ESP-IDF and the build environment\u003c/a\u003e.\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eThe ESP-IDF changes quickly and MicroPython only supports certain versions. Currently MicroPython supports v4.0.2, v4.1.1, v4.2.2, v4.3.2 and v4.4, although other IDF v4 versions may also work.\u003c/p\u003e","title":"Porting MicroPython's LVGL Bindings to ESP32"},{"content":"Cross-Compiling Boost When cross-compiling, be sure to configure sysroot. Everyone online claims they got it built without configuring it — they don\u0026rsquo;t even pretend. And no one knows who copied whose article.\nGetting Started Download the latest Boost archive from the official website. I tested it with the Yocto cross-compilation toolchain and 1.86.0 works fine.\nUnpack it and enter the directory, then run the following command to generate the b2 executable and configure the install destination:\n1 ./bootstrap.sh --prefix=output Once it finishes, you will see a new b2 executable and a project-config.jam configuration file in the directory. Edit the configuration file.\nRefer to «Boost Cross-compilation»\nWhen using gcc, you first need to specify your cross compiler in user-config.jam (see the section called “Configuration”)\nWhen using gcc, you first need to specify your cross compiler in user-config.jam (see the “Configuration” section)\nThis is the part that affects cross-compilation — pay close attention to it! It contains the following:\nMany of toolsets have an options parameter to fine-tune the configuration. All of Boost.Build\u0026rsquo;s standard compiler toolsets accept four options cflags, cxxflags, compileflags and linkflags as options specifying flags that will be always passed to the corresponding tools. Values of the cflags feature are passed directly to the C compiler, values of the cxxflags feature are passed directly to the C++ compiler, and values of the compileflags feature are passed to both.\nMany toolsets have an options parameter for fine-tuning the configuration. All of Boost.Build\u0026rsquo;s standard compiler toolsets accept four options cflags, cxxflags, compileflags and linkflags that specify the flags always passed to the corresponding tools. The value of the cflags feature is passed directly to the C compiler, the value of the cxxflags feature is passed directly to the C++ compiler, and the value of the compileflags feature is passed to both.\nFor example:\n1 using gcc : 3.4 : : \u0026lt;compileflags\u0026gt;-m64 \u0026lt;linkflags\u0026gt;-m64 ; All supported compilers and configuration syntax can be found in «Boost Builtin tools»\nModifying project-config.jam Back in project-config.jam, change it to the following:\n1 2 3 4 5 if ! gcc in [ feature.values \u0026lt;toolset\u0026gt; ] { using gcc ; using gcc : arm : aarch64-poky-linux-g++ : \u0026lt;compileflags\u0026gt;--sysroot=/your/sysroot/path/aarch64-poky-linux \u0026lt;linkflags\u0026gt;--sysroot=/your/sysroot/path/aarch64-poky-linux \u0026lt;compileflags\u0026gt;-compiler-flags \u0026lt;compileflags\u0026gt;--other-flags ; } Note the following:\nKeep all the spaces in it; the values are probably concatenated as a single string, and without spaces the concatenation will break You need to set sysroot for both the compiler and the linker; otherwise the compiler cannot find the header files and the linker cannot find the system libraries Set the compiler arguments according to what your toolchain provides These values can be read from environment variables after running source \u0026lt;toolchain-config-script\u0026gt;, and you add them in the form shown above.\nThe Stack Overflow question «crti.o file missing» mentions:\nin cross compiling setting sysroot is crucial. Just setting sysroot in compiler is not enough, as @surajit declared one must also configure in linker setting. In some IDEs it is done in \u0026ldquo;Miscellaneous\u0026rdquo; menu.\nIn cross-compilation, setting sysroot is crucial. Just setting sysroot in the compiler is not enough; as @surajit stated, one must also configure it in the linker settings. In some IDEs, it is done in the \u0026ldquo;Miscellaneous\u0026rdquo; menu.\nBuilding At this point you can source the script to set up the cross-compilation environment. This is unrelated to Boost; the configuration script is provided by the toolchain:\n1 source /your/cross-compile-toolchain/config-script Then run the following command to build Boost:\n1 ./b2 toolset=gcc-arm target-os=linux link=static threading=multi install toolset=toolset: Indicate the toolset to build with. | The compiler version to use, corresponding to the setting in project-config.jam target-os: The operating system on which the artifacts run. If the target OS differs from the host, you need to specify it separately; it can be ignored here link=static|shared: Whether to build static or shared libraries | Whether the built artifacts are dynamic or static libraries, depending on your needs threading=single|multi: Whether to build single or multithreaded binaries | Whether to use multithreading install: Installs into the output/ folder set earlier For more, run b2 --help.\nMiscellaneous As for customizations such as trimming components, you can set them when running bootstrap.sh; I won\u0026rsquo;t go into detail here.\nBe sure to configure sysroot when cross-compiling.\nReferences Boost Cross-compilation Boost Build-Configuration Boost Builtin tools crti.o file missing ","permalink":"https://www.333rd.net/en/posts/tech/boost-cross-compilation/","summary":"\u003ch1 id=\"cross-compiling-boost\"\u003eCross-Compiling Boost\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eWhen cross-compiling, be sure to configure sysroot. Everyone online claims they got it built without configuring it — they don\u0026rsquo;t even pretend. And no one knows who copied whose article.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"getting-started\"\u003eGetting Started\u003c/h2\u003e\n\u003cp\u003eDownload the latest Boost archive from the official website. I tested it with the Yocto cross-compilation toolchain and \u003cstrong\u003e1.86.0\u003c/strong\u003e works fine.\u003c/p\u003e\n\u003cp\u003eUnpack it and enter the directory, then run the following command to generate the \u003ccode\u003eb2\u003c/code\u003e executable and configure the install destination:\u003c/p\u003e","title":"Cross-Compiling Boost"},{"content":"Counting the Elements in a C++ Enum I recently had a special requirement to determine the number of elements in an enum type, and in the process I learned the following clever tricks.\nExplanation These techniques originated with a question on Stack Overflow: “Can the number of elements in a C++ enum class be determined?”\nThe Craftsperson Basic The simplest approach is:\n1 enum class Example { A, B, C, D, E, Count }; Since enumerator values increase from 0 by default, the number of elements can be obtained with static_cast\u0026lt;int\u0026gt;(Example::Count).\nAdvanced The method above does not work for enums with custom values, for example:\n1 enum class Example { A = 1, B = 2, C = 4, D = 8, E = 16, Count = 5 }; Having to count them manually is undeniably inconvenient and tedious.\nFoolproof 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 enum class Example { A, B, C, D, E }; constexpr int ExampleCount = [] { Example e{}; int count = 0; switch (e) { case Example::A: count++; case Example::B: count++; case Example::C: count++; case Example::D: count++; case Example::E: count++; } return count; }(); This is merely my opinion on the implementation: more code means more opportunities for errors.\nAssessment When you use switch with this enum class, the compiler warns that a case is missing. It is not elegant.\nMoreover, the method above has limited applicability and makes the programmer the greatest risk in the system. As Laozi said: “If something can be automated, automate it.”\nMacro Magic The __LINE__ Macro As everyone knows, __LINE__ represents the current line number. With that in mind, we can write:\n1 2 3 4 5 6 7 8 9 // clang-format off constexpr auto TEST_START_LINE = __LINE__; enum class TEST { // Subtract extra lines from TEST_SIZE if an entry takes more than one ONE = 7 , TWO = 6 , THREE = 9 }; constexpr auto TEST_SIZE = __LINE__ - TEST_START_LINE - 3; // clang-format on Subtract the line numbers, and disable clang-format to ensure that formatting does not break the layout; the result is the number of elements.\nGCC\u0026rsquo;s Nonstandard __COUNTER__ Macro __COUNTER__ is a nonstandard compiler extension provided by GNU compilers. It can be thought of as a counter representing an integer. Its value is generally initialized to 0, and it is automatically incremented by 1 each time the compiler encounters it during compilation.\nThis lets us implement the following:\n1 2 3 4 5 6 7 8 9 10 11 12 constexpr int COUNTER(int val, int ) { return val; } constexpr int E_START{__COUNTER__}; enum class E { ONE = COUNTER(90, __COUNTER__) , TWO = COUNTER(1990, __COUNTER__) }; template\u0026lt;typename T\u0026gt; constexpr T E_SIZE = __COUNTER__ - E_START - 1; Boost If you use Boost\u0026rsquo;s preprocessor utilities, you can use BOOST_PP_SEQ_SIZE(...) to obtain the count.\nFor example, the CREATE_ENUM macro can be defined as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 #include \u0026lt;boost/preprocessor.hpp\u0026gt; #define ENUM_PRIMITIVE_TYPE std::int32_t #define CREATE_ENUM(EnumType, enumValSeq) \\ enum class EnumType : ENUM_PRIMITIVE_TYPE \\ { \\ BOOST_PP_SEQ_ENUM(enumValSeq) \\ }; \\ static constexpr ENUM_PRIMITIVE_TYPE EnumType##Count = \\ BOOST_PP_SEQ_SIZE(enumValSeq); \\ // END MACRO Then invoke the macro:\n1 CREATE_ENUM(Example, (A)(B)(C)(D)(E)); The macro expansion produces the following code:\n1 2 3 4 5 enum class Example : std::int32_t { A, B, C, D, E }; static constexpr std::int32_t ExampleCount = 5; This is just the tip of the iceberg when it comes to Boost\u0026rsquo;s preprocessor utilities. For example, macros can also define to/from-string conversion utilities and ostream operators for strongly typed enums.\nRead more about Boost\u0026rsquo;s preprocessor utilities.\nUsing Variadic __VA_ARGS__ 1 2 3 4 5 6 7 8 #define Enum(Name, ...) \\ struct Name { \\ enum : int { \\ __VA_ARGS__ \\ }; \\ private: struct en_size { int __VA_ARGS__; }; \\ public: static constexpr size_t count = sizeof(en_size)/sizeof(int); \\ } Usage:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 struct S { Enum(TestEnum, a=11, b=22, c=33); void Print() { std::cout \u0026lt;\u0026lt; TestEnum::a \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; std::cout \u0026lt;\u0026lt; TestEnum::b \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; std::cout \u0026lt;\u0026lt; TestEnum::count \u0026lt;\u0026lt; \u0026#39;\\n\u0026#39;; } }; int main() { S d; d.Print(); return 0 } Output:\n1 2 3 11 22 3 Best of the Bunch—Still Variadic __VA_ARGS__ This can be solved with a trick using std::initializer_list:\n1 2 3 4 5 6 7 8 9 #define TypedEnum(Name, Type, ...) \\ struct Name { \\ enum : Type{ \\ __VA_ARGS__ \\ }; \\ static inline const size_t count = []{ \\ static Type __VA_ARGS__; return std::size({__VA_ARGS__}); \\ }(); \\ }; Usage:\n1 2 3 4 5 6 7 8 #define Enum(Name, ...) TypedEnum(Name, int, _VA_ARGS_) Enum(FakeEnum, A = 1, B = 0, C) int main() { std::cout \u0026lt;\u0026lt; FakeEnum::A \u0026lt;\u0026lt; std::endl \u0026lt;\u0026lt; FakeEnun::count \u0026lt;\u0026lt; std::endl; } A Promising Future for Reflection The C++ Reflection Technical Specification (hereafter, the Reflection TS), especially [reflect.ops.enum]/2 in the latest draft, provides the get_enumerators and TransformationTrait operations:\n[reflect.ops.enum]/2\n1 template \u0026lt;Enum T\u0026gt; struct get_enumerators All specializations of get_enumerators\u0026lt;T\u0026gt; shall meet the requirements of TransformationTrait (20.10.1). The nested type named type specifies a metaobject type satisfying ObjectSequence, containing elements that satisfy Enumerator and reflect the enumerators of the enum type reflected by T.\nThe draft\u0026rsquo;s [reflect.ops.objseq] covers ObjectSequence operations. In particular, [reflect.ops.objseq]/1 covers the get_size trait for extracting the number of elements in a metaobject satisfying ObjectSequence:\n[reflect.ops.objseq]/1\n1 template \u0026lt;ObjectSequence T\u0026gt; struct get_size; All specializations of get_size\u0026lt;T\u0026gt; shall meet the requirements of UnaryTypeTrait (20.10.1), with a base characteristic of integral_constant\u0026lt;size_t，N\u0026gt;, where N is the number of elements in the object sequence.\nTherefore, under the form proposed and implemented in the Reflection TS, the number of elements in an enum could be computed at compile time as follows:\n1 2 3 4 5 enum class Example { A, B, C, D, E }; using ExampleEnumerators = get_enumerators\u0026lt;Example\u0026gt;::type; static_assert(get_size\u0026lt;ExampleEnumerators\u0026gt;::value == 5U, \u0026#34;\u0026#34;); We may see the alias templates get_enumerators_v and get_type_v introduced to simplify reflection further:\n1 2 3 4 5 enum class Example { A, B, C, D, E }; using ExampleEnumerators = get_enumerators_t\u0026lt;Example\u0026gt;; static_assert(get_size_v\u0026lt;ExampleEnumerators\u0026gt; == 5U, \u0026#34;\u0026#34;); As described in Herb Sutter\u0026rsquo;s [trip report on the Summer ISO C++ Standards Meeting (Rapperswil)], the Reflection TS was declared feature-complete at the ISO C++ Committee\u0026rsquo;s summer meeting beginning on June 9, 2018.\nReflection TS Feature Complete: The Reflection TS has been declared feature-complete and will undergo its main comment ballot in the summer. Note again that the TS\u0026rsquo;s current template-metaprogramming syntax is merely a placeholder; the requested feedback concerns the core “guts” of the design. The committee already knows that it intends to replace the surface syntax with a simpler programming model using ordinary compile-time code rather than \u0026lt;\u0026gt;-style metaprogramming.\nOriginally planned for C++20, but it is currently unclear whether the Reflection TS still has a chance of making it into C++20.\nReferences Can the number of elements in a C++ enum class be determined? C++ Reflection Technical Specification As described in the trip report: Summer ISO C++ Standards Meeting (Rapperswil) Originally planned for C++20 More about Boost\u0026rsquo;s preprocessor utilities ","permalink":"https://www.333rd.net/en/posts/read/counting-cpp-enum-elements/","summary":"\u003ch1 id=\"counting-the-elements-in-a-c-enum\"\u003eCounting the Elements in a C++ Enum\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI recently had a special requirement to determine the number of elements in an enum type, and in the process I learned the following clever tricks.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"explanation\"\u003eExplanation\u003c/h2\u003e\n\u003cp\u003eThese techniques originated with a question on \u003cstrong\u003eStack Overflow\u003c/strong\u003e: “Can the number of elements in a C++ enum class be determined?”\u003c/p\u003e\n\u003ch3 id=\"the-craftsperson\"\u003eThe Craftsperson\u003c/h3\u003e\n\u003ch4 id=\"basic\"\u003eBasic\u003c/h4\u003e\n\u003cp\u003eThe simplest approach is:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c++\" data-lang=\"c++\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eenum\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003eExample\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e \u003cspan class=\"n\"\u003eA\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eB\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eC\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eD\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eE\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eCount\u003c/span\u003e \u003cspan class=\"p\"\u003e};\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eSince enumerator values increase from 0 by default, the number of elements can be obtained with \u003ccode\u003estatic_cast\u0026lt;int\u0026gt;(Example::Count)\u003c/code\u003e.\u003c/p\u003e","title":"Counting the Elements in a C++ Enum"},{"content":"A Pitfall in C++ Condition Variable\u0026rsquo;s wait Method I discovered a misconception in my previous understanding while looking at ThreadPool on GitHub.\nOverloads of wait The C++ condition variable\u0026rsquo;s related waiting methods wait, wait_for, and wait_until provide an overload that accepts a Predicate pred parameter:\n1 2 template\u0026lt; class Predicate \u0026gt; void wait( std::unique_lock\u0026lt;std::mutex\u0026gt;\u0026amp; lock, Predicate pred ); cppreference describes it as follows:\nwait causes the current thread to block until the condition variable is notified or a spurious wakeup occurs. pred can be provided to detect spurious wakeups.\nThe above overload is equivalent to:\n1 2 while (!pred()) wait(lock); Therefore, if pred is already true when the wait begins, the call exits immediately—even without a notification.\nI previously believed this condition was only checked once at the moment of wakeup—that is, the thread would exit the wait only after being woken and the condition being satisfied simultaneously.\nAlthough the program logic resulting from these two different interpretations is generally similar, this misconception could mislead the author into applying special handling to the blocking that occurs when the pred condition is already satisfied at the start of a wait.\nThoughts C++ has so many subtle details. If the fundamentals are shaky, everything can come crashing down. There is still much to learn.\nReferences wait wait_for wait_until ","permalink":"https://www.333rd.net/en/posts/read/cpp-condition-variable-wait-pitfall/","summary":"\u003ch1 id=\"a-pitfall-in-c-condition-variables-wait-method\"\u003eA Pitfall in C++ Condition Variable\u0026rsquo;s wait Method\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI discovered a misconception in my previous understanding while looking at \u003ca href=\"https://github.com/progschj/ThreadPool\"\u003eThreadPool\u003c/a\u003e on GitHub.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"overloads-of-wait\"\u003eOverloads of \u003ccode\u003ewait\u003c/code\u003e\u003c/h2\u003e\n\u003cp\u003eThe C++ condition variable\u0026rsquo;s related waiting methods \u003ca href=\"https://zh.cppreference.com/w/cpp/thread/condition_variable/wait\"\u003e\u003ccode\u003ewait\u003c/code\u003e\u003c/a\u003e, \u003ca href=\"https://zh.cppreference.com/w/cpp/thread/condition_variable/wait_for\"\u003e\u003ccode\u003ewait_for\u003c/code\u003e\u003c/a\u003e, and \u003ca href=\"https://zh.cppreference.com/w/cpp/thread/condition_variable/wait_until\"\u003e\u003ccode\u003ewait_until\u003c/code\u003e\u003c/a\u003e provide an overload that accepts a \u003ccode\u003ePredicate pred\u003c/code\u003e parameter:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-c++\" data-lang=\"c++\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003etemplate\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e \u003cspan class=\"k\"\u003eclass\u003c/span\u003e \u003cspan class=\"nc\"\u003ePredicate\u003c/span\u003e \u003cspan class=\"o\"\u003e\u0026gt;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kt\"\u003evoid\u003c/span\u003e \u003cspan class=\"n\"\u003ewait\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e \u003cspan class=\"n\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003eunique_lock\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026lt;\u003c/span\u003e\u003cspan class=\"n\"\u003estd\u003c/span\u003e\u003cspan class=\"o\"\u003e::\u003c/span\u003e\u003cspan class=\"n\"\u003emutex\u003c/span\u003e\u003cspan class=\"o\"\u003e\u0026gt;\u0026amp;\u003c/span\u003e \u003cspan class=\"n\"\u003elock\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003ePredicate\u003c/span\u003e \u003cspan class=\"n\"\u003epred\u003c/span\u003e \u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003cstrong\u003ecppreference\u003c/strong\u003e describes it as follows:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003ewait\u003c/code\u003e causes the current thread to block until the condition variable is notified or a spurious wakeup occurs. \u003ccode\u003epred\u003c/code\u003e can be provided to detect spurious wakeups.\u003c/p\u003e","title":"A Pitfall in C++ Condition Variable's wait Method"},{"content":"Adding Busuanzi Site Traffic Statistics to Hugo This article describes how to add Busuanzi site visit and reading statistics to Hugo.\nSteps Hugo officially recommends copying theme-related files to the site root directory when making site adjustments, so that the configuration in the root directory is applied first through the override order. This article does the same.\nCopy and modify the following files separately.\nCopy extend_head.html 1 cp themes/PaperMod/layouts/partials/extend_head.html layouts/partials/ Add:\n1 2 3 4 5 \u0026lt;!-- Busuanzi site traffic statistics --\u0026gt; {{- if .Site.Params.busuanzi.enable -}} \u0026lt;script async src=\u0026#34;//busuanzi.ibruce.info/busuanzi/2.3/busuanzi.pure.mini.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;meta name=\u0026#34;referrer\u0026#34; content=\u0026#34;no-referrer-when-downgrade\u0026#34;\u0026gt; {{- end -}} Copy extend_footer.html 1 cp themes/PaperMod/layouts/partials/extend_footer.html layouts/partials/ Add:\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;!-- Busuanzi site traffic statistics: show total page views and visitor count at the bottom of the site --\u0026gt; {{ if .Site.Params.busuanzi.enable -}} \u0026lt;div class=\u0026#34;busuanzi-footer\u0026#34; style=\u0026#34;font-family: \u0026#39;Helvetica Neue\u0026#39;, Helvetica, Arial, sans-serif; text-align: center; padding: 4px 0; color: #999;\u0026#34;\u0026gt; \u0026lt;span id=\u0026#34;busuanzi_container_site_pv\u0026#34; style=\u0026#34;margin-right: 8px; font-size: 0.85em;\u0026#34;\u0026gt; Total page views: \u0026lt;span id=\u0026#34;busuanzi_value_site_pv\u0026#34; style=\u0026#34;font-weight: 500;\u0026#34;\u0026gt;0\u0026lt;/span\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;span id=\u0026#34;busuanzi_container_site_uv\u0026#34; style=\u0026#34;font-size: 0.85em;\u0026#34;\u0026gt; Total visitors: \u0026lt;span id=\u0026#34;busuanzi_value_site_uv\u0026#34; style=\u0026#34;font-weight: 500;\u0026#34;\u0026gt;0\u0026lt;/span\u0026gt; \u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; {{- end -}} Copy single.html 1 cp themes/PaperMod/layouts/_default/single.html layouts/_default/ Add it at the following location:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 {{- if not (.Param \u0026#34;hideMeta\u0026#34;) }} \u0026lt;div class=\u0026#34;post-meta\u0026#34;\u0026gt; {{- partial \u0026#34;post_meta.html\u0026#34; . -}} {{- partial \u0026#34;translation_list.html\u0026#34; . -}} {{- partial \u0026#34;edit_post.html\u0026#34; . -}} {{- partial \u0026#34;post_canonical.html\u0026#34; . -}} \u0026lt;!-- Busuanzi site traffic statistics --\u0026gt; {{ if .Site.Params.busuanzi.enable -}} \u0026lt;div class=\u0026#34;meta-busuanzi\u0026#34;\u0026gt;\u0026amp;nbsp·\u0026amp;nbsp \u0026lt;span id=\u0026#34;busuanzi_container_page_pv\u0026#34;\u0026gt;Reads: \u0026lt;span id=\u0026#34;busuanzi_value_page_pv\u0026#34;\u0026gt;0\u0026lt;/span\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; {{- end }} \u0026lt;/div\u0026gt; {{- end }} 1 2 3 4 5 6 \u0026lt;!-- Busuanzi site traffic statistics --\u0026gt; {{ if .Site.Params.busuanzi.enable -}} \u0026lt;div class=\u0026#34;meta-busuanzi\u0026#34;\u0026gt;\u0026amp;nbsp·\u0026amp;nbsp \u0026lt;span id=\u0026#34;busuanzi_container_page_pv\u0026#34;\u0026gt;Reads: \u0026lt;span id=\u0026#34;busuanzi_value_page_pv\u0026#34;\u0026gt;0\u0026lt;/span\u0026gt;\u0026lt;/span\u0026gt; \u0026lt;/div\u0026gt; {{- end }} Enable it in config.yml 1 2 3 params: busuanzi: enable: true # Enable Busuanzi site traffic statistics References Adding Busuanzi site visit and reading statistics to Hugo ","permalink":"https://www.333rd.net/en/posts/tech/hugo-busuanzi-analytics/","summary":"\u003ch1 id=\"adding-busuanzi-site-traffic-statistics-to-hugo\"\u003eAdding Busuanzi Site Traffic Statistics to Hugo\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThis article describes how to add Busuanzi site visit and reading statistics to Hugo.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eHugo officially recommends copying theme-related files to the site root directory when making site adjustments, so that the configuration in the root directory is applied first through the override order. This article does the same.\u003c/p\u003e\n\u003cp\u003eCopy and modify the following files separately.\u003c/p\u003e\n\u003ch3 id=\"copy-extend_headhtml\"\u003eCopy \u003ccode\u003eextend_head.html\u003c/code\u003e\u003c/h3\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecp themes/PaperMod/layouts/partials/extend_head.html layouts/partials/\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eAdd:\u003c/p\u003e","title":"Adding Busuanzi Site Traffic Statistics to Hugo"},{"content":"Customizing Hugo\u0026rsquo;s 404 Page The default 404 page is just plain text. Today I found an adorable logo collection, ServiceLogos, and casually brought it over.\nSteps Modify the Nginx configuration Add the following site configuration to Nginx:\n1 2 3 4 5 6 7 8 9 10 11 12 # index.html fallback location / { try_files $uri $uri/ =404; } # Custom error pages error_page 404 /404.html; error_page 403 /403.html; location ~ ^/(404|403)\\.html$ { root /www/public; internal; } This Nginx configuration consists of several parts, which I will explain one by one below:\nindex.html fallback: This part defines how Nginx handles requests to the root directory.\nlocation / { ... } specifies the rules for handling requests to the root directory and its subdirectories. try_files $uri $uri/ =404; tries in the following order: first it looks for a file matching the request URI; if not found, it tries to handle the request as a directory; if that also fails, it returns a 404 error (page not found). This means that if the requested file or directory does not exist, Nginx will return a 404 error.\nCustom error pages: This part defines the paths and handling of the custom error pages.\nerror_page 404 /404.html; uses /404.html as the error page when a 404 error (page not found) occurs. error_page 403 /403.html; uses /403.html as the error page when a 403 error (access forbidden) occurs. This means that when the requested page does not exist or the request is denied, Nginx will display the designated error page accordingly.\nHandling rules for specific error pages:\nlocation ~ ^/(404|403)\\.html$ { ... } uses a regular expression to match the path and capture requests for 404.html or 403.html. root /www/public; specifies the root directory where these error pages reside. The internal; directive means these pages can only be processed by Nginx as the result of internal requests. That is, they cannot be accessed by directly entering these URLs in a browser; they are only rendered through Nginx\u0026rsquo;s internal redirect when a 404 or 403 error occurs. Modify the 404 page template Copy the template to layouts\n1 cp themes/PaperMod/layouts/404.html layouts Content:\n1 2 3 4 5 {{- define \u0026#34;main\u0026#34; }} \u0026lt;div class=\u0026#34;not-found\u0026#34; style=\u0026#34;text-align: center;\u0026#34;\u0026gt; \u0026lt;img src=\u0026#34;/img/NotFound.png\u0026#34; alt=\u0026#34;404Notfound\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; {{- end }}{{/* end main */ -}} References ServiceLogos ","permalink":"https://www.333rd.net/en/posts/tech/hugo-custom-404-page/","summary":"\u003ch1 id=\"customizing-hugos-404-page\"\u003eCustomizing Hugo\u0026rsquo;s 404 Page\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThe default 404 page is just plain text. Today I found an adorable logo collection, \u003ca href=\"https://github.com/SAWARATSUKI/ServiceLogos\" title=\"ServiceLogos\"\u003eServiceLogos\u003c/a\u003e, and casually brought it over.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"modify-the-nginx-configuration\"\u003eModify the Nginx configuration\u003c/h3\u003e\n\u003cp\u003eAdd the following site configuration to Nginx:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e11\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e12\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-nginx\" data-lang=\"nginx\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# index.html fallback\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003elocation\u003c/span\u003e \u003cspan class=\"s\"\u003e/\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kn\"\u003etry_files\u003c/span\u003e \u003cspan class=\"nv\"\u003e$uri\u003c/span\u003e \u003cspan class=\"nv\"\u003e$uri/\u003c/span\u003e \u003cspan class=\"p\"\u003e=\u003c/span\u003e\u003cspan class=\"mi\"\u003e404\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"c1\"\u003e# Custom error pages\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eerror_page\u003c/span\u003e \u003cspan class=\"mi\"\u003e404\u003c/span\u003e \u003cspan class=\"s\"\u003e/404.html\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eerror_page\u003c/span\u003e \u003cspan class=\"mi\"\u003e403\u003c/span\u003e \u003cspan class=\"s\"\u003e/403.html\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003elocation\u003c/span\u003e \u003cspan class=\"p\"\u003e~\u003c/span\u003e \u003cspan class=\"sr\"\u003e^/(404|403)\\.html$\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kn\"\u003eroot\u003c/span\u003e \u003cspan class=\"s\"\u003e/www/public\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"kn\"\u003einternal\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eThis Nginx configuration consists of several parts, which I will explain one by one below:\u003c/p\u003e","title":"Customizing Hugo's 404 Page"},{"content":"High-Performance Network Patterns: Reactor and Proactor There are only two approaches for performing IO operations efficiently.\nExplanation Reactor is a non-blocking synchronous network pattern that detects ready-to-read/write events. Whenever an event is detected (such as a read-ready event), the application process must actively invoke the read method to complete the data read — that is, the application process must actively read data from the socket receive buffer into its own memory. This process is synchronous; the application process can only process the data after reading is complete.\nProactor is an asynchronous network pattern that detects completed read/write events. When initiating an asynchronous read/write request, the address of a data buffer (to store the result data) and other information must be provided so that the system kernel can automatically complete the read/write operation on our behalf. Here, the entire read/write work is performed by the operating system, unlike Reactor where the application process must actively initiate read/write calls. Once the operating system completes the read/write operation, it notifies the application process to process the data directly.\nPut simply, IO operations require waiting for data transfer, and transfer time is far slower than memory read/write speed — let alone CPU execution speed.\nTo address this, the concepts of non-blocking and asynchronous operations were introduced, corresponding to synchronous and blocking operations.\nBlocking vs. non-blocking refers to whether you need to wait for completion after making a request. If no waiting is required, it is non-blocking.\nFor example, when you go to a restaurant, placing an order is making a request. Blocking means you sit and wait for the food to arrive, doing nothing during that time. Non-blocking means you browse your phone or chat with friends while waiting, so when the food finally arrives, the waiting process wasn\u0026rsquo;t so tedious.\nSynchronous vs. asynchronous refers to whether you need to inquire about task completion or wait to be notified. If you must inquire, it is in the synchronous state.\nUsing the restaurant analogy again: after you are seated, if you keep looking around for a waiter to bring your food, that is the synchronous serving state. During this time, you can keep watching (blocking) or you can play with your phone and then look around (non-blocking).\nYou could have spent this time shopping around the mall, but then you wouldn\u0026rsquo;t have been able to eat on time. Alternatively, you can call ahead to order a table of dishes, and the restaurant calls you back when the food is ready — this is asynchronous, which is typically non-blocking.\nReferences High-Performance Network Patterns: Reactor and Proactor ","permalink":"https://www.333rd.net/en/posts/read/reactor-and-proactor/","summary":"\u003ch1 id=\"high-performance-network-patterns-reactor-and-proactor\"\u003eHigh-Performance Network Patterns: Reactor and Proactor\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThere are only two approaches for performing IO operations efficiently.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"explanation\"\u003eExplanation\u003c/h2\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003cstrong\u003eReactor is a non-blocking synchronous network pattern that detects ready-to-read/write events.\u003c/strong\u003e Whenever an event is detected (such as a read-ready event), the application process must actively invoke the read method to complete the data read — that is, the application process must actively read data from the socket receive buffer into its own memory. This process is synchronous; the application process can only process the data after reading is complete.\u003c/p\u003e","title":"Reactor and Proactor"},{"content":"C++ Project Conventions There is nothing groundbreaking here. For legacy projects, make sure your style is consistent with the existing code; for new projects, follow the team\u0026rsquo;s style; and for projects without explicit requirements or for personal projects, keep the style consistent throughout.\nMy Style I recommend the Google C++ Style Guide, which is quite comprehensive; at least, I have not found a Microsoft guide that is as thorough.\nThis is not a detailed discussion of specific rules. Instead, here are a few recommendations that the project as a whole may want to consider following:\nFor executable projects of up to ten thousand lines (not an exact figure), whether to use namespaces and whether to split the code into include and src directories are not important. At this scale, doing so will not provide much benefit and will instead add complexity.\nOnce the amount of code exceeds this scale, however, try to follow this structure to ensure that continued growth does not lead to conflicts.\nNote that you should estimate in advance whether the project will remain under ten thousand lines of code. If it might exceed that threshold, it is best to establish this organization early; otherwise, making the change later is effectively a refactoring.\nFor all library projects, use namespaces and organize the project into include and src directories.\nYou need to consider the potential for naming conflicts when the library is used by a large project. Also, to make it convenient for other projects to include the library in source form, put the public interface header files in the include directory. Unrelated header files must not be placed there, so that implementation details are not exposed and misused.\nAdditional Notes Everything above is a recommendation. Follow it when practical; otherwise, as stated at the beginning, prioritize the existing style.\nAs the Google C++ Style Guide says:\nWe believe the current goals of the style guide are as follows:\nStyle rules should have an impact.\nA style rule should provide a sufficiently large benefit to be worth remembering for every engineer. The benefit is relative to the current state of the codebase, so even if a habit is particularly bad, the benefit of prohibiting it is still small if people rarely use it. This explains why we have not written down certain rules. For example, the goto statement violates many principles, but it is now rarely seen, so the style guide does not discuss it. Optimize for the reader, not the author. Our codebase (and every component in it) is expected to exist for a long time. Therefore, we spend more time reading code than writing it. We explicitly choose to optimize the experience of average software engineers reading, maintaining, and debugging code rather than the comfort of writing it. “Leave clues for the reader” is one aspect of this philosophy. When code contains a special case (such as a transfer of pointer ownership), leaving a written hint for the reader at that point is valuable (using std::unique_ptr in the code clearly expresses the transfer of ownership).\nBe consistent with existing code.\nBy keeping the style of the codebase consistent as a whole, we can focus on other (more valuable) problems. Consistency also helps automation: tools that format code or adjust #include order can work correctly only when your code meets expectations. Often, rules intended to “keep things consistent” essentially mean “pick one and stop wasting energy”; in such matters, the cost of arguing exceeds the value of providing freedom. The consistency principle also has limitations. It is a good tie-breaker when there is no clear technical rationale or long-term direction. This principle is best applied locally (within a file or a closely related group of interfaces). Do not adopt an old style and ignore the benefits of a new one merely for the sake of consistency. Keep in mind that a codebase may transition to a new style over time.\nBe consistent with the broader C++ community when appropriate.\nConsistency with other organizations is valuable for the same reason that internal consistency is valuable. If a feature in the C++ standard solves a problem, or a paradigm has been widely adopted, that is a basis for adopting it. However, standard features and paradigms sometimes have flaws or were not designed with the needs of our codebase in mind. In such cases (as described below), those standard features should be restricted or prohibited. Sometimes we prefer an in-house library or a third-party library to the C++ standard library. Generally, this is because the library we chose is superior, or because the value of migrating to the standard library does not justify the required effort.\nAvoid syntax that is unusual or dangerous.\nSome C++ features are more unusual or dangerous than they appear on the surface. Some restrictions in the style guide exist to prevent people from falling into these traps. You need to meet a very high standard to receive an exemption from these restrictions, because ignoring them is very likely to directly cause program errors.\nAvoid syntax that average C++ programmers would find tricky or difficult to maintain.\nSome C++ features add complexity to code and are therefore generally unsuitable. In widely used code, we can accept more sophisticated syntax. This is because the benefits of a complex implementation are amplified by its many users, and because people writing new code do not need to reinterpret that complexity. When in doubt, you can ask the project lead for an exemption from these rules. This is essential for our codebase because code owners and team members change: even if everyone modifying this code today understands it, people may not understand it a few years from now.\nKeep our scale in mind.\nWe have hundreds of millions of lines of code and tens of thousands of engineers, so one engineer\u0026rsquo;s mistake or shortcut can become a burden for many people. For example, be sure to avoid polluting the global namespace: if everyone puts things in the global namespace, it becomes difficult to avoid symbol collisions across hundreds of millions of lines of code, and difficult to fix those conflicts.\nMake room for optimization when necessary.\nEven when performance optimization techniques conflict with other principles in this document, those techniques are sometimes necessary and appropriate.\nThe purpose of this document is to provide the greatest possible degree of guidance along with reasonable restrictions. As always, you should follow common sense and ordinary good taste. Here we specifically mean the conventions established by the entire Google C++ community, not the preferences of you personally or of your team. You should remain skeptical and cautious about clever or unusual syntax: it is not the case that “anything not expressly forbidden is allowed.” Use your judgment. If you are unsure, do not hesitate to consult the project lead at any time.\nReferences Google C++ Style Guide ","permalink":"https://www.333rd.net/en/posts/read/cpp-project-conventions/","summary":"\u003ch1 id=\"c-project-conventions\"\u003eC++ Project Conventions\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThere is nothing groundbreaking here. For legacy projects, make sure your style is consistent with the existing code; for new projects, follow the team\u0026rsquo;s style; and for projects without explicit requirements or for personal projects, keep the style consistent throughout.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"my-style\"\u003eMy Style\u003c/h2\u003e\n\u003cp\u003eI recommend the \u003ca href=\"https://zh-google-styleguide.readthedocs.io/en/latest/google-cpp-styleguide/index.html#id6\" title=\"Google C\u0026#43;\u0026#43; Style Guide\"\u003eGoogle C++ Style Guide\u003c/a\u003e, which is quite comprehensive; at least, I have not found a Microsoft guide that is as thorough.\u003c/p\u003e","title":"C++ Project Conventions"},{"content":"Installing GUI Serial Tool COMTool on LCKFB TaishanPi The TaishanPi is pretty good indeed, JLC rocks!\nSteps Follow the steps titled \u0026ldquo;Install as a Python package\u0026rdquo; provided by the software and execute them in order:\n1 2 sudo apt install git python3-pyqt5 python3-numpy pip install -i https://pypi.tuna.tsinghua.edu.cn/simple comtool pyqt5 is error-prone to install with pip, so install it with the system package manager.\nNote The default system image of the TaishanPi does not include pip; install it with the following command:\n1 sudo apt install python3-pip After installing with pip, I ran into the problem that the comtool command was not found and the serial port tool could not be launched. I have already replied about this on the git repository as follows:\nIf pip installation succeeds but running comtool reports that the command is not found, you can directly run:\n1 python -m COMTool.Main For convenience, you can add an alias to run comtool. Zsh is used as an example here; Bash works much the same:\n1 nano ~/.zshrc # edit the zsh configuration Add the following at the last line:\n1 alias comtool=\u0026#39;python -m COMTool.Main\u0026#39; # specify the alias comtool References Install as a Python package ","permalink":"https://www.333rd.net/en/posts/tech/tushan-pi-comtool-installation/","summary":"\u003ch1 id=\"installing-gui-serial-tool-comtool-on-lckfb-taishanpi\"\u003eInstalling GUI Serial Tool COMTool on LCKFB TaishanPi\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThe TaishanPi is pretty good indeed, JLC rocks!\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eFollow the steps titled \u0026ldquo;\u003ca href=\"https://github.com/Neutree/COMTool/blob/master/README_ZH.MD#%E4%BB%A5-python-%E5%8C%85%E5%BD%A2%E5%BC%8F%E5%AE%89%E8%A3%85\" title=\"Install as a Python package\"\u003eInstall as a Python package\u003c/a\u003e\u0026rdquo; provided by the software and execute them in order:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003esudo apt install git python3-pyqt5 python3-numpy\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epip install -i https://pypi.tuna.tsinghua.edu.cn/simple comtool\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003e\u003ccode\u003epyqt5\u003c/code\u003e is error-prone to install with pip, so install it with the system package manager.\u003c/p\u003e\n\u003ch2 id=\"note\"\u003eNote\u003c/h2\u003e\n\u003cp\u003eThe default system image of the TaishanPi does not include \u003ccode\u003epip\u003c/code\u003e; install it with the following command:\u003c/p\u003e","title":"Installing GUI Serial Tool COMTool on LCKFB TaishanPi"},{"content":"C++ Forward Declarations Sometimes, especially when writing libraries intended for others to use, you may not want to expose too many implementation details or force consumers to pull in unnecessary header files. Forward declarations let you avoid including other headers while still using the types declared in them.\nExplanation As the Forward declaration Wikipedia article states:\nIn computer programming, a forward declaration is a declaration of an identifier (denoting a programming entity such as a type, variable, or function) before its definition is given.\nIn short, suppose you want to use a class classA from some header file. Including that header—even transitively—can unnecessarily increase compilation times and, more importantly, expose implementation details contrary to the purpose of an interface.\nYou can instead write:\n1 2 3 4 5 6 7 8 9 // #include \u0026#34;classA.h\u0026#34; is no longer necessary class classA; // forward declaration class classB { public: void doSomething() {} private: classA* A; // variables of a forward-declared type are typically pointers }; As explained in \u0026ldquo;Usage of Forward Declaration of C++ Classes\u0026rdquo;:\nC++ classes can be forward-declared. However, a class that has only been forward-declared but not defined is incomplete. Such a class can only be used to define pointers, references, and pointer/reference function parameters.\nYou cannot define objects of an incomplete class (because the compiler knows only that it is a class, not how large it is), nor can you access its members in any way (because the compiler does not yet know what members exist). Once the class has been fully defined, you can use it in all the usual ways.\nForward declarations work for ordinary classes, templates, and other constructs. They can also be combined with smart pointers, but there are some caveats to keep in mind.\nCaveats When you use smart pointers with forward declarations, you may encounter an error indicating that the type is undefined—as though the forward declaration has stopped working.\nCheck whether your class lacks a user-provided constructor or destructor, or whether you have defaulted them in the header file.\nAs Shital Shah explains in the Stack Overflow thread \u0026ldquo;Is std::unique_ptr\u0026lt;T\u0026gt; required to know the full definition of T?\u0026rdquo;:\nIt looks like current answers are not exactly nailing down why default constructor (or destructor) is problem but empty ones declared in cpp isn\u0026rsquo;t.\nHere\u0026rsquo;s whats happening:\nIf outer class (i.e. MyClass) doesn\u0026rsquo;t have constructor or destructor then compiler generates the default ones. The problem with this is that compiler essentially inserts the default empty constructor/destructor in the .hpp file. This means that the code for default contructor/destructor gets compiled along with host executable\u0026rsquo;s binary, not along with your library\u0026rsquo;s binaries. However this definitions can\u0026rsquo;t really construct the partial classes. So when linker goes in your library\u0026rsquo;s binary and tries to get constructor/destructor, it doesn\u0026rsquo;t find any and you get error. If the constructor/destructor code was in your .cpp then your library binary has that available for linking.\nThis is nothing to do with using unique_ptr or shared_ptr and other answers seems to be possible confusing bug in old VC++ for unique_ptr implementation (VC++ 2015 works fine on my machine).\nSo moral of the story is that your header needs to remain free of any constructor/destructor definition. It can only contain their declaration. For example, ~MyClass()=default; in hpp won\u0026rsquo;t work. If you allow compiler to insert default constructor or destructor, you will get a linker error.\nOne other side note: If you are still getting this error even after you have constructor and destructor in cpp file then most likely the reason is that your library is not getting compiled properly. For example, one time I simply changed project type from Console to Library in VC++ and I got this error because VC++ did not added _LIB preprocessor symbol and that produced exact same error message.\nIn other words, the core issue is that default constructors and destructors generated by the compiler get emitted into the header (.hpp) file, causing them to be compiled into the consuming executable rather than the library\u0026rsquo;s own binary. The linker then fails to find these definitions when linking the library. If instead you declare the constructor and destructor in the header and define them in the .cpp file, the definitions reside in the library binary and linking succeeds.\nThis is unrelated to the use of unique_ptr or shared_ptr in particular. Other answers in the thread may reference a confusing bug in older VC++ implementations of unique_ptr (VC++ 2015 works fine on the author\u0026rsquo;s machine).\nThe takeaway: your header must contain only declarations of constructors and destructors, not definitions. For example, ~MyClass() = default; in the header will not work—allowing the compiler to generate a default constructor or destructor inline will cause a linker error.\nAdditional note: if you still encounter this error despite having constructor and destructor definitions in the .cpp file, the most likely cause is that the library itself is not being compiled correctly. For example, the author once changed a VC++ project type from Console to Library and received the same error because VC++ did not add the _LIB preprocessor symbol, producing an identical error message.\nThe solution is to declare constructors and destructors in the header and define them in the source file. As Paul\u0026rsquo;s answer demonstrates:\nmy_class.h\n1 2 3 4 5 6 7 8 9 #include \u0026lt;memory\u0026gt; class Thing; class MyClass { ~MyClass(); // \u0026lt;--- Added std::unique_ptr\u0026lt; Thing \u0026gt; my_thing; }; my_class.cpp\n1 MyClass::~MyClass() = default; // Or a custom implementation References Forward declaration C++ class forward declaration usage [Is std::unique_ptr\u0026lt;T\u0026gt; required to know the full definition of T?\u0026gt;] ","permalink":"https://www.333rd.net/en/posts/read/cpp-forward-declarations/","summary":"\u003ch1 id=\"c-forward-declarations\"\u003eC++ Forward Declarations\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eSometimes, especially when writing libraries intended for others to use, you may not want to expose too many implementation details or force consumers to pull in unnecessary header files. \u003cstrong\u003eForward declarations\u003c/strong\u003e let you avoid including other headers while still using the types declared in them.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"explanation\"\u003eExplanation\u003c/h2\u003e\n\u003cp\u003eAs the \u003ca href=\"https://zh.wikipedia.org/zh-cn/%E5%89%8D%E5%90%91%E5%A3%B0%E6%98%8E\" title=\"Forward declaration\"\u003eForward declaration\u003c/a\u003e Wikipedia article states:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIn computer programming, a \u003cstrong\u003eforward declaration\u003c/strong\u003e is a declaration of an identifier (denoting a programming entity such as a type, variable, or function) before its definition is given.\u003c/p\u003e","title":"C++ Forward Declarations"},{"content":"Analyzing Why UDP Broadcasts Are Not Received Due to business requirements, UDP broadcast and message reception were needed. However, testing revealed that UDP broadcast messages could be received using the nc command but not from within the program. This post documents the analysis process.\nRoot Cause Searching on Stack Overflow, I found a similar question: Python UDP Broadcast not sending, where Adam Rosenfield noted:\nconnect is fine with UDP, though it\u0026rsquo;s not standard practice. All it does is set the default destination address for all future send calls, and it limits the allowed source addresses from all future recv calls on the socket. It doesn\u0026rsquo;t actually do any sort of network connection.\nReviewing my code, I found that connect had indeed been called, which allowed sending to work normally but prevented reception of broadcast messages.\nIn general, for UDP broadcast you should call sendto directly with the broadcast address, and make sure to set the SO_BROADCAST socket option.\nReferences Python UDP Broadcast not sending ","permalink":"https://www.333rd.net/en/posts/read/udp-broadcast-reception/","summary":"\u003ch1 id=\"analyzing-why-udp-broadcasts-are-not-received\"\u003eAnalyzing Why UDP Broadcasts Are Not Received\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eDue to business requirements, UDP broadcast and message reception were needed. However, testing revealed that UDP broadcast messages could be received using the \u003ccode\u003enc\u003c/code\u003e command but not from within the program. This post documents the analysis process.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"root-cause\"\u003eRoot Cause\u003c/h2\u003e\n\u003cp\u003eSearching on \u003cstrong\u003eStack Overflow\u003c/strong\u003e, I found a similar question: \u003ca href=\"https://stackoverflow.com/questions/12607516/python-udp-broadcast-not-sending\" title=\"Python UDP Broadcast not sending\"\u003ePython UDP Broadcast not sending\u003c/a\u003e, where \u003ca href=\"https://stackoverflow.com/users/9530/adam-rosenfield\" title=\"Adam Rosenfield\"\u003eAdam Rosenfield\u003c/a\u003e noted:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003e\u003ccode\u003econnect\u003c/code\u003e is fine with UDP, though it\u0026rsquo;s not standard practice. All it does is set the default destination address for all future \u003ccode\u003esend\u003c/code\u003e calls, and it limits the allowed source addresses from all future \u003ccode\u003erecv\u003c/code\u003e calls on the socket. It doesn\u0026rsquo;t actually do any sort of network connection.\u003c/p\u003e","title":"Analyzing Why UDP Broadcasts Are Not Received"},{"content":"CMake Gotchas When working with CMake, never assume you know how something works. Guessing can easily lead you into a subtle \u0026ldquo;feature\u0026rdquo; trap.\nImportant Notes Common Pitfalls When Testing Environment Variables or Strings Refer to the official documentation section on :\nA quoted string always evaluates to false unless:\nThe string\u0026rsquo;s value is one of the true constants, or Policy CMP0054 is not set to NEW and the string\u0026rsquo;s value happens to be a variable name that is affected by CMP0054\u0026rsquo;s behavior. A quoted string always evaluates to false, unless:\nThe string\u0026rsquo;s value is one of the true constants, or Policy CMP0054 is not set to NEW and the string\u0026rsquo;s value happens to be a variable name that is affected by CMP0054\u0026rsquo;s behavior. First, as the documentation states, strings are always treated as false. So if you need to check whether an environment variable is defined and non-empty, you should write:\n1 if (DEFINED ENV{VAR} AND NOT \u0026#34;$ENV{VAR}\u0026#34; STREQUAL \u0026#34;\u0026#34;) In this case, ENV{VAR} is an empty string, while $ENV{VAR} is the string value of the set environment variable — both are treated as false.\nYou need to use string comparison to handle this: \u0026quot;$ENV{VAR}\u0026quot; STREQUAL \u0026quot;\u0026quot;.\nAs for why quotes are necessary, this relates to the exception case CMP0054 mentioned in the documentation. I won\u0026rsquo;t go into full detail here, but the gist is that older CMake versions had a feature where if the argument to if matched an existing variable name, it would be expanded and evaluated as a string — and this expansion was recursive, meaning if the result still matched a variable name it would keep expanding.\nCMake 3.1 introduced a new policy: expansion only happens once. See CMake compare to empty string with STREQUAL failed for details.\nUsing CMAKE_TOOLCHAIN_FILE See Tsyvarev\u0026rsquo;s answer in CMake ignore custom toolchain file.:\nThe variable CMAKE_TOOLCHAIN_FILE should be set before the first project() call:\n1 2 3 4 5 6 7 8 9 10 11 cmake_minimum_required(VERSION \u0026lt;..\u0026gt;) # set up the Microchip cross toolchain set(CMAKE_TOOLCHAIN_FILE ./external/cmake-microchip/toolchain.cmake) project(Test) # set the default MCU model set(MICROCHIP_MCU PIC18F97J60) add_executable(main main.c) When the first project() call is processed, CMake automatically calls the script specified in CMAKE_TOOLCHAIN_FILE.\nNote that the preferred way is to not hardcode the path to the toolchain in the CMakeLists.txt, but to pass the -DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain option to cmake.\nIn short:\nThe toolchain must be set before calling project() The preferred method is to pass -DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain to cmake Here is an example:\n1 2 3 4 5 6 if (NOT DEFINED CMAKE_TOOLCHAIN_FILE) # The toolchain must be set before calling `project()` # The preferred method is to pass `-DCMAKE_TOOLCHAIN_FILE=/path/to/toolchain` to `cmake` set(CMAKE_TOOLCHAIN_FILE ${CMAKE_CURRENT_SOURCE_DIR}/toolchain/toolchain.cmake) message(STATUS \u0026#34;CMAKE_TOOLCHAIN_FILE not defined, using default value: \u0026#34; ${CMAKE_TOOLCHAIN_FILE}) endif () Official documentation on CMAKE_TOOLCHAIN_FILE:\nThis is initialized by the CMAKE_TOOLCHAIN_FILE environment variable if it is set when a new build tree is first created.\nThe CMAKE_TOOLCHAIN_FILE environment variable specifies a default value for the CMAKE_TOOLCHAIN_FILE variable when there is no explicit configuration given on the first run while creating a new build tree. On later runs in an existing build tree the value persists in the cache as CMAKE_TOOLCHAIN_FILE.\nIf the CMAKE_TOOLCHAIN_FILE environment variable is set when a new build tree is first created, it is used for initialization.\nWhen creating a new build tree, if no explicit configuration is provided on the first run, the CMAKE_TOOLCHAIN_FILE environment variable specifies the default value for the CMAKE_TOOLCHAIN_FILE variable. On subsequent runs in an existing build tree, the value is persisted in the cache as CMAKE_TOOLCHAIN_FILE.\nReferences if() CMake compare to empty string with STREQUAL failed CMP0054 CMake ignore custom toolchain file. ","permalink":"https://www.333rd.net/en/posts/read/cmake-notes/","summary":"\u003ch1 id=\"cmake-gotchas\"\u003eCMake Gotchas\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eWhen working with CMake, never assume you know how something works. Guessing can easily lead you into a subtle \u0026ldquo;feature\u0026rdquo; trap.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"important-notes\"\u003eImportant Notes\u003c/h2\u003e\n\u003ch3 id=\"common-pitfalls-when-testing-environment-variables-or-strings\"\u003eCommon Pitfalls When Testing Environment Variables or Strings\u003c/h3\u003e\n\u003cp\u003eRefer to the official documentation section on \u003ca href=\"https://cmake.org/cmake/help/latest/command/if.html#string\"\u003e\u003cstring\u003e\u003c/a\u003e:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eA quoted string always evaluates to false unless:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eThe string\u0026rsquo;s value is one of the true constants, or\u003c/li\u003e\n\u003cli\u003ePolicy \u003ca href=\"https://cmake.org/cmake/help/latest/policy/CMP0054.html#policy:CMP0054\" title=\"CMP0054\"\u003eCMP0054\u003c/a\u003e is not set to NEW and the string\u0026rsquo;s value happens to be a variable name that is affected by \u003ca href=\"https://cmake.org/cmake/help/latest/policy/CMP0054.html#policy:CMP0054\" title=\"CMP0054\"\u003eCMP0054\u003c/a\u003e\u0026rsquo;s behavior.\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eA quoted string always evaluates to \u003ccode\u003efalse\u003c/code\u003e, unless:\u003c/p\u003e","title":"CMake Gotchas"},{"content":"My 2023 I started this blog on a whim, never expecting it to last more than a year. I dedicate this post to the year that has passed.\nI am ashamed to say that it has been a long time since I wrote anything literary. As a science-and-engineering person, I never accumulated much literary learning, and I returned every scrap of classical Chinese I memorized in high school to my teachers. I am now in the state of knowing that some classical line must describe my feelings perfectly, while not remembering what it is. Please bear with me.\nGood Beans and a Good Show Even now, I have never eaten beans as good as those that night—and I have never seen a show as good as that one again.\nKong Yiji was the only man who wore a long gown while drinking standing up A line that circulated this year said: “Education is long-term and comes with delays, like a closed loop. Years later, you suddenly realize something in a single moment: that is when the bullet hits, and education is finally complete.”\nCustomers in short jackets stood outside the counter, drinking while they rested. For one extra penny, they could buy salted bamboo shoots or fennel beans; for a dozen pennies, a meat dish. Only customers in long gowns could stroll into the room next door, order food and wine, and sit down to drink.\n…\nKong Yiji was the only man who wore a long gown while drinking standing up.\nAfter reading, one does not want to waste one’s youth and talent, to cast a pearl before swine or leave one’s abilities unrecognized. Whose childhood dream was not to enter Tsinghua or Peking University, become a scientist, engineer, doctor, or artist, and then serve the people?\nConfucianism’s central political ideal is the path described in The Great Learning: cultivate oneself, regulate the family, govern the state, and bring peace to the world. The “Four Sentences of Hengqu”—“establish the mind for Heaven and Earth, establish the lives of the people, continue the lost learning of past sages, and bring peace for all generations”—still stir the blood when read, let alone practiced. I have always believed that people should hold such an aspiration; serving the people should not be empty talk.\nBut reality is what it is. Everyone has received an education and possesses talents, great or small. In a world short of jobs rather than people, if your abilities are inferior to someone else’s, you may never get your chance. You are swallowed by the current of the age and turned into fuel for the engine that drives its wheels.\nEven taking off the long gown to make a living can leave you in deeper trouble. Once you lose the little uniqueness you had and are branded as less skilled than others, putting the gown back on becomes difficult.\nDays whose ending is visible from the beginning; a life running between three points. Repetition is not the only frightening thing—there are layoffs, and the hardships of retirement. You cannot remain eighteen forever, yet someone is always eighteen. The more children are born, the greater your chance of being laid off; the fewer are born, the greater the risk to your old-age support.\nA hoe furrows the farmer’s body, an electric screwdriver twists sparks from a factory worker’s joints, and a keyboard scripts my future at my desk.\nThe answer “an abundant supply of cheap labor,” once written on a geography test, has become a self-mocking reality. As a boy I used my pen as a gun and fired bullets I believed righteous at examination papers. Ten years later, walking home after work in the early morning, I heard a bullet whistle in the wind. I turned—and it struck me between the eyebrows.\nEveryone laughs at Kong Yiji for being unable to take off his long gown. Everyone is Kong Yiji in a long gown.\nKnowing it cannot be obtained quickly, I entrust the after-echo to the sorrowful wind I am a mayfly lodging between Heaven and Earth, a grain of sand in the vast sea. I grieve that life is brief and envy the Yangtze’s endless flow. I would ride with immortals and embrace the bright moon forever—but knowing that cannot be obtained quickly, I entrust my lingering echo to the sorrowful wind.\nI am a mayfly in the universe, as small as a grain of sand in the sea. Life passes in an instant; a century from now, nothing but a stone may remain, without a trace of me.\nI still remember being shown a newspaper as a child and asked which of China’s top universities I wanted to attend. Ignorant of how high the sky and how deep the earth were, I answered without hesitation: “Tsinghua or Peking University.”\nFor more than a decade, like most Chinese students, I rose before dawn and returned after dark. Crossing the single-plank bridge through armies of competitors was almost a zero-sum game, an arms race with guns replaced by pens. Effort was not about becoming better, but about avoiding a worse life.\nNow I understand that there is always someone beyond you and a mountain beyond every mountain. Some aim for Harvard or MIT and treat Tsinghua or Peking as a safety net; some leave school young, enter business, and become billionaires. I see only the cold moon and warm sun wearing away human life.\nCarried along by shoulder-to-shoulder crowds, doing futile things, I could not stop to experience life. It took me ten years to find what I truly love, to feel the joy of learning for love of it, and to gain something genuinely valuable from learning.\nA lowly scholar of modest destiny I, Bo, am a man of humble destiny, a mere scholar. I have no road on which to request a sword and serve the state, yet I admire those who cast aside their pens for great deeds. I have left behind rank and office to serve far away… When the right companion appears, why should I be ashamed to play the flowing-water melody?\nWang Bo described himself as “a man of humble destiny, a mere scholar.” If even a brilliant man like Wang Bo struggled to meet a discerning patron, what of ordinary people like us? What if we are not swift horses either?\nImprisoned, I long for a road I cannot reach; small in person and weak in strength, I carry great wishes I cannot fulfill. Where is the road ahead? The road ahead is boundless and unclear!\nRegret cannot mend the past; the future can still be pursued. The mistake has not taken me too far; I know now what is right and what was wrong.\nLamenting the past is useless. While there is still time to turn back from a wrong road, once you have found your own path, there is no need to hesitate—walk forward boldly.\nHello, World People burdened by life are everywhere, and so are people who have found a purpose. Their aims may look different, but I believe even Du Fu, a poet from a thousand years ago, held the great wish to “shelter all the poor scholars beneath one roof.” Anyone shaped by Marxism and Mao Zedong Thought in modern society can possess this sense of social responsibility and ultimate goal.\nThe road beyond the mighty pass was hard as iron; now we stride over it anew.\nMao Zedong advanced this process in China. Now it is our turn to take up the baton. Taking off the long gown changes nothing—that is defeatism, escapism, surrender. Shouldn’t our task be to let everyone wear a long gown?\nIf I have done all I could yet cannot reach the goal, I can have no regrets. Who could condemn me?\nFinally, with the thing I love—programming—I challenge this imperfect world:\n1 print(\u0026#34;Hello, World!\u0026#34;) References The moment the bullet hits the forehead is when education completes its loop, and we should accept the ordinary ","permalink":"https://www.333rd.net/en/posts/life/my-2023/","summary":"\u003ch1 id=\"my-2023\"\u003eMy 2023\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI started this blog on a whim, never expecting it to last more than a year. I dedicate this post to the year that has passed.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eI am ashamed to say that it has been a long time since I wrote anything literary. As a science-and-engineering person, I never accumulated much literary learning, and I returned every scrap of classical Chinese I memorized in high school to my teachers. I am now in the state of knowing that some classical line must describe my feelings perfectly, while not remembering what it is. Please bear with me.\u003c/em\u003e\u003c/p\u003e","title":"My 2023"},{"content":"Adding a Friend Links Page to Hugo Friend links are a great tool for expanding a blog\u0026rsquo;s reach. This article explains how to add a friend links page to a Hugo blog.\nSteps Inline HTML Implementation Configuring the friend links page link is mentioned in config.yml in Building a Personal Blog with Hugo (2), corresponding to the following configuration:\n1 2 3 4 - identifier: links name: 🤝Friend Links url: links weight: 60 To keep it simple and easy to modify, it can be implemented with inline HTML.\nEdit content/links.md and add the following content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 --- title: \u0026#34;🤝Friend Links\u0026#34; layout: links summary: links --- \u0026lt;style\u0026gt; .friend-links { display: flex; justify-content: space-between; /* Distribute elements evenly in a row */ flex-wrap: wrap; /* Allow elements to wrap */ } .friend-link { display: inline-block; margin: 5%; text-align: center; text-decoration: none; color: var(--content); max-width: 15%; } .friend-link img { width: 100%; height: auto; border-radius: 50%; } .friend-link .name { margin-top: 5px; } \u0026lt;/style\u0026gt; \u0026lt;div class=\u0026#34;friend-links\u0026#34;\u0026gt; \u0026lt;a class=\u0026#34;friend-link\u0026#34; href=\u0026#34;https://333rd.net/\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt; \u0026lt;img src=\u0026#34;https://333rd.net/img/logo.gif\u0026#34; alt=\u0026#34;Friend Link Logo\u0026#34; loading=\u0026#34;lazy\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;name\u0026#34;\u0026gt;3rd\u0026#39;s Blog\u0026lt;/div\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;!-- Add more friend link elements --\u0026gt; \u0026lt;/div\u0026gt; Then run hugo --gc to update your blog.\nShortcodes Implementation If you want to implement it with Shortcodes, you can refer to《Adding friend links to a Hugo blog and successfully exchanging friend links with LINUX DO》.\nCreate layouts/shortcodes/friendlink.html and add the following content:\n1 2 3 4 5 6 7 8 9 10 11 \u0026lt;article class=\u0026#34;friend-card\u0026#34;\u0026gt; \u0026lt;a class=\u0026#34;friend-link\u0026#34; href=\u0026#34;{{ .Get \u0026#34;url\u0026#34; }}\u0026#34; target=\u0026#34;_blank\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;friend-logo\u0026#34;\u0026gt; \u0026lt;img src=\u0026#34;{{ .Get \u0026#34;logo\u0026#34; }}\u0026#34; alt=\u0026#34;Friend Link Logo\u0026#34; loading=\u0026#34;lazy\u0026#34;\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;friend-info\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;friend-name\u0026#34;\u0026gt;{{ .Get \u0026#34;name\u0026#34; }}\u0026lt;/div\u0026gt; \u0026lt;div class=\u0026#34;friend-slogan\u0026#34;\u0026gt;{{ .Get \u0026#34;slogan\u0026#34; }}\u0026lt;/div\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;/article\u0026gt; Create assets/css/extended/links.css\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 .friend-container { display: flex; flex-wrap: wrap; justify-content: center; gap: 20px; margin: 20px 0; } .friend-card { flex: 0 1 200px; border: 1px solid #ccc; border-radius: 8px; overflow: hidden; transition: transform 0.3s ease, box-shadow 0.3s ease; box-shadow: 0 2px 5px rgba(0,0,0,0.1); min-height: 250px; /* Ensure consistent card height */ } .friend-card:hover { transform: translateY(-5px); box-shadow: 0 4px 10px rgba(0,0,0,0.2); } .friend-link { display: block; text-decoration: none; color: var(--content); padding: 15px; text-align: center; } .friend-logo { width: 100%; height: 150px; display: flex; align-items: center; justify-content: center; background-color: #f7f7f7; } .friend-logo img { max-width: 100%; max-height: 100%; object-fit: contain; transition: opacity 0.3s ease; } .friend-logo:hover img { opacity: 0.8; } .friend-info { padding: 10px; text-align: center; min-height: 100px; /* Ensure enough space for the slogan */ } .friend-name { font-size: 18px; font-weight: bold; margin: 5px 0; } .friend-slogan { font-size: 14px; color: #666; margin: 0; } @media (max-width: 768px) { .friend-card { flex: 0 1 150px; } .friend-logo { height: 120px; } .friend-name { font-size: 16px; } .friend-slogan { font-size: 12px; } } Create static/friendlogo/ — friend link logos can be placed here.\nFinally, in content/links.md, use the shortcode as follows to add friend links:\n1 2 3 4 5 6 7 8 9 10 11 12 --- title: \u0026#34;🤝Friend Links\u0026#34; layout: links summary: links --- \u0026lt;!--Friend links need to be used inside this tag to match the CSS styles--\u0026gt; \u0026lt;div class=\u0026#34;friend-container\u0026#34;\u0026gt; {{\u0026lt; friendlink name=\u0026#34;3rd\u0026#39;s Blog\u0026#34; url=\u0026#34;https://333rd.net/\u0026#34; logo=\u0026#34;/friendlogo/3rd.logo.png\u0026#34; slogan=\u0026#34;Slogan placeholder; may be used in the future.\u0026#34; \u0026gt;}} \u0026lt;/div\u0026gt; References Adding friend links to a Hugo blog and successfully exchanging friend links with LINUX DO ","permalink":"https://www.333rd.net/en/posts/tech/hugo-friend-links-page/","summary":"\u003ch1 id=\"adding-a-friend-links-page-to-hugo\"\u003eAdding a Friend Links Page to Hugo\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eFriend links are a great tool for expanding a blog\u0026rsquo;s reach. This article explains how to add a friend links page to a Hugo blog.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"inline-html-implementation\"\u003eInline HTML Implementation\u003c/h3\u003e\n\u003cp\u003eConfiguring the friend links page link is mentioned in \u003ccode\u003econfig.yml\u003c/code\u003e in \u003ca href=\"/en/posts/tech/deploying-a-hugo-blog-part-2/#1-edit-configyml\" title=\"Building a Personal Blog with Hugo (2)\"\u003eBuilding a Personal Blog with Hugo (2)\u003c/a\u003e, corresponding to the following configuration:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-yml\" data-lang=\"yml\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e          \u003c/span\u003e- \u003cspan class=\"nt\"\u003eidentifier\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003elinks\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"nt\"\u003ename\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003e🤝Friend Links\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"nt\"\u003eurl\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"l\"\u003elinks\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"w\"\u003e            \u003c/span\u003e\u003cspan class=\"nt\"\u003eweight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"m\"\u003e60\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eTo keep it simple and easy to modify, it can be implemented with inline HTML.\u003c/p\u003e","title":"Adding a Friend Links Page to Hugo"},{"content":"Responsive Image Sizing in Hugo When you insert images in Hugo, their sizes are not adapted automatically, and inconsistent image sizes can hurt readability. This article explains how to configure responsive image sizing.\nSteps Edit assets/css/extended/blank.css; if the file does not exist, run the following command to copy one in:\n1 cp themes/PaperMod/assets/css/extended/blank.css assets/css/extended/blank.css Add the following css code:\n1 2 3 4 5 6 7 8 9 10 /* 当用户点击文章内容中的图片时，图片会被放大 */ .post-content img[loading=\u0026#34;lazy\u0026#34;]:active { transform: scale(1.382); } /* 使文章中的图片在页面上显示时都具有相同的宽度 */ .post-content img[loading=\u0026#34;lazy\u0026#34;] { width: 100%; height: auto; } Run hugo --gc to update the blog.\n","permalink":"https://www.333rd.net/en/posts/tech/hugo-responsive-image-sizing/","summary":"\u003ch1 id=\"responsive-image-sizing-in-hugo\"\u003eResponsive Image Sizing in Hugo\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eWhen you insert images in Hugo, their sizes are not adapted automatically, and inconsistent image sizes can hurt readability. This article explains how to configure responsive image sizing.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eEdit \u003ccode\u003eassets/css/extended/blank.css\u003c/code\u003e; if the file does not exist, run the following command to copy one in:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecp themes/PaperMod/assets/css/extended/blank.css assets/css/extended/blank.css \n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eAdd the following css code:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-css\" data-lang=\"css\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e/* 当用户点击文章内容中的图片时，图片会被放大 */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003epost-content\u003c/span\u003e \u003cspan class=\"nt\"\u003eimg\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"nt\"\u003eloading\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\u003cspan class=\"nd\"\u003eactive\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003etransform\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"nb\"\u003escale\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"mf\"\u003e1.382\u003c/span\u003e\u003cspan class=\"p\"\u003e);\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"c\"\u003e/* 使文章中的图片在页面上显示时都具有相同的宽度 */\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e.\u003c/span\u003e\u003cspan class=\"nc\"\u003epost-content\u003c/span\u003e \u003cspan class=\"nt\"\u003eimg\u003c/span\u003e\u003cspan class=\"o\"\u003e[\u003c/span\u003e\u003cspan class=\"nt\"\u003eloading\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"s2\"\u003e\u0026#34;lazy\u0026#34;\u003c/span\u003e\u003cspan class=\"o\"\u003e]\u003c/span\u003e \u003cspan class=\"p\"\u003e{\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003ewidth\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"mi\"\u003e100\u003c/span\u003e\u003cspan class=\"kt\"\u003e%\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eheight\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e \u003cspan class=\"kc\"\u003eauto\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"p\"\u003e}\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eRun \u003ccode\u003ehugo --gc\u003c/code\u003e to update the blog.\u003c/p\u003e","title":"Responsive Image Sizing in Hugo"},{"content":"Hugo Code Block Folding Strictly speaking, it is not folding, it just limits the maximum height of code blocks, though the effect is quite good.\nSteps All of the following operations are performed in the root directory of the blog site.\nControl the layout with css First add a code.css to assets/css/extended/code.css, which limits the maximum height of code blocks via css, and can be adjusted according to your own blog layout.\n1 nano assets/css/extended/code.css Content:\n1 2 3 4 .post-content pre code { max-height: 52em; /* 设置代码块的最大高度为父元素字体大小的xx倍 */ overflow: auto; /* 当代码块内容超出最大高度时，显示滚动条 */ } Implement synchronized scrolling with js However, this introduces a problem: when scrolling through a code block, the code scrolls but the line numbers do not scroll in sync. You need to add js code to make them scroll together.\n1 2 cp themes/PaperMod/layouts/partials/extend_footer.html layouts/partials/ nano layouts/partials/extend_footer.html Content:\nLine number positioning stopped working after some update. I don\u0026rsquo;t plan to maintain it; use the collapse shortcode instead. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 {{- /* Footer custom content area start */ -}} {{- /* Insert any custom code web-analytics, resources, etc. here */ -}} {{- /* Footer custom content area end */ -}} {{- if (and (eq .Kind \u0026#34;page\u0026#34;) (ne .Layout \u0026#34;archives\u0026#34;) (ne .Layout \u0026#34;search\u0026#34;) (.Param \u0026#34;ShowCodeCopyButtons\u0026#34;)) }} \u0026lt;script\u0026gt; // 实现代码块和行号同步滚动。 // 在页面加载完毕后匹配出代码块和行号对应的容器并绑定滚动事件 window.addEventListener(\u0026#39;load\u0026#39;, function () { // 获取代码块容器和行号容器的元素 var codeContainers = document.querySelectorAll(\u0026#39;.highlight code[class^=\u0026#34;language-\u0026#34;]\u0026#39;); var lineNumbersContainers = document.querySelectorAll(\u0026#39;.highlight code[class^=\u0026#34;hljs \u0026#34;]\u0026#39;); // 添加滚动事件监听器 var isScrollSyncing = false; // 标志位，指示是否正在进行滚动同步操作 codeContainers.forEach(function (container, index) { container.addEventListener(\u0026#39;scroll\u0026#39;, function () { if (!isScrollSyncing) { isScrollSyncing = true; // 将行号容器的滚动位置设置为相同的值 lineNumbersContainers[index].scrollTop = container.scrollTop; isScrollSyncing = false; } }); }); lineNumbersContainers.forEach(function (container, index) { container.addEventListener(\u0026#39;scroll\u0026#39;, function () { if (!isScrollSyncing) { isScrollSyncing = true; // 将代码块容器的滚动位置设置为相同的值 codeContainers[index].scrollTop = container.scrollTop; isScrollSyncing = false; } }); }); }); \u0026lt;/script\u0026gt; {{- end }} Additional Notes Thanks to the extensibility of Hugo and the extension interface provided by the PaperMod theme, rich effects can be achieved quite easily.\nI also reviewed my frontend coding style and was forced to debug the issue of scrolling not staying in sync. That is the nice thing about frontend development: what you see is what you get. With a browser you don\u0026rsquo;t even need to set up an environment, haha.\nFinally, thanks to Sulv\u0026rsquo;s blog for the idea, see Git for Sulv\u0026rsquo;s Blog.\nReferences Git for Sulv\u0026rsquo;s Blog ","permalink":"https://www.333rd.net/en/posts/tech/hugo-code-block-folding/","summary":"\u003ch1 id=\"hugo-code-block-folding\"\u003eHugo Code Block Folding\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eStrictly speaking, it is not folding, it just limits the maximum height of code blocks, though the effect is quite good.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eAll of the following operations are performed in the root directory of the blog site.\u003c/p\u003e\n\u003ch3 id=\"control-the-layout-with-css\"\u003eControl the layout with \u003ccode\u003ecss\u003c/code\u003e\u003c/h3\u003e\n\u003cp\u003eFirst add a \u003ccode\u003ecode.css\u003c/code\u003e to \u003ccode\u003eassets/css/extended/code.css\u003c/code\u003e, which limits the maximum height of code blocks via \u003ccode\u003ecss\u003c/code\u003e, and can be adjusted according to your own blog layout.\u003c/p\u003e","title":"Hugo Code Block Folding"},{"content":"Google Play Always Shows Pending Install Items It started appearing one day after I bought the phone, and no matter what I did I could not eliminate the prompt; this finally settles it.\nSteps Almost every new phone has a Google Play services for AR. Back then I did not know what it was for, so I uninstalled it, and ever since there has been a prompt:\n1 Google Play - 管理应用和设备 - 上次更新时间：X 分钟前和 1 个待安装项目 Finally, on V2EX I saw a post Seeking help: when updating apps on Google Play, there are always pending install items with the same situation.\nInstalling Google Play services for AR back fixed it. Speechless.\nReferences Seeking help: when updating apps on Google Play, there are always pending install items ","permalink":"https://www.333rd.net/en/posts/tech/google-play-pending-install-items/","summary":"\u003ch1 id=\"google-play-always-shows-pending-install-items\"\u003eGoogle Play Always Shows Pending Install Items\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eIt started appearing one day after I bought the phone, and no matter what I did I could not eliminate the prompt; this finally settles it.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eAlmost every new phone has a \u003cstrong\u003eGoogle Play services for AR\u003c/strong\u003e. Back then I did not know what it was for, so I uninstalled it, and ever since there has been a prompt:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003eGoogle Play - 管理应用和设备 - 上次更新时间：X 分钟前和 1 个待安装项目\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eFinally, on V2EX I saw a post \u003ca href=\"https://www.v2ex.com/t/984151\" title=\"Seeking help: when updating apps on Google Play, there are always pending install items\"\u003eSeeking help: when updating apps on Google Play, there are always pending install items\u003c/a\u003e with the same situation.\u003c/p\u003e","title":"Google Play Always Shows Pending Install Items"},{"content":"ESP32 Development with CLion Since I wasn\u0026rsquo;t used to VSCode\u0026rsquo;s half-baked C++ IDE features, I turned to CLion. But CLion in turn faces the problem of having fewer plugins than VSCode, so I ran into issues when developing for the ESP32. Finally, let me stress: if you primarily use VSCode for development, just use the official plugin provided by ESP32 — it\u0026rsquo;s simple and convenient, no command line required.\nSteps Actually, there is no need to bother with environment variables or cross-compilation toolchains. I\u0026rsquo;ve tried them, and they\u0026rsquo;re useless — they only add unnecessary trouble.\nJust open the ESP32 project directly with the default toolchain, as long as CMake is provided. Compile once with idf.py build, then reload the CMake project in CLion, and CLion will seemingly recognize the build artifacts on its own and configure automatically.\nThen you can happily edit code with intelligent suggestions.\nIf you occasionally run into code recognition issues, just repeat the steps above.\nWindows is even simpler: after installing ESP-IDF, two command-line shortcuts are placed on the desktop, ESP-IDF \u0026lt;version\u0026gt; CMD and ESP-IDF \u0026lt;version\u0026gt; PowerShell. Pick either one and type clion, and the CLion that opens will automatically use ESP-IDF\u0026rsquo;s variables.\nIssues The downside is that there is no official support after all. Based on my testing, only the build and flash commands execute successfully; other commands like monitor are either ineffective or malfunction.\nIt\u0026rsquo;s better to type commands by hand in the command line and use CLion\u0026rsquo;s code-editing assistance features for development. If you find that troublesome, you can also manually add a Run/Debug Configuration, except that it cannot be used as a template across projects — it has to be reconfigured for every project.\nIf that\u0026rsquo;s too much trouble, you can also set up aliases for the commands:\n1 2 3 4 5 6 7 8 9 10 11 alias get-idf=\u0026#34;. $HOME/esp/esp-idf/export.sh\u0026#34; alias idf-set-target=\u0026#34;idf.py set-target\u0026#34; alias idf-menuconfig=\u0026#34;idf.py menuconfig\u0026#34; alias idf-build=\u0026#34;idf.py build\u0026#34; alias idf-flash=\u0026#34;idf.py flash\u0026#34; alias idf-monitor=\u0026#34;idf.py monitor\u0026#34; alias idf-reconfigure=\u0026#34;idf.py reconfigure\u0026#34; alias idf-clean=\u0026#34;idf.py clean\u0026#34; alias idf-fullclean=\u0026#34;idf.py fullclean\u0026#34; alias idf-erase-flash=\u0026#34;idf.py erase-flash\u0026#34; In addition, on Linux systems you may also run into the issue of no permission for the serial port. Here\u0026rsquo;s how to solve it:\nCheck the permissions of the serial port ttyUSB0:\n1 ls -l /dev/ttyUSB0 You can see that the owning user group is dialout, and only the root user has the permission to operate it.\nCheck the current user:\n1 whoami Then add the current user to the dialout group so that they have permission to operate the serial port ttyUSB0:\n1 sudo usermod -aG dialout jove Done, just reboot the system.\nReferences CLion and ESP-IDF Integration Setting Permanent Serial Port Permissions on Linux ","permalink":"https://www.333rd.net/en/posts/tech/clion-esp32-development/","summary":"\u003ch1 id=\"esp32-development-with-clion\"\u003eESP32 Development with CLion\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eSince I wasn\u0026rsquo;t used to VSCode\u0026rsquo;s half-baked C++ IDE features, I turned to CLion. But CLion in turn faces the problem of having fewer plugins than VSCode, so I ran into issues when developing for the ESP32. Finally, let me stress: if you primarily use VSCode for development, just use the official plugin provided by ESP32 — it\u0026rsquo;s simple and convenient, no command line required.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eActually, there is no need to bother with environment variables or cross-compilation toolchains. I\u0026rsquo;ve tried them, and they\u0026rsquo;re useless — they only add unnecessary trouble.\u003c/p\u003e","title":"ESP32 Development with CLion"},{"content":"CMake Configuration for Dynamically Linking spdlog Compiling spdlog in header-only form greatly increases the binary size, and since I have multiple programs that all use it, I decided to use dynamic linking to save resources. However, after searching around the web I found nothing useful, and finally worked it out through a discussion with ChatGPT. Below is the way to configure CMake to dynamically link spdlog.\nSteps 1. Write spdlog.cmake First, write a CMake module configuration file spdlog.cmake and put it under ${PROJECT_SOURCE_DIR}/cmake. The file content is as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 # Set spdlog to be built as a shared library # build shared option option(SPDLOG_BUILD_SHARED \u0026#34;Build shared library\u0026#34; ON) # precompiled headers option option(SPDLOG_ENABLE_PCH \u0026#34;Build static or shared library using precompiled header to speed up compilation time\u0026#34; ON) # build position independent code option(SPDLOG_BUILD_PIC \u0026#34;Build position independent code (-fPIC)\u0026#34; ON) # Add the spdlog library add_subdirectory(${PROJECT_SOURCE_DIR}/lib/spdlog) # Set the output path of the spdlog library\u0026#39;s lib set_target_properties(spdlog PROPERTIES LIBRARY_OUTPUT_DIRECTORY ${PROJECT_SOURCE_DIR}/output/lib) 2. Include the Module Configuration Add a line to the CMakeLists.txt at the project root:\n1 include(${PROJECT_SOURCE_DIR}/cmake/spdlog.cmake) # Load the spdlog module 3. Dynamically Link spdlog Add the following to the sub CMakeLists.txt where spdlog is used:\n1 2 3 4 set(LIB_SPDLOG_ROOT ${PROJECT_SOURCE_DIR}/path/to/spdlog) # Set the root path of the spdlog library include_directories(${LIB_SPDLOG_ROOT}/include) # Set the include file path of the spdlog library link_directories(${PROJECT_SOURCE_DIR}/output/lib) # Set the library linking path target_link_libraries(\u0026lt;target\u0026gt; spdlog) ","permalink":"https://www.333rd.net/en/posts/tech/spdlog-shared-library-cmake/","summary":"\u003ch1 id=\"cmake-configuration-for-dynamically-linking-spdlog\"\u003eCMake Configuration for Dynamically Linking spdlog\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eCompiling spdlog in header-only form greatly increases the binary size, and since I have multiple programs that all use it, I decided to use dynamic linking to save resources. However, after searching around the web I found nothing useful, and finally worked it out through a discussion with ChatGPT. Below is the way to configure CMake to dynamically link spdlog.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-write-spdlogcmake\"\u003e1. Write \u003ccode\u003espdlog.cmake\u003c/code\u003e\u003c/h3\u003e\n\u003cp\u003eFirst, write a CMake module configuration file \u003ccode\u003espdlog.cmake\u003c/code\u003e and put it under \u003ccode\u003e${PROJECT_SOURCE_DIR}/cmake\u003c/code\u003e. The file content is as follows:\u003c/p\u003e","title":"CMake Configuration for Dynamically Linking spdlog"},{"content":"When to Use decltype in C++ decltype is usually meant to be wrapped in generic (template) library code and used only when strictly necessary. Outside of that, non-abusive auto is the day-to-day syntactic sugar.\n1. What Is decltype The name decltype is formed from \u0026ldquo;declare\u0026rdquo; and \u0026ldquo;type\u0026rdquo;, indicating that it is used to declare a type.\nFor details and usage, see the [decltype specifier] reference. In short, it extracts the type of a given expression and lets you use that data type to declare a new variable.\n2. Differences from auto Both can appear as type keywords when declaring variables, but the key differences are:\nVariables declared with auto must be initialized, because type deduction happens at compile time based on the initializer expression — the expression is also evaluated at runtime. decltype, on the other hand, already deduces the type from the expression without requiring initialization, and does not evaluate the expression\u0026rsquo;s value. Modifiers (such as const and references) are handled differently. However, due to C++\u0026rsquo;s ongoing patchwork of features, there are some exceptions that add complexity, which we won\u0026rsquo;t go into here. These are only superficial differences. decltype is commonly found in template programming. For example, xinnjie\u0026rsquo;s implementation for compile-time detection of whether a class has a foo() method, as discussed in \u0026ldquo;Why does C++11 have decltype when it already has auto?\u0026rdquo;:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 //c++14 #include \u0026lt;experimental/type_traits\u0026gt; // for srd::experimental::is_de_detected #include \u0026lt;iostream\u0026gt; #include \u0026lt;type_traits\u0026gt; template\u0026lt;typename T\u0026gt; using has_foo_func = decltype(std::declval\u0026lt;T\u0026gt;().foo()); struct HasFoo { void foo() {} }; struct NotHasFoo { }; int main() { std::cout \u0026lt;\u0026lt; \u0026#34;has foo\u0026#34; \u0026lt;\u0026lt; std::experimental::is_detected\u0026lt;has_foo_func, HasFoo\u0026gt;::value \u0026lt;\u0026lt; std::endl; // true std::cout \u0026lt;\u0026lt; \u0026#34;has foo\u0026#34; \u0026lt;\u0026lt; std::experimental::is_detected\u0026lt;has_foo_func, NotHasFoo\u0026gt;::value \u0026lt;\u0026lt; std::endl; // false } B. Stroustrup presents more examples in the C++11 FAQ.\nIf you feel that auto and decltype overlap in functionality — I think they do. As stated in \u0026ldquo;A Brief Overview of auto and decltype in C++11\u0026rdquo;:\ndecltype was introduced to address some issues with auto. Anywhere you can use auto, you can use decltype. However, auto is more concise than decltype, so prefer auto when you can and fall back to decltype when you must.\nIn certain situations, decltype is the better choice. For instance, when you want y to always have whatever the declared type of x is.\nWhen you want y to always have whatever the declared type of x is.\n3. Summary You shouldn\u0026rsquo;t be seeing or using decltype in \u0026ldquo;day-to-day\u0026rdquo; programming. It is most useful in generic (templated) library code, where the expression in question is not known and depends on a paramater. (By contrast, auto may be used generously all over the place.) In short, if you\u0026rsquo;re new to programming, you probably won\u0026rsquo;t need to use decltype for some time.\nAs noted in What is decltype and how is it used?: you shouldn\u0026rsquo;t be seeing or using decltype in \u0026ldquo;day-to-day\u0026rdquo; programming. It is most useful in generic (templated) library code, where the expression in question is unknown and depends on a parameter. (By contrast, auto may be used generously all over the place.) In short, if you\u0026rsquo;re new to programming, you probably won\u0026rsquo;t need to use decltype for some time.\nReferences decltype specifier Why does C++11 have decltype when it already has auto? (Zhihu) A Brief Overview of auto and decltype in C++11 (Juejin) C++11 FAQ When should I use decltype(x) instead of auto to declare the type of a variable? (Stack Overflow) What is decltype and how is it used? (Stack Overflow) ","permalink":"https://www.333rd.net/en/posts/read/when-to-use-cpp-decltype/","summary":"\u003ch1 id=\"when-to-use-decltype-in-c\"\u003eWhen to Use decltype in C++\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003e\u003ccode\u003edecltype\u003c/code\u003e is usually meant to be wrapped in generic (template) library code and used only when strictly necessary. Outside of that, non-abusive \u003ccode\u003eauto\u003c/code\u003e is the day-to-day syntactic sugar.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"1-what-is-decltype\"\u003e1. What Is \u003ccode\u003edecltype\u003c/code\u003e\u003c/h2\u003e\n\u003cp\u003eThe name \u003ccode\u003edecltype\u003c/code\u003e is formed from \u0026ldquo;\u003cstrong\u003edeclare\u003c/strong\u003e\u0026rdquo; and \u0026ldquo;\u003cstrong\u003etype\u003c/strong\u003e\u0026rdquo;, indicating that it is used to declare a type.\u003c/p\u003e\n\u003cp\u003eFor details and usage, see the [\u003ccode\u003edecltype\u003c/code\u003e specifier] reference. In short, it extracts the type of a given expression and lets you use that data type to declare a new variable.\u003c/p\u003e","title":"When to Use decltype in C++"},{"content":"Embedding ICO Icons in Tkinter Although Tkinter is fairly basic, it is still quite convenient when developing some graphical gadgets with Python. Since the native icon is rather plain, a custom icon is generally chosen instead, but then a single-file executable program also has to be shipped with an ICO file to display the icon, which is undoubtedly cumbersome. This article describes how to embed the icon into the executable file.\nSteps Jessie Wilson\u0026rsquo;s question \u0026ldquo;Embed icon in python script\u0026rdquo; on StackOverflow provides a solution:\n1 2 3 4 5 6 7 8 import Tkinter as tk icon = \u0026#34;\u0026#34;\u0026#34; REPLACE THIS WITH YOUR BASE64 VERSION OF THE ICON \u0026#34;\u0026#34;\u0026#34; root = tk.Tk() root.iconphoto(True, tk.PhotoImage(data=icon_png)) The icon is required to be a .png file. The code that implements the image-to-Base64 encoding conversion is as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 \u0026#34;\u0026#34;\u0026#34;ico icon\u0026#34;\u0026#34;\u0026#34; import base64 def image_to_base64(image_path): with open(image_path, \u0026#34;rb\u0026#34;) as image_file: encoded_string = base64.b64encode(image_file.read()).decode(\u0026#34;utf-8\u0026#34;) return encoded_string icon_png = \u0026#34;\u0026#34;\u0026#34; base64 code \u0026#34;\u0026#34;\u0026#34; if __name__ == \u0026#34;__main__\u0026#34;: print(image_to_base64(\u0026#34;icon.png\u0026#34;)) References Embed icon in python script simulated-mouse ","permalink":"https://www.333rd.net/en/posts/tech/tkinter-embed-ico-icon/","summary":"\u003ch1 id=\"embedding-ico-icons-in-tkinter\"\u003eEmbedding ICO Icons in Tkinter\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eAlthough Tkinter is fairly basic, it is still quite convenient when developing some graphical gadgets with Python. Since the native icon is rather plain, a custom icon is generally chosen instead, but then a single-file executable program also has to be shipped with an ICO file to display the icon, which is undoubtedly cumbersome. This article describes how to embed the icon into the executable file.\u003c/em\u003e\u003c/p\u003e","title":"Embedding ICO Icons in Tkinter"},{"content":"Using Mouse Side Buttons to Navigate Forward and Backward in CLion I\u0026rsquo;m not sure whether the official keymap supports navigating forward and backward with mouse side buttons while browsing code. The VSCode shortcut plugin does not support this natively, but it can be added manually.\nSteps The Navigate menu in the menu bar provides the forward and backward actions. Go to Settings -\u0026gt; Keymap -\u0026gt; Main Menu -\u0026gt; Navigate, where you\u0026rsquo;ll find the Forward and Back actions. Right-click and select Add Mouse Shortcut, then set it to a mouse side button.\nReferences Shortcuts for going back and forward in CLion ","permalink":"https://www.333rd.net/en/posts/tech/clion-mouse-side-buttons-navigation/","summary":"\u003ch1 id=\"using-mouse-side-buttons-to-navigate-forward-and-backward-in-clion\"\u003eUsing Mouse Side Buttons to Navigate Forward and Backward in CLion\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI\u0026rsquo;m not sure whether the official keymap supports navigating forward and backward with mouse side buttons while browsing code. The VSCode shortcut plugin does not support this natively, but it can be added manually.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eThe \u003cstrong\u003eNavigate\u003c/strong\u003e menu in the menu bar provides the forward and backward actions. Go to \u003cstrong\u003eSettings\u003c/strong\u003e -\u0026gt; \u003cstrong\u003eKeymap\u003c/strong\u003e -\u0026gt; \u003cstrong\u003eMain Menu\u003c/strong\u003e -\u0026gt; \u003cstrong\u003eNavigate\u003c/strong\u003e, where you\u0026rsquo;ll find the \u003cstrong\u003eForward\u003c/strong\u003e and \u003cstrong\u003eBack\u003c/strong\u003e actions. Right-click and select \u003cstrong\u003eAdd Mouse Shortcut\u003c/strong\u003e, then set it to a mouse side button.\u003c/p\u003e","title":"Using Mouse Side Buttons to Navigate Forward and Backward in CLion"},{"content":"Adding Filesystem Access Permissions for Specific Paths to Flatpak Applications Flatpak uses a sandbox to ensure security, but as a result some folders and paths cannot be accessed inside applications. After consulting the documentation, I found that this problem can be solved by setting access permissions.\nSteps See the \u0026ldquo;Filesystem access\u0026rdquo; section of the official documentation. You can use the following option to add access permissions for a specific path:\n1 --filesystem=/path/path The documentation also provides the following options.\n--filesystem=host - Access the host\u0026rsquo;s regular files, not including the host operating system or system internals mentioned below --filesystem=home - Access the user\u0026rsquo;s home directory --filesystem=/path/path - Access a specific path --filesystem=xdg-download - Access a specific XDG folder References Filesystem access ","permalink":"https://www.333rd.net/en/posts/tech/flatpak-add-filesystem-access/","summary":"\u003ch1 id=\"adding-filesystem-access-permissions-for-specific-paths-to-flatpak-applications\"\u003eAdding Filesystem Access Permissions for Specific Paths to Flatpak Applications\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eFlatpak uses a sandbox to ensure security, but as a result some folders and paths cannot be accessed inside applications. After consulting the documentation, I found that this problem can be solved by setting access permissions.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eSee the \u0026ldquo;\u003ca href=\"https://docs.flatpak.org/zh_CN/latest/sandbox-permissions.html#filesystem-access\" title=\"Filesystem access\"\u003eFilesystem access\u003c/a\u003e\u0026rdquo; section of the official documentation. You can use the following option to add access permissions for a specific path:\u003c/p\u003e","title":"Adding Filesystem Access Permissions for Specific Paths to Flatpak Applications"},{"content":"Resolving the Issue of Restart Getting Stuck at A0 Self-Test while Shutdown Works Normally This had been bothering me for a very long time. Shutting down and then powering on works fine, but a direct restart gets stuck at the BIOS self-test, showing A0.\nAnalysis I found that the machine could restart normally without the mechanical hard drive installed, but the problem appeared once it was installed.\nMy suspicion was that the hard drive was partitioned into only a single partition like /dev/sdx, without partitioned entries such as /dev/sdx1. As a result, the BIOS self-test would misidentify the boot drive, preventing a normal boot.\nAll of the above are just guesses, but the problem must be caused by the hard drives, and most likely it failed to boot from the system drive, operating on the wrong hard drive instead.\nHowever, although I tried keeping only the system drive\u0026rsquo;s boot entry, the problem still was not solved.\nSolution Finally, while randomly fiddling with the BIOS settings, I suddenly noticed that the description of one setting seemed to be able to get out of this predicament:\nBIOS Settings As shown in the figure, after enabling Fast Boot, these sub-options appear, indicating how these devices should be initialized during the fast boot process.\nAmong them, the initialization of SATA devices can be controlled. Since my system drive uses an NVMe interface, I simply set this option to \u0026ldquo;Only initialize the SATA device last used for Boot\u0026rdquo;.\nI could not find a disabled option, but after setting it this way it made no difference from being disabled, and the restart worked normally again.\n","permalink":"https://www.333rd.net/en/posts/tech/bios-a0-reboot-self-test-fix/","summary":"\u003ch1 id=\"resolving-the-issue-of-restart-getting-stuck-at-a0-self-test-while-shutdown-works-normally\"\u003eResolving the Issue of Restart Getting Stuck at A0 Self-Test while Shutdown Works Normally\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThis had been bothering me for a very long time. Shutting down and then powering on works fine, but a direct restart gets stuck at the BIOS self-test, showing A0.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"analysis\"\u003eAnalysis\u003c/h2\u003e\n\u003cp\u003eI found that the machine could restart normally without the mechanical hard drive installed, but the problem appeared once it was installed.\u003c/p\u003e\n\u003cp\u003eMy suspicion was that the hard drive was partitioned into only a single partition like \u003ccode\u003e/dev/sdx\u003c/code\u003e, without partitioned entries such as \u003ccode\u003e/dev/sdx1\u003c/code\u003e. As a result, the BIOS self-test would misidentify the boot drive, preventing a normal boot.\u003c/p\u003e","title":"Resolving the Issue of Restart Getting Stuck at A0 Self-Test while Shutdown Works Normally"},{"content":"Restoring WindTerm\u0026rsquo;s Disappeared Menu Bar WindTerm\u0026rsquo;s menu bar mysteriously disappeared. Is this a collapse of morality, or a distortion of human nature?\nSteps First press Alt+W, then press Alt+M.\nAlso note the program\u0026rsquo;s permissions. If the permissions do not match the current user, the menu bar also seems to disappear.\nReferences windTerm menu bar disappeared and how to bring it back ","permalink":"https://www.333rd.net/en/posts/tech/restore-windterm-missing-menu-bar/","summary":"\u003ch1 id=\"restoring-windterms-disappeared-menu-bar\"\u003eRestoring WindTerm\u0026rsquo;s Disappeared Menu Bar\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eWindTerm\u0026rsquo;s menu bar mysteriously disappeared. Is this a collapse of morality, or a distortion of human nature?\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eFirst press \u003ccode\u003eAlt+W\u003c/code\u003e, then press \u003ccode\u003eAlt+M\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eAlso note the program\u0026rsquo;s permissions. If the permissions do not match the current user, the menu bar also seems to disappear.\u003c/p\u003e\n\u003ch2 id=\"references\"\u003eReferences\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://blog.csdn.net/weixin_43648241/article/details/125074919\" title=\"windTerm menu bar disappeared and how to bring it back\"\u003ewindTerm menu bar disappeared and how to bring it back\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"Restoring WindTerm's Disappeared Menu Bar"},{"content":"Fixing the Issue of VMware Shared Folder Failing After Reboot Although the mechanism of the failure is unknown, from the system logs there are no errors. Maybe it was not executed? But as long as it can be solved, that is fine.\nSteps Just add a boot-time scheduled task:\n1 crontab -e Add one line:\n1 @reboot sudo /usr/bin/vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other -o uid=1000 -o gid=1000 -o umask=022 References Resolving the Issue of VMware Shared File Failure Between Linux and Windows Solution to Shared Folders Not Showing Under /mnt/hgfs/ in Ubuntu 16.04 ","permalink":"https://www.333rd.net/en/posts/tech/vmware-shared-folder-reboot-fix/","summary":"\u003ch1 id=\"fixing-the-issue-of-vmware-shared-folder-failing-after-reboot\"\u003eFixing the Issue of VMware Shared Folder Failing After Reboot\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eAlthough the mechanism of the failure is unknown, from the system logs there are no errors. Maybe it was not executed? But as long as it can be solved, that is fine.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eJust add a boot-time scheduled task:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-shell\" data-lang=\"shell\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecrontab -e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eAdd one line:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-text\" data-lang=\"text\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e@reboot sudo /usr/bin/vmhgfs-fuse .host:/ /mnt/hgfs -o allow_other -o uid=1000 -o gid=1000 -o umask=022\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003ch2 id=\"references\"\u003eReferences\u003c/h2\u003e\n\u003cul\u003e\n\u003cli\u003e\u003ca href=\"https://blog.csdn.net/rankun1/article/details/78106665\" title=\"Resolving the Issue of VMware Shared File Failure Between Linux and Windows\"\u003eResolving the Issue of VMware Shared File Failure Between Linux and Windows\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e\u003ca href=\"https://www.jianshu.com/p/d87a0c9535e2\" title=\"Solution to Shared Folders Not Showing Under /mnt/hgfs/ in Ubuntu 16.04\"\u003eSolution to Shared Folders Not Showing Under /mnt/hgfs/ in Ubuntu 16.04\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e","title":"Fixing the Issue of VMware Shared Folder Failing After Reboot"},{"content":"update-notifier reports an AttributeError E: Unknown error: \u0026ldquo;\u0026lt;class \u0026lsquo;AttributeError\u0026rsquo;\u0026gt;\u0026rdquo; (module \u0026lsquo;apt_pkg\u0026rsquo; has no attribute \u0026lsquo;Error\u0026rsquo;).\nAnalysis First I found that the service has some scripts under /usr/lib/update-notifier/, and apt-check -\u0026gt; apt_check.py among them is the culprit.\nIn my system, the erroring code looks like this:\n1 2 3 4 5 6 7 8 9 10 11 import apt_pkg ... def get_apt_pkg_esm_cache(): ... try: esm_cache = apt_pkg.Cache(progress=None) except apt_pkg.Error: esm_cache = None The apt_pkg.Error attribute does not exist, but according to python-apt Navigation, it should, and:\nIt inherits from SystemError, so make sure to catch this class first.\nAnd apt_pkg is a dynamic library:\n1 2 3 4 import apt_pkg apt_pkg.__file__ # \u0026#39;/usr/lib/python2.7/dist-packages/apt_pkg.x86_64-linux-gnu.so\u0026#39; Presumably it is a version problem, so let me reinstall it:\n1 2 3 # 按照系统使用的python版本 sudo apt install --reinstall python-apt sudo apt install --reinstall python3-apt No effect, so I edited the file directly:\n1 2 # except apt_pkg.Error: except SystemError: Done.\nReferences python-apt Navigation ","permalink":"https://www.333rd.net/en/posts/tech/handling-ubuntu-update-notifier-error/","summary":"\u003ch1 id=\"update-notifier-reports-an-attributeerror\"\u003eupdate-notifier reports an AttributeError\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eE: Unknown error: \u0026ldquo;\u0026lt;class \u0026lsquo;AttributeError\u0026rsquo;\u0026gt;\u0026rdquo; (module \u0026lsquo;apt_pkg\u0026rsquo; has no attribute \u0026lsquo;Error\u0026rsquo;).\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"analysis\"\u003eAnalysis\u003c/h2\u003e\n\u003cp\u003eFirst I found that the service has some scripts under \u003ccode\u003e/usr/lib/update-notifier/\u003c/code\u003e, and \u003ccode\u003eapt-check -\u0026gt; apt_check.py\u003c/code\u003e among them is the culprit.\u003c/p\u003e\n\u003cp\u003eIn my system, the erroring code looks like this:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e 1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 6\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 7\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 8\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e 9\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e10\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e11\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"kn\"\u003eimport\u003c/span\u003e \u003cspan class=\"nn\"\u003eapt_pkg\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"o\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003edef\u003c/span\u003e \u003cspan class=\"nf\"\u003eget_apt_pkg_esm_cache\u003c/span\u003e\u003cspan class=\"p\"\u003e():\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e...\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003etry\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eesm_cache\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003eapt_pkg\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eCache\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003eprogress\u003c/span\u003e\u003cspan class=\"o\"\u003e=\u003c/span\u003e\u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\u003cspan class=\"p\"\u003e)\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"k\"\u003eexcept\u003c/span\u003e \u003cspan class=\"n\"\u003eapt_pkg\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003eError\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e        \u003cspan class=\"n\"\u003eesm_cache\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"kc\"\u003eNone\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003eThe \u003cstrong\u003e\u003ccode\u003eapt_pkg.Error\u003c/code\u003e\u003c/strong\u003e attribute does not exist, but according to \u003ca href=\"https://apt-team.pages.debian.net/python-apt/library/apt_pkg.html#apt_pkg.Error\" title=\"python-apt Navigation\"\u003epython-apt Navigation\u003c/a\u003e, it should, and:\u003c/p\u003e","title":"Handling an Ubuntu update-notifier Error"},{"content":"C++ Singleton Template Singleton implementations are mostly the same. A template makes the singleton pattern more convenient to use.\nAnalysis The singleton pattern must ensure that resource initialization is thread-safe, which has led to the following approaches (selected from \u0026ldquo;Template Implementation of C++ Singleton Pattern\u0026rdquo; and \u0026ldquo;Is the Singleton Pattern Simple? But Can You Really Get It Right?\u0026rdquo;):\n1. Direct Locking 1 2 3 4 5 6 7 8 // Thread-safe version, but the cost of locking is too high Singleton* Singleton::GetInstance() { Lock lock; // Pseudo-code: lock if (instance == nullptr) { instance = new Singleton(); } return instance; } Although this ensures initialization happens only once, the lock is acquired every time GetInstance is called, resulting in poor performance.\n2. Double-Checked Locking Since locking is unnecessary after initialization, we can add a check if (instance == nullptr) before locking. The resource is only locked and initialized if it hasn\u0026rsquo;t been initialized yet, and we check again before initialization to ensure that if multiple threads enter the first if simultaneously, only one thread will initialize the resource.\n1 2 3 4 5 6 7 8 9 10 11 // Double-checked locking, but unsafe due to memory read/write reordering Singleton* Singleton::GetInstance() { // First check if it has been initialized; if so, the lock is never used if(instance==nullptr){ Lock lock; // Pseudo-code if (instance == nullptr) { instance = new Singleton(); } } return instance; } Looks perfect, doesn\u0026rsquo;t it? But a program after compiler optimization may not work as expected. instance = new Singleton(); can be divided into three steps:\nAllocate memory needed for a Singleton object. Construct the Singleton object at the allocated memory location. Assign the address of the allocated memory to the pointer instance. Due to compiler optimizations, out-of-order memory reads and writes can occur, and only step 1 is guaranteed to execute first. If thread A executes steps 1, 3, 2 in that order, then after step 3, instance is no longer nullptr. Thread B will directly return instance;, but the object hasn\u0026rsquo;t been constructed yet. Once thread B uses this object, it will lead to a bug.\n3. Eager Initialization The two methods above allocate resources and initialize the object only on first use. This is also called lazy initialization — like the toad in Daming Lake, it hops only when poked. Instead, we can initialize before the program enters main, bypassing the thread-safety issue entirely.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 template\u0026lt;typename T\u0026gt; class EagerSingleton { private: static T* t_; public: static T\u0026amp; GetInstance() { return *t_; } EagerSingleton(T\u0026amp;\u0026amp;) = delete; EagerSingleton(const T\u0026amp;) = delete; void operator= (const T\u0026amp;) = delete; protected: EagerSingleton() = default; virtual ~EagerSingleton() = default; }; template\u0026lt;typename T\u0026gt; T* EagerSingleton\u0026lt;T\u0026gt;::t_ = new (std::nothrow) T; However, this pattern also has a problem: the object is initialized even if it is never used. If the object\u0026rsquo;s resource cost is high, this wastes resources.\n4. C++ 11 to the Rescue Scott Meyers proposed a singleton pattern using C++\u0026rsquo;s static keyword in Effective C++, Item 4: Make sure that objects are initialized before they\u0026rsquo;re used. This implementation is concise and efficient. Its characteristics are:\nThe instance object is initialized only when the program first reaches the GetInstance function. After C++ 11, variables qualified with static are guaranteed to be thread-safe. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 #ifndef SINGLETON_H #define SINGLETON_H /** * \\brief Singleton template * \\tparam T The type to be used as a singleton * * \\code{.cpp} * // Direct use (recommended) * Singleton\u0026lt;T\u0026gt;::GetInstance() * * // Inheritance derivation * class C : public Singleton\u0026lt;T\u0026gt; * \\endcode */ template\u0026lt;typename T, typename... Args\u0026gt; class Singleton { public: // Delete copy constructor, move constructor, copy assignment operator, and move assignment operator to prevent external copying, assignment, or moving of the object Singleton(const Singleton \u0026amp;) = delete; Singleton(Singleton \u0026amp;\u0026amp;) = delete; Singleton \u0026amp;operator=(const Singleton \u0026amp;) = delete; Singleton \u0026amp;operator=(Singleton \u0026amp;\u0026amp;) = delete; static T \u0026amp;GetInstance(Args \u0026amp;\u0026amp;... args) { static T instance(std::forward\u0026lt;Args\u0026gt;(args)...); return instance; } protected: Singleton() = default; virtual ~Singleton() = default; }; #endif // SINGLETON_H By deleting the singleton class\u0026rsquo;s copy constructor, move constructor, and operator=, we prevent the unique instance from being copied or moved. Not exposing the constructor and destructor ensures the singleton class cannot be instantiated through other means, while defining both as protected allows them to be inherited and used by subclasses.\n5. AI\u0026rsquo;s Wisdom The singleton template code in Section 4. C++ 11 to the Rescue has a problem — the singleton object exists in stack memory, which can be an unnecessary overhead if the class is large. To address this, I implemented the following template with AI assistance:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 #ifndef SINGLETON_H #define SINGLETON_H #include \u0026lt;memory\u0026gt; #include \u0026lt;mutex\u0026gt; /** * \\brief Singleton template * \\tparam T The type to be used as a singleton * * \\code{.cpp} * // Direct use (recommended) * Singleton\u0026lt;T\u0026gt;::GetInstance() * * // Inheritance derivation * class C : public Singleton\u0026lt;T\u0026gt; * \\endcode */ template\u0026lt;typename T, typename... Args\u0026gt; class Singleton { public: // Delete copy constructor, move constructor, copy assignment operator, and move assignment operator to prevent external copying, assignment, or moving of the object Singleton(const Singleton \u0026amp;) = delete; Singleton \u0026amp;operator=(const Singleton \u0026amp;) = delete; Singleton(Singleton \u0026amp;\u0026amp;) = delete; Singleton \u0026amp;operator=(Singleton \u0026amp;\u0026amp;) = delete; // Get instance function for the singleton template class, using perfect forwarding and std::call_once to create and return the singleton object static T *GetInstance(Args \u0026amp;\u0026amp;... args) { std::call_once(flag_, [\u0026amp;]() { instance_ = std::make_unique\u0026lt;T\u0026gt;(std::forward\u0026lt;Args\u0026gt;(args)...); }); return instance_.get(); } private: // Constructor and destructor of the singleton template class, privatized to prevent external creation or destruction of objects Singleton() = default; ~Singleton() = default; // Member variables of the singleton template class, including a smart pointer to the singleton object and a once_flag variable to ensure thread safety static std::unique_ptr\u0026lt;T\u0026gt; instance_; static std::once_flag flag_; }; // Initialize static member variables of the singleton template class template\u0026lt;typename T, typename... Args\u0026gt; std::unique_ptr\u0026lt;T\u0026gt; Singleton\u0026lt;T, Args...\u0026gt;::instance_ = nullptr; template\u0026lt;typename T, typename... Args\u0026gt; std::once_flag Singleton\u0026lt;T, Args...\u0026gt;::flag_; #endif // SINGLETON_H References Template Implementation of C++ Singleton Pattern Is the Singleton Pattern Simple? But Can You Really Get It Right? ","permalink":"https://www.333rd.net/en/posts/read/cpp-singleton-template/","summary":"\u003ch1 id=\"c-singleton-template\"\u003eC++ Singleton Template\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eSingleton implementations are mostly the same. A template makes the singleton pattern more convenient to use.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"analysis\"\u003eAnalysis\u003c/h2\u003e\n\u003cp\u003eThe singleton pattern must ensure that resource initialization is thread-safe, which has led to the following approaches (selected from \u0026ldquo;\u003ca href=\"https://zhuanlan.zhihu.com/p/232319083\" title=\"Template Implementation of C\u0026#43;\u0026#43; Singleton Pattern\"\u003eTemplate Implementation of C++ Singleton Pattern\u003c/a\u003e\u0026rdquo; and \u0026ldquo;\u003ca href=\"https://zhuanlan.zhihu.com/p/423176550\" title=\"Is the Singleton Pattern Simple? But Can You Really Get It Right?\"\u003eIs the Singleton Pattern Simple? But Can You Really Get It Right?\u003c/a\u003e\u0026rdquo;):\u003c/p\u003e","title":"C++ Singleton Template"},{"content":"What Is reflection reflection ’s human ’s , has ’s .\nDigestcalculate, among them, has as follows:code:\n1 2 3 4 5 6 if selected_algorithm == \u0026#34;SHA-512\u0026#34;: algorithm = hashlib.sha512() elif selected_algorithm == \u0026#34;SHA-256\u0026#34;: algorithm = hashlib.sha256() else: algorithm = hashlib.md5() code is userchoose ’s Digestcalculateway,Creating ’s object,very ’s method.\nbut is has ,cannotmore?reflection:\n1 2 3 4 if hasattr(hashlib, selected_algorithm): algorithm = getattr(hashlib, selected_algorithm)() else: algorithm = hashlib.md5() a kind of Digestalgorithm,program,;Usingreflection, can runtime,choose ’s string ’s methodcall.\n,cost is ?cost is someefficiency. Java has Usingreflection, for example, ’s Spring.\nhas somereflection ’s materialreflection can encapsulation,accessclass and object ’s member, is a . if need access,necessaryprivateprotectedmember; if must ,mostthink is is has Problem,do not more.\nAboutC++ and reflection C++ 20standard has , is has keywordtypeid has reflection ’s , ’s standard.\n, is must ’s ,applicationscenariocodecomplexity,Using.\nProgramming,first step is ,Usage、;second step is ,do not ;third step is ,,also,,.\n","permalink":"https://www.333rd.net/en/posts/read/programming-reflection/","summary":"\u003ch1 id=\"what-is-reflection\"\u003eWhat Is reflection\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003ereflection ’s human ’s , has  ’s .\u003c/em\u003e\u003c/p\u003e\n\u003ch2\u003e\u003c/h2\u003e\n\u003cp\u003e\u003ca href=\"https://github.com/BunnySakura/HashCalculator/blame/0b87c69ef6c6db0bf422098162d84ac94406e47e/main.py#L181\"\u003eDigestcalculate\u003c/a\u003e, among them,  has  as follows:code:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e5\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e6\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"n\"\u003eselected_algorithm\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;SHA-512\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ealgorithm\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ehashlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esha512\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelif\u003c/span\u003e \u003cspan class=\"n\"\u003eselected_algorithm\u003c/span\u003e \u003cspan class=\"o\"\u003e==\u003c/span\u003e \u003cspan class=\"s2\"\u003e\u0026#34;SHA-256\u0026#34;\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ealgorithm\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ehashlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003esha256\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ealgorithm\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ehashlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emd5\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003ecode is userchoose ’s Digestcalculateway,Creating ’s object,very ’s method.\u003c/p\u003e\n\u003cp\u003ebut  is  has ,cannotmore?reflection:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e4\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-python\" data-lang=\"python\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eif\u003c/span\u003e \u003cspan class=\"nb\"\u003ehasattr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehashlib\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eselected_algorithm\u003c/span\u003e\u003cspan class=\"p\"\u003e):\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ealgorithm\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"nb\"\u003egetattr\u003c/span\u003e\u003cspan class=\"p\"\u003e(\u003c/span\u003e\u003cspan class=\"n\"\u003ehashlib\u003c/span\u003e\u003cspan class=\"p\"\u003e,\u003c/span\u003e \u003cspan class=\"n\"\u003eselected_algorithm\u003c/span\u003e\u003cspan class=\"p\"\u003e)()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eelse\u003c/span\u003e\u003cspan class=\"p\"\u003e:\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"n\"\u003ealgorithm\u003c/span\u003e \u003cspan class=\"o\"\u003e=\u003c/span\u003e \u003cspan class=\"n\"\u003ehashlib\u003c/span\u003e\u003cspan class=\"o\"\u003e.\u003c/span\u003e\u003cspan class=\"n\"\u003emd5\u003c/span\u003e\u003cspan class=\"p\"\u003e()\u003c/span\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cp\u003ea kind of Digestalgorithm,program,;Usingreflection, can runtime,choose ’s string ’s methodcall.\u003c/p\u003e\n\u003cp\u003e\u003cdel\u003e,cost is ?cost is someefficiency.\u003c/del\u003e Java has Usingreflection, for example,  ’s \u003ccode\u003eSpring\u003c/code\u003e.\u003c/p\u003e","title":"What Is Reflection in Programming?"},{"content":"Setting Up a Private Git Service with Gitea There is always code that doesn\u0026rsquo;t fit on someone else\u0026rsquo;s server, so why not build your own Git service. This tutorial uses Docker, making deployment easy, migration simple, and it supports https.\nSteps 1. Preparation Pick a path you like and create a new folder to store all the files that come later. Here I recommend naming it Gitea.\nThen create ./docker-compose.yml for quickly deploying the container.\n2. Edit docker-compose.yml docker-compose.yml is modified from the official documentation, using the configuration given in the PostgreSQL Database section.\nHere, in addition to the gitea and postgres images, I also added the nginx image to enable https.\nThe content is as follows, and you can modify it as needed:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 version: \u0026#34;3\u0026#34; networks: gitea: external: false services: server: image: gitea/gitea:latest container_name: gitea environment: - USER_UID=1000 - USER_GID=1000 - GITEA__database__DB_TYPE=postgres - GITEA__database__HOST=db:5432 - GITEA__database__NAME=gitea - GITEA__database__USER=gitea - GITEA__database__PASSWD=gitea restart: always networks: - gitea volumes: - ./gitea:/data - /etc/timezone:/etc/timezone:ro - /etc/localtime:/etc/localtime:ro # ports: # - \u0026#34;3000:3000\u0026#34; # - \u0026#34;222:22\u0026#34; depends_on: - db - reverse_proxy db: image: postgres:14 restart: always environment: - POSTGRES_USER=gitea - POSTGRES_PASSWORD=gitea - POSTGRES_DB=gitea networks: - gitea volumes: - ./postgres:/var/lib/postgresql/data reverse_proxy: image: nginx:latest restart: always networks: - gitea ports: - \u0026#34;443:443\u0026#34; volumes: - ./nginx/conf.d/gitea.conf:/etc/nginx/conf.d/gitea.conf - ./nginx/gitea.crt:/etc/nginx/gitea.crt - ./nginx/gitea.key:/etc/nginx/gitea.key 3. Configure https Create the directory ./nginx/conf.d, and create the files ./nginx/conf.d/gitea.conf, ./nginx/gitea.crt, and ./nginx/gitea.key:\n1 2 mkdir -p ./nginx/conf.d touch ./nginx/conf.d/gitea.conf ./nginx/gitea.crt ./nginx/gitea.key Edit gitea.conf:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 server { # SSL access port number is 443 listen 443 ssl; # Enter the domain bound to the certificate server_name gitea.com; # If they come here using HTTP, bounce them to the correct scheme error_page 497 https://$server_name:$server_port$request_uri; # Upload size limit client_max_body_size 1000M; # Logs error_log /var/log/nginx/error.log; access_log /var/log/nginx/access.log; # Certificate file ssl_certificate /etc/nginx/gitea.crt; # Certificate key file ssl_certificate_key /etc/nginx/gitea.key; ssl_protocols TLSv1.2 TLSv1.1 TLSv1; ssl_ciphers ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-AES128-SHA256:ECDHE-RSA-AES128-SHA256:ECDHE-ECDSA-AES128-SHA:ECDHE-RSA-AES256-SHA384:ECDHE-RSA-AES128-SHA:ECDHE-ECDSA-AES256-SHA384:ECDHE-ECDSA-AES256-SHA:ECDHE-RSA-AES256-SHA:DHE-RSA-AES128-SHA256:DHE-RSA-AES128-SHA:DHE-RSA-AES256-SHA256:DHE-RSA-AES256-SHA:ECDHE-ECDSA-DES-CBC3-SHA:ECDHE-RSA-DES-CBC3-SHA:EDH-RSA-DES-CBC3-SHA:AES128-GCM-SHA256:AES256-GCM-SHA384:AES128-SHA256:AES256-SHA256:AES128-SHA:AES256-SHA:DES-CBC3-SHA:!DSS; ssl_prefer_server_ciphers on; location / { proxy_pass http://gitea:3000; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $http_host; proxy_set_header X-Nginx-Proxy true; } } Copy the SSL certificate content into gitea.crt and gitea.key.\n4. Start the Containers 1 2 # -d: run in the background docker-compose up -d Then visit the website and you can start using it.\nReferences About Gitea ","permalink":"https://www.333rd.net/en/posts/tech/gitea-private-git-service/","summary":"\u003ch1 id=\"setting-up-a-private-git-service-with-gitea\"\u003eSetting Up a Private Git Service with Gitea\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThere is always code that doesn\u0026rsquo;t fit on someone else\u0026rsquo;s server, so why not build your own Git service. This tutorial uses Docker, making deployment easy, migration simple, and it supports https.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-preparation\"\u003e1. Preparation\u003c/h3\u003e\n\u003cp\u003ePick a path you like and create a new folder to store all the files that come later. Here I recommend naming it \u003ccode\u003eGitea\u003c/code\u003e.\u003c/p\u003e\n\u003cp\u003eThen create \u003ccode\u003e./docker-compose.yml\u003c/code\u003e for quickly deploying the container.\u003c/p\u003e","title":"Setting Up a Private Git Service with Gitea"},{"content":"Using a USB WiFi Adapter with Kali for Penetration Testing I found a driver-free USB WiFi adapter. Although it can access the internet without a driver, you still need to install the driver to unlock all its features.\nSteps 1. Buy a USB WiFi Adapter Search Taobao for kali and driver-free and just pick one you like. I used the 2.4G adapter from the Qiwei Huaqing shop that looks like a receiver.\n2. Install the Driver Run the following commands to update:\n1 2 sudo apt update sudo apt install linux-image-amd64 -y Edit /etc/modprobe.d/realtek.conf and add this line:\n1 blacklist r8188eu Reboot, then run:\n1 2 sudo apt install linux-headers-$(uname -r) -y sudo apt install realtek-rtl8188eus-dkms -y That\u0026rsquo;s it. After that, you can mess around freely with things like ARP man-in-the-middle attacks and packet sniffing. However, I only used it to change my MAC address, so I won\u0026rsquo;t do any further testing — I\u0026rsquo;m a law-abiding citizen 🤣.\n","permalink":"https://www.333rd.net/en/posts/tech/kali-usb-wifi-adapter-penetration-testing/","summary":"\u003ch1 id=\"using-a-usb-wifi-adapter-with-kali-for-penetration-testing\"\u003eUsing a USB WiFi Adapter with Kali for Penetration Testing\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI found a driver-free USB WiFi adapter. Although it can access the internet without a driver, you still need to install the driver to unlock all its features.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-buy-a-usb-wifi-adapter\"\u003e1. Buy a USB WiFi Adapter\u003c/h3\u003e\n\u003cp\u003eSearch Taobao for \u003ccode\u003ekali\u003c/code\u003e and \u003ccode\u003edriver-free\u003c/code\u003e and just pick one you like. I used the 2.4G adapter from the \u003cstrong\u003eQiwei Huaqing\u003c/strong\u003e shop that looks like a receiver.\u003c/p\u003e","title":"Using a USB WiFi Adapter with Kali for Penetration Testing"},{"content":"Using External Tools in PyCharm This thing is really handy, especially for packaging with pyinstaller — one-click packaging, no need to keep typing commands.\nSteps 1. Getting Started Open Settings, find the External Tools option as shown in the figure, and open it.\nGetting Started 2. Create a New External Tool An external tool is a set of predefined commands. When you use it, macros are replaced based on the file to produce the final command, which is then executed automatically. In short, it saves you the time spent typing commands — a few mouse clicks and the pyinstaller command is invoked to package your script. Let\u0026rsquo;s take that as an example below.\npyinstaller packaging Among them, Name is the name displayed when the tool is used, Group is used to organize tools into groups, and Description is self-explanatory.\nFocus mainly on the tool settings:\nProgram\nEnter the path to the program, which can be an absolute path or a relative path. Clicking the plus sign lets you add macros, making it adapt to your project path automatically. Here\u0026rsquo;s what I use:\n1 $ProjectFileDir$\\venv\\Scripts\\pyinstaller.exe Here, $ProjectFileDir$ is a macro that is automatically replaced with the project file directory when used.\nArguments\nHere you configure the arguments passed to the program when it runs.\n1 --clean -F $FileNameWithoutExtension$.py -w -i logo.ico -n $ProjectName$ Working directory\nThe current directory in which the program runs. Here I\u0026rsquo;ve set it to the file directory, i.e., the path of the file being executed. For example, when I package main.py, it\u0026rsquo;s the path where that file is located.\n1 $FileDir$ 3. Use the External Tool For example, to package main.py with pyinstaller, right-click main.py, click External Tools in the menu, and then click the external tool created above. It will automatically invoke pyinstaller and execute the command with the given arguments to complete the packaging.\n4. A Few More Common External Tools Open Qt Designer\nProgram 1 $ProjectFileDir$\\venv\\Scripts\\pyside6-designer.exe Working directory 1 $FileDir$ Convert UI files to py files\nProgram 1 $ProjectFileDir$\\venv\\Scripts\\pyside6-uic.exe Arguments 1 $FileName$ -o $FileNameWithoutExtension$.py Working directory 1 $FileDir$ Convert resource files to py files\nProgram 1 $ProjectFileDir$\\venv\\Scripts\\pyside6-rcc.exe Arguments 1 $FileName$ -o $FileNameWithoutExtension$.py Working directory 1 $FileDir$ ","permalink":"https://www.333rd.net/en/posts/tech/pycharm-external-tools/","summary":"\u003ch1 id=\"using-external-tools-in-pycharm\"\u003eUsing External Tools in PyCharm\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThis thing is really handy, especially for packaging with \u003ccode\u003epyinstaller\u003c/code\u003e — one-click packaging, no need to keep typing commands.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-getting-started\"\u003e1. Getting Started\u003c/h3\u003e\n\u003cp\u003eOpen Settings, find the \u003cstrong\u003eExternal Tools\u003c/strong\u003e option as shown in the figure, and open it.\u003c/p\u003e\n\u003cp\u003e\n\n\n\u003cfigure class=\"zoom-container\"\u003e\n    \u003cimg src=\"https://img.333rd.net/%E6%8A%80%E6%9C%AF/PyCharm%E4%BD%BF%E7%94%A8%E5%A4%96%E9%83%A8%E5%B7%A5%E5%85%B7/%E5%BC%80%E5%A7%8B%E8%AE%BE%E7%BD%AE.png\" \n         alt=\"Getting Started\" title=\"Getting Started\"loading=\"lazy\" /\u003e\n    \n    \u003cfigcaption\u003eGetting Started\u003c/figcaption\u003e\u003c/figure\u003e\n\u003c/p\u003e\n\u003ch3 id=\"2-create-a-new-external-tool\"\u003e2. Create a New External Tool\u003c/h3\u003e\n\u003cp\u003eAn external tool is a set of predefined commands. When you use it, macros are replaced based on the file to produce the final command, which is then executed automatically. In short, it saves you the time spent typing commands — a few mouse clicks and the \u003ccode\u003epyinstaller\u003c/code\u003e command is invoked to package your script. Let\u0026rsquo;s take that as an example below.\u003c/p\u003e","title":"Using External Tools in PyCharm"},{"content":"Cleaning Up Remote Desktop History Microsoft really doesn\u0026rsquo;t care about small details; these little problems are probably ancient legacy issues.\nSteps 1. Clean up the configuration file There is a Default.rdp in the Documents directory, which stores the configuration of the last connection. Deleting it can clear the default display, but it will be created again the next time you connect to Remote Desktop, so it is not very meaningful.\n2. Delete shortcut connections Right-click the Remote Desktop icon, and you can see the recent connection records; right-click and delete them.\n3. Clean up the Remote Desktop host dropdown list Open the following path in the registry:\n1 计算机\\HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Terminal Server Client\\Default The values starting with MRU on the right side are the history records; delete the ones you don\u0026rsquo;t want.\n4. Clean up the saved usernames of Remote Desktop Open the following path in the registry:\n1 计算机\\HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Terminal Server Client\\Servers Expand this directory to see the saved usernames; delete the ones you don\u0026rsquo;t want.\n5. Delete network drive mapping history Open the following path in the registry:\n1 计算机\\HKEY_CURRENT_USER\\SOFTWARE\\Microsoft\\Windows\\CurrentVersion\\Explorer\\Map Network Drive MRU Delete the ones you don\u0026rsquo;t want on the right side.\n","permalink":"https://www.333rd.net/en/posts/tech/clean-remote-desktop-history/","summary":"\u003ch1 id=\"cleaning-up-remote-desktop-history\"\u003eCleaning Up Remote Desktop History\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eMicrosoft really doesn\u0026rsquo;t care about small details; these little problems are probably ancient legacy issues.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-clean-up-the-configuration-file\"\u003e1. Clean up the configuration file\u003c/h3\u003e\n\u003cp\u003eThere is a \u003cstrong\u003eDefault.rdp\u003c/strong\u003e in the \u003cstrong\u003eDocuments\u003c/strong\u003e directory, which stores the configuration of the last connection. Deleting it can clear the default display, but it will be created again the next time you connect to Remote Desktop, so it is not very meaningful.\u003c/p\u003e","title":"Cleaning Up Remote Desktop History"},{"content":"Setting Up an SSH Proxy in VSCode There is little material online about setting up an SSH proxy in VSCode, and what exists either requires installing dedicated software or simply does not work. I finally found one that works. The proxy is provided by Git. Compared with other proxy software, Git is probably installed on most machines. This method applies to the Windows platform.\nSteps Open the configuration file of the SSH extension and modify it according to your actual installation path:\n1 2 3 4 5 6 7 ProxyCommand C:\\Program Files\\Git\\mingw64\\bin\\connect.exe -S 127.0.0.1:1080 %h %p # C:\\Program Files\\Git\\mingw64\\bin\\connect.exe 改为Git安装目录下connect程序的绝对路径； # -S 使用socks代理，也可以选其他参数，见connect.exe帮助信息； # 127.0.0.1:10810 改为对应的IP和端口； # %h 是一个占位符，表示要连接的目标机，也就是Hostname指定的ip或者主机名。 # %p 同样也是占位符，表示要连接到目标机的端口。 The final SSH configuration data looks roughly like this:\n1 2 3 4 5 Host NewBee HostName 114.114.114.114 User root Port 22 ProxyCommand C:\\Program Files\\Git\\mingw64\\bin\\connect.exe -S 127.0.0.1:1080 %h %p ","permalink":"https://www.333rd.net/en/posts/tech/vscode-ssh-proxy/","summary":"\u003ch1 id=\"setting-up-an-ssh-proxy-in-vscode\"\u003eSetting Up an SSH Proxy in VSCode\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThere is little material online about setting up an SSH proxy in VSCode, and what exists either requires installing dedicated software or simply does not work. I finally found one that works. The proxy is provided by Git. Compared with other proxy software, Git is probably installed on most machines. \u003cstrong\u003eThis method applies to the Windows platform.\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003cp\u003eOpen the configuration file of the SSH extension and modify it according to your actual installation path:\u003c/p\u003e","title":"Setting Up an SSH Proxy in VSCode"},{"content":"Fixing Hugo Display Issues Caused by Missing CSS Press F12 to open the console, and you will see the error Failed to find a valid digest in the 'integrity' attribute for resource, which manifests as the page showing only text and icons with the layout gone. Local deployment or access via IP works fine, but access via the domain name fails.\nSolution The idea comes from Hugo - Failed to find a valid digest in the \u0026lsquo;integrity\u0026rsquo; attribute for resource - The resource has been blocked - Host on Github.\nThe cause of this problem is a Cloudflare setting — Auto Minify, which strips the spaces and some formatting from html, js, css and other files to shrink their size and optimize access speed. However, after the files are modified, the file verification fails when the site is accessed, so the browser blocks the file from loading for security reasons, which leads to the problem.\nThe solution is to turn off this setting; the gain is negligible anyway 🤣.\nAuto Minify References Hugo - Failed to find a valid digest in the \u0026lsquo;integrity\u0026rsquo; attribute for resource - The resource has been blocked - Host on Github ","permalink":"https://www.333rd.net/en/posts/tech/hugo-missing-css-display-fix/","summary":"\u003ch1 id=\"fixing-hugo-display-issues-caused-by-missing-css\"\u003eFixing Hugo Display Issues Caused by Missing CSS\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003ePress \u003ccode\u003eF12\u003c/code\u003e to open the console, and you will see the error \u003ccode\u003eFailed to find a valid digest in the 'integrity' attribute for resource\u003c/code\u003e, which manifests as the page showing only text and icons with the layout gone. Local deployment or access via IP works fine, but access via the domain name fails.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"solution\"\u003eSolution\u003c/h2\u003e\n\u003cp\u003eThe idea comes from \u003ca href=\"https://stackoverflow.com/questions/65040931/hugo-failed-to-find-a-valid-digest-in-the-integrity-attribute-for-resource\" title=\"Hugo - Failed to find a valid digest in the \u0026#39;integrity\u0026#39; attribute for resource - The resource has been blocked - Host on Github\"\u003eHugo - Failed to find a valid digest in the \u0026lsquo;integrity\u0026rsquo; attribute for resource - The resource has been blocked - Host on Github\u003c/a\u003e.\u003c/p\u003e","title":"Fixing Hugo Display Issues Caused by Missing CSS"},{"content":"Adding Math Formula Support to Hugo I happened to need it, so I just went ahead and solved it, based on KaTeX. It supports math formulas almost perfectly; both inline and display formulas can be rendered directly without modifying or adding any Shortcodes. The only drawback is that symbols with a double meaning in Markdown syntax itself, such as *, need to be escaped as \\*.\nSteps 1. Modify extend_head.html All paths below are described with the site root directory as the current directory:\nIf layouts/partials/extend_head.html exists, simply append the content at the end; if it doesn\u0026rsquo;t exist, create that path and copy the specified file into the directory for editing. The command is as follows:\n1 2 cp themes/PaperMod/layouts/partials/extend_head.html layouts/partials/extend_head.html \\ \u0026amp;\u0026amp; nano layouts/partials/extend_head.html Following the KaTeX documentation Auto-render Extension, add the following content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 {{ if or .Params.math .Site.Params.math }} \u0026lt;link rel=\u0026#34;stylesheet\u0026#34; href=\u0026#34;https://cdn.jsdelivr.net/npm/katex@0.16.2/dist/katex.min.css\u0026#34; integrity=\u0026#34;sha384-bYdxxUwYipFNohQlHt0bjN/LCpueqWz13HufFEV1SUatKs1cm4L6fFgCi1jT643X\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34;\u0026gt; \u0026lt;script defer src=\u0026#34;https://cdn.jsdelivr.net/npm/katex@0.16.2/dist/katex.min.js\u0026#34; integrity=\u0026#34;sha384-Qsn9KnoKISj6dI8g7p1HBlNpVx0I8p1SvlwOldgi3IorMle61nQy4zEahWYtljaz\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script defer src=\u0026#34;https://cdn.jsdelivr.net/npm/katex@0.16.2/dist/contrib/auto-render.min.js\u0026#34; integrity=\u0026#34;sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05\u0026#34; crossorigin=\u0026#34;anonymous\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; document.addEventListener(\u0026#34;DOMContentLoaded\u0026#34;, function () { renderMathInElement(document.body, { // customised options // • auto-render specific keys, e.g.: delimiters: [ { left: \u0026#39;$$\u0026#39;, right: \u0026#39;$$\u0026#39;, display: true }, { left: \u0026#39;$\u0026#39;, right: \u0026#39;$\u0026#39;, display: false }, { left: \u0026#39;\\\\(\u0026#39;, right: \u0026#39;\\\\)\u0026#39;, display: false }, { left: \u0026#39;\\\\[\u0026#39;, right: \u0026#39;\\\\]\u0026#39;, display: true } ], // • rendering keys, e.g.: throwOnError: false }); }); \u0026lt;/script\u0026gt; {{ end }} 2. Usage Add the following field to the front matter of the article in which you want to use math formulas:\n1 math: true # 是否开启mathjax You can also add the above setting to archetypes/defaults.md, so that the field is automatically added every time a new article is created. In addition, changing true to false will disable the math formula feature in that article.\nThe usage format is the same as Markdown math formulas. Just be aware of the characters that need escaping. Here is an example, in which * is escaped as \\*.\n1 $$0.414213562373095048\\approx6\\*16^{-1}+a\\*16^{-2}+0\\*16^{-3}+\\cdots$$ $$0.414213562373095048\\approx6*16^{-1}+a*16^{-2}+0*16^{-3}+\\cdots$$\nReferences How to enable Math Typesetting in PaperMod? Auto-render Extension ","permalink":"https://www.333rd.net/en/posts/tech/hugo-mathjax-support/","summary":"\u003ch1 id=\"adding-math-formula-support-to-hugo\"\u003eAdding Math Formula Support to Hugo\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI happened to need it, so I just went ahead and solved it, based on \u003cstrong\u003eKaTeX\u003c/strong\u003e. It supports math formulas almost perfectly; both inline and display formulas can be rendered directly without modifying or adding any \u003ccode\u003eShortcodes\u003c/code\u003e. The only drawback is that symbols with a double meaning in Markdown syntax itself, such as \u003ccode\u003e*\u003c/code\u003e, need to be escaped as \u003ccode\u003e\\*\u003c/code\u003e.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-modify-extend_headhtml\"\u003e1. Modify extend_head.html\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003eAll paths below are described with the site root directory as the current directory:\u003c/strong\u003e\u003c/p\u003e","title":"Adding MathJax Math Formula Support to Hugo"},{"content":"SHA Message Digest The Secure Hash Algorithm (SHA) is a family of cryptographic hash functions certified by FIPS. It computes a fixed-length string (also known as a message digest) from a given digital message, with a very high probability that different messages will produce different strings.\n1. Introduction to SHA SHA currently has four algorithm versions: SHA-0, SHA-1, SHA-2, and SHA-3. The most widely used today is SHA-2.\nSHA function comparison Algorithm and variant Output hash value length\n（bits） Intermediate hash value length\n（bits） Block size\n（bits） Maximum input message length\n（bits） Number of rounds Operations used Collision attack\n（bits） Performance example[3]\n(MiB/s) MD5（for reference） 128 128\n(4 × 32) 512 Unlimited[4] 64 And, Xor, Rot, Add (mod\u0026nbsp;232), Or ≤18\n（collision found） 335 SHA-0 160 160\n(5 × 32) 512 264 − 1 80 And, Xor, Rot, Add (mod\u0026nbsp;232), Or \u0026lt;34\n（collision found） - SHA-1 160 160\n(5 × 32) 512 264 − 1 80 \u0026lt;63[5]\n（collision found[6]） 192 SHA-2 SHA-224\nSHA-256 224\n256 256\n(8 × 32) 512 264 − 1 64 And, Xor, Rot, Add (mod\u0026nbsp;232), Or, Shr 112 128 139 SHA-384\nSHA-512\nSHA-512/224\nSHA-512/256 384\n512\n224 256 512\n(8 × 64) 1024 2128 − 1 80 And, Xor, Rot, Add (mod\u0026nbsp;264), Or, Shr 192\n256\n112\n128 154 SHA-3 SHA3-224\nSHA3-256\nSHA3-384\nSHA3-512 224\n256\n384\n512 1600\n(5 × 5 × 64) 1152\n1088\n832\n576 Unlimited[7] 24[8] And, Xor, Rot, Not 112\n128\n192\n256 - SHAKE128\nSHAKE256 d (arbitrary)\nd (arbitrary) 1344\n1088 min(d/2, 128)\nmin(d/2, 256) - Since the various SHA-2 algorithm variants differ only slightly in digest length, number of rounds, and other minor details while sharing the same basic structure, and since there are many detailed explanations of SHA-256 available online, we will use SHA-256 as our working example here.\nThe original text is based on \u0026ldquo;Understanding the SHA-256 Algorithm: Principles and Implementation\u0026rdquo;. For easier understanding, a SHA-256 Algorithm Step-by-Step Visualization is provided; it is recommended to follow along with the visualization as you read.\n2. Principle Analysis 2.1. Constant Initialization The initial hash value $H^{(0)}$ is derived from the fractional parts of the square roots of the first 8 prime numbers $(2,3,5,7,11,13,17,19)$, taking the first 32 bits of each. For example, the fractional part of $\\sqrt{2}$ is approximately $0.414213562373095048$, where\n$$0.414213562373095048\\approx6*16^{-1}+a*16^{-2}+0*16^{-3}+\\cdots$$\nThus, the first 32 bits of the fractional part of the square root of prime 2 correspond to 0x6a09e667.\nFollowing the same process, the initial hash value $H^{(0)}$ consists of the following eight 32-bit hash initial values:\n$$H_{1}^{(0)}=6a09e667$$ $$H_{2}^{(0)}=bb67ae85$$ $$H_{3}^{(0)}=3c6ef372$$ $$H_{4}^{(0)}=a54ff53a$$ $$H_{5}^{(0)}=510e527f$$ $$H_{6}^{(0)}=9b05688c$$ $$H_{7}^{(0)}=1f83d9ab$$ $$H_{8}^{(0)}=5be0cd19$$\nThe SHA-256 algorithm also uses 64 round constants, derived from the first 32 bits of the fractional parts of the cube roots of the first 64 prime numbers. Expressed in hexadecimal, the constants are as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 428a2f98 71374491 b5c0fbcf e9b5dba5 3956c25b 59f111f1 923f82a4 ab1c5ed5 d807aa98 12835b01 243185be 550c7dc3 72be5d74 80deb1fe 9bdc06a7 c19bf174 e49b69c1 efbe4786 0fc19dc6 240ca1cc 2de92c6f 4a7484aa 5cb0a9dc 76f988da 983e5152 a831c66d b00327c8 bf597fc7 c6e00bf3 d5a79147 06ca6351 14292967 27b70a85 2e1b2138 4d2c6dfc 53380d13 650a7354 766a0abb 81c2c92e 92722c85 a2bfe8a1 a81a664b c24b8b70 c76c51a3 d192e819 d6990624 f40e3585 106aa070 19a4c116 1e376c08 2748774c 34b0bcb5 391c0cb3 4ed8aa4a 5b9cca4f 682e6ff3 748f82ee 78a5636f 84c87814 8cc70208 90befffa a4506ceb bef9a3f7 c67178f2 2.2. Message Preprocessing The message is first padded so that its final length is a multiple of 512 bits. It is then divided into 512-bit blocks: $M^{(1)},M^{(2)},\\cdots,M^{(N)}$.\nSuppose the binary encoding of the message $M$ has a length of $l$ bits. First, a single \u0026ldquo;$1$\u0026rdquo; bit is appended to the message, followed by $k$ \u0026ldquo;$0$\u0026rdquo; bits, where $k$ is the smallest non-negative integer satisfying:\n$$l+1+k=448\\ mod\\ 512$$\nThis padding ensures the message length is brought to $448$ bits modulo $512$, because the final 64 bits will be used to encode the original message length $l$ as a 64-bit binary value, bringing the total length to an exact multiple of $512$ bits.\nTwo important notes here:\nRegardless of the original message length, padding must always be performed—even if the message length already satisfies $l \\equiv 448 \\pmod{512}$, in which case a full 512-bit block of padding is added. Additionally, since the message length $l$ is encoded as a 64-bit binary value, the length must be less than $2^{64}$, which is more than sufficient for virtually all use cases. After padding, the message is divided into 512-bit blocks: $M^{(1)},M^{(2)},\\cdots,M^{(N)}$, where the first 32 bits of the $i$-th block are denoted $M_{0}^{(i)}$, the next 32 bits $M_{1}^{(i)}$, and so on, with the final 32 bits denoted $M_{15}^{(i)}$. We use the $Big\\ endian$ (big-endian) convention for data encoding, meaning the first byte is the most significant byte; therefore, for each 32-bit word, the leftmost bit is the most significant bit.\n2.3. Main Digest Calculation Loop For convenience, we define the following operators (all operations are performed on 32-bit binary data):\nOperator Operation $\\oplus$ Bitwise XOR $\\wedge$ Bitwise AND $\\vee$ Bitwise OR $\\neg$ Bitwise NOT $+$ Addition modulo $2^{32}$ $R^{n}$ Right shift by $n$ bits $S^{n}$ Circular right shift by $n$ bits The following functions are defined: $$Ch(x,y,z)=(x \\wedge y) \\oplus (\\neg x \\wedge z)$$ $$M_{aj}(x,y,z)=(x \\wedge y) \\oplus (x \\wedge z) \\oplus (y \\wedge z)$$ $$\\Sigma_{0}(x)=S^{2}(x) \\oplus S^{13}(x) \\oplus S^{22}(x)$$ $$\\Sigma_{1}(x)=S^{6}(x) \\oplus S^{11}(x) \\oplus S^{25}(x)$$ $$\\sigma_{0}(x)=S^{7}(x) \\oplus S^{18}(x) \\oplus R^{3}(x)$$ $$\\sigma_{1}(x)=S^{17}(x) \\oplus S^{19}(x) \\oplus R^{10}(x)$$\n2.3.1. Message Schedule Expansion Each original message block $M^{(i)}$ is expanded from 512 bits to 2048 bits for computation, where every 32 bits form one word $W_{j}$.\nThe expanded message words $W_{0},W_{1},\\cdots,W_{63}$ are computed as follows:\n$for\\ j = 0 \\rightarrow 15$ $$W_{j}=M_{j}^{(i)}$$\n$for\\ j = 16 \\rightarrow 63$ $$W_{j}=W_{j-16}+\\sigma_{0}(W_{j-15})+W_{j-7}+\\sigma_{1}(W_{j-2})$$\nOnce all words are computed, we proceed to the next step.\n2.3.2. Intermediate Hash Value Calculation The constant initialization section provided the initial hash value $H^{(0)}$ and the 64 constants $K_{0},K_{1},\\cdots,K_{63}$, which are used in this section. We also introduce $a,b,c,d,e,f,g,h$ as working variables.\n$for\\ i = 1 \\rightarrow N$（where $N$ is the number of padded message blocks） $$a=H_{1}^{(i-1)}$$ $$b=H_{2}^{(i-1)}$$ $$\\vdots$$ $$h=H_{8}^{(i-1)}$$\nThat is, for the first block (when $i=1$), the working variables $a,b,c,d,e,f,g,h$ are initialized with $H^{(0)}$. For each subsequent block, they are initialized with the intermediate hash values $H^{(i-1)}$.\n$for\\ j = 0 \\rightarrow 63$\nFirst, compute $T_{1}$ and $T_{2}$: $$T_{1}=h+\\Sigma_{1}(e)+Ch(e,f,g)+K_{j}+W_{j}$$ $$T_{2}=\\Sigma_{0}(a)+M_{aj}(a,b,c)$$\nFor each expanded message word $W_{j}$ in $W_{0},W_{1},\\cdots,W_{63}$, perform the following operations: $$h=g$$ $$g=f$$ $$f=e$$ $$e=d+T_{1}$$ $$d=c$$ $$c=b$$ $$b=a$$ $$a=T_{1}+T_{2}$$\nOnce all iterations are complete, the final values of $a,b,c,d,e,f,g,h$ for this block are used to compute: $$H_{1}^{(i)}=a+H_{1}^{(i-1)}$$ $$H_{2}^{(i)}=b+H_{2}^{(i-1)}$$ $$\\vdots$$ $$H_{8}^{(i)}=h+H_{8}^{(i-1)}$$\n$H_{1}^{(i)},H_{2}^{(i)},\\cdots,H_{8}^{(i)}$ are the intermediate hash values for this block, which also serve as the initial values of $a,b,c,d,e,f,g,h$ for the next block.\n2.4. Obtaining the Hash Result After processing every 512-bit padded message block, we obtain $H_{1}^{(N)},H_{2}^{(N)},\\cdots,H_{8}^{(N)}$. These are expressed in hexadecimal and concatenated together, with $H_{1}^{(N)}$ as the most significant part, followed by $H_{2}^{(N)},H_{3}^{(N)},\\cdots,H_{8}^{(N)}$ in order, and $H_{8}^{(N)}$ as the least significant part. The resulting hexadecimal string is the final output, expressed in big-endian order.\n2.5. Summary The SHA-256 algorithm can be summarized as follows:\nThe message is padded so that its final length is a multiple of 512 bits, then divided into 512-bit blocks. Starting from a fixed initial hash $H^{(0)}$, each message block is processed sequentially with the following computation: $$H^{(i)}=H^{(i-1)}+C_{M^{(i)}}(H^{(i-1)})$$ where $C$ is the SHA-256 compression function, $+$ denotes addition modulo $2^{32}$ (i.e., adding two numbers and taking the result modulo $2^{32}$), and $H^{(N)}$ is the final hash value of the message. References SHA Family Understanding the SHA-256 Algorithm: Principles and Implementation SHA-256 Algorithm Step-by-Step Visualization ","permalink":"https://www.333rd.net/en/posts/read/sha-message-digest/","summary":"\u003ch1 id=\"sha-message-digest\"\u003eSHA Message Digest\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThe Secure Hash Algorithm (SHA) is a family of cryptographic hash functions certified by FIPS. It computes a fixed-length string (also known as a message digest) from a given digital message, with a very high probability that different messages will produce different strings.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"1-introduction-to-sha\"\u003e1. Introduction to SHA\u003c/h2\u003e\n\u003cp\u003eSHA currently has four algorithm versions: SHA-0, SHA-1, SHA-2, and SHA-3. The most widely used today is \u003cstrong\u003eSHA-2\u003c/strong\u003e.\u003c/p\u003e\n\u003ctable class=\"wikitable\" style=\"margin-top: 0px; width:100%\"\u003e\n\u003ccaption\u003eSHA function comparison\n\u003c/caption\u003e\n\u003ctbody\u003e\u003ctr style=\"vertical-align:bottom;\"\u003e\n\u003cth colspan=\"2\"\u003eAlgorithm and variant\n\u003c/th\u003e\n\u003cth\u003eOutput hash value length\u003cbr\u003e（bits）\n\u003c/th\u003e\n\u003cth\u003eIntermediate hash value length\u003cbr\u003e（bits）\n\u003c/th\u003e\n\u003cth\u003eBlock size\u003cbr\u003e（bits）\n\u003c/th\u003e\n\u003cth\u003eMaximum input message length\u003cbr\u003e（bits）\n\u003c/th\u003e\n\u003cth\u003eNumber of rounds\n\u003c/th\u003e\n\u003cth\u003eOperations used\n\u003c/th\u003e\n\u003cth\u003eCollision attack\u003cbr\u003e（bits）\n\u003c/th\u003e\n\u003cth\u003ePerformance example\u003csup id=\"cite_ref-3\" class=\"reference\"\u003e\u003ca href=\"#cite_note-3\"\u003e[3]\u003c/a\u003e\u003c/sup\u003e\u003cbr\u003e(\u003ca href=\"/wiki/Mebibyte\" title=\"Mebibyte\"\u003eMiB\u003c/a\u003e/s)\n\u003c/th\u003e\u003c/tr\u003e\n\u003ctr style=\"text-align:center;vertical-align:top;\"\u003e\n\u003ctd colspan=\"2\"\u003e\u003cb\u003e\u003ca href=\"/wiki/MD5\" title=\"MD5\"\u003eMD5\u003c/a\u003e\u003c/b\u003e（for reference）\u003c/td\u003e\n\u003ctd\u003e128\u003c/td\u003e\n\u003ctd\u003e128\u003cbr\u003e\u003cspan class=\"nowrap\"\u003e(4 × 32)\u003c/span\u003e\u003c/td\u003e\n\u003ctd\u003e512\u003c/td\u003e\n\u003ctd\u003eUnlimited\u003csup id=\"cite_ref-4\" class=\"reference\"\u003e\u003ca href=\"#cite_note-4\"\u003e[4]\u003c/a\u003e\u003c/sup\u003e\u003c/td\u003e\n\u003ctd\u003e64\u003c/td\u003e\n\u003ctd\u003eAnd, Xor, Rot, \u003cspan class=\"nowrap\"\u003eAdd (mod\u0026nbsp;2\u003csup\u003e32\u003c/sup\u003e),\u003c/span\u003e Or\u003c/td\u003e\n\u003ctd class=\"table-no\" style=\"text-align:center; background:#FF9090\"\u003e≤18\u003cbr\u003e（collision found）\u003c/td\u003e\n\u003ctd\u003e335\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr style=\"text-align:center;vertical-align:top;\"\u003e\n\u003ctd colspan=\"2\"\u003e\u003cb\u003e\u003cspan class=\"nowrap\"\u003e\u003ca href=\"/zh-cn/SHA-0\" class=\"mw-redirect\" title=\"SHA-0\"\u003eSHA-0\u003c/a\u003e\u003c/span\u003e\u003c/b\u003e\u003c/td\u003e\n\u003ctd\u003e160\u003c/td\u003e\n\u003ctd\u003e160\u003cbr\u003e\u003cspan class=\"nowrap\"\u003e(5 × 32)\u003c/span\u003e\u003c/td\u003e\n\u003ctd\u003e512\u003c/td\u003e\n\u003ctd\u003e2\u003csup\u003e64\u003c/sup\u003e − 1\u003c/td\u003e\n\u003ctd\u003e80\u003c/td\u003e\n\u003ctd rowspan=\"2\"\u003eAnd, Xor, Rot, \u003cspan class=\"nowrap\"\u003eAdd (mod\u0026nbsp;2\u003csup\u003e32\u003c/sup\u003e),\u003c/span\u003e Or\u003c/td\u003e\n\u003ctd class=\"table-no\" style=\"text-align:center; background:#FF9090\"\u003e\u0026lt;34\u003cbr\u003e（collision found）\u003c/td\u003e\n\u003ctd\u003e-\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr style=\"text-align:center;vertical-align:top;\"\u003e\n\u003ctd colspan=\"2\"\u003e\u003cb\u003e\u003cspan class=\"nowrap\"\u003e\u003ca href=\"/zh-cn/SHA-1\" title=\"SHA-1\"\u003eSHA-1\u003c/a\u003e\u003c/span\u003e\u003c/b\u003e\u003c/td\u003e\n\u003ctd\u003e160\u003c/td\u003e\n\u003ctd\u003e160\u003cbr\u003e\u003cspan class=\"nowrap\"\u003e(5 × 32)\u003c/span\u003e\u003c/td\u003e\n\u003ctd\u003e512\u003c/td\u003e\n\u003ctd\u003e2\u003csup\u003e64\u003c/sup\u003e − 1\u003c/td\u003e\n\u003ctd\u003e80\u003c/td\u003e\n\u003ctd class=\"table-no\" style=\"text-align:center; background:#FF9090\"\u003e\u0026lt;63\u003csup id=\"cite_ref-5\" class=\"reference\"\u003e\u003ca href=\"#cite_note-5\"\u003e[5]\u003c/a\u003e\u003c/sup\u003e\u003cbr\u003e（collision found\u003csup id=\"cite_ref-6\" class=\"reference\"\u003e\u003ca href=\"#cite_note-6\"\u003e[6]\u003c/a\u003e\u003c/sup\u003e）\u003c/td\u003e\n\u003ctd\u003e192\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr style=\"text-align:center;vertical-align:top;\"\u003e\n\u003ctd rowspan=\"2\"\u003e\u003cb\u003e\u003cspan class=\"nowrap\"\u003e\u003ca href=\"/wiki/SHA-2\" title=\"SHA-2\"\u003eSHA-2\u003c/a\u003e\u003c/span\u003e\u003c/b\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ci\u003eSHA-224\u003c/i\u003e\u003cbr\u003e\u003ci\u003eSHA-256\u003c/i\u003e\u003c/td\u003e\n\u003ctd\u003e224\u003cbr\u003e256\u003c/td\u003e\n\u003ctd\u003e256\u003cbr\u003e\u003cspan class=\"nowrap\"\u003e(8 × 32)\u003c/span\u003e\u003c/td\u003e\n\u003ctd\u003e512\u003c/td\u003e\n\u003ctd\u003e2\u003csup\u003e64\u003c/sup\u003e − 1\u003c/td\u003e\n\u003ctd\u003e64\u003c/td\u003e\n\u003ctd\u003eAnd, Xor, Rot, \u003cspan class=\"nowrap\"\u003eAdd (mod\u0026nbsp;2\u003csup\u003e32\u003c/sup\u003e),\u003c/span\u003e Or, Shr\u003c/td\u003e\n\u003ctd style=\"background: #99FF99; color: black; vertical-align: middle; text-align: center;\" class=\"yes table-yes2\"\u003e112 \u003cbr\u003e 128\u003c/td\u003e\n\u003ctd\u003e139\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr style=\"text-align:center;vertical-align:top;\"\u003e\n\u003ctd\u003e\u003ci\u003eSHA-384\u003c/i\u003e\u003cbr\u003e\u003ci\u003eSHA-512\u003c/i\u003e\u003cbr\u003e\u003ci\u003e\u003cspan class=\"nowrap\"\u003eSHA-512/224\u003c/span\u003e\u003c/i\u003e\u003cbr\u003e\u003ci\u003e\u003cspan class=\"nowrap\"\u003eSHA-512/256\u003c/span\u003e\u003c/i\u003e\u003c/td\u003e\n\u003ctd\u003e384\u003cbr\u003e512\u003cbr\u003e224 \u003cbr\u003e256\u003c/td\u003e\n\u003ctd\u003e512\u003cbr\u003e\u003cspan class=\"nowrap\"\u003e(8 × 64)\u003c/span\u003e\u003c/td\u003e\n\u003ctd\u003e1024\u003c/td\u003e\n\u003ctd\u003e2\u003csup\u003e128\u003c/sup\u003e − 1\u003c/td\u003e\n\u003ctd\u003e80\u003c/td\u003e\n\u003ctd\u003eAnd, Xor, Rot, \u003cspan class=\"nowrap\"\u003eAdd (mod\u0026nbsp;2\u003csup\u003e64\u003c/sup\u003e),\u003c/span\u003e Or, Shr\u003c/td\u003e\n\u003ctd style=\"background: #99FF99; color: black; vertical-align: middle; text-align: center;\" class=\"yes table-yes2\"\u003e192\u003cbr\u003e256\u003cbr\u003e112\u003cbr\u003e128\u003c/td\u003e\n\u003ctd\u003e154\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr style=\"text-align:center;vertical-align:top;\"\u003e\n\u003ctd rowspan=\"2\"\u003e\u003cb\u003e\u003cspan class=\"nowrap\"\u003e\u003ca href=\"/wiki/SHA-3\" title=\"SHA-3\"\u003eSHA-3\u003c/a\u003e\u003c/span\u003e\u003c/b\u003e\u003c/td\u003e\n\u003ctd\u003e\u003ci\u003eSHA3-224\u003c/i\u003e\u003cbr\u003e\u003ci\u003eSHA3-256\u003c/i\u003e\u003cbr\u003e\u003ci\u003eSHA3-384\u003c/i\u003e\u003cbr\u003e\u003ci\u003eSHA3-512\u003c/i\u003e\u003c/td\u003e\n\u003ctd\u003e224\u003cbr\u003e256\u003cbr\u003e384\u003cbr\u003e512\u003c/td\u003e\n\u003ctd rowspan=\"2\"\u003e1600\u003cbr\u003e\u003cspan class=\"nowrap\"\u003e(5 × 5 × 64)\u003c/span\u003e\u003c/td\u003e\n\u003ctd\u003e1152\u003cbr\u003e1088\u003cbr\u003e832\u003cbr\u003e576\u003c/td\u003e\n\u003ctd rowspan=\"2\"\u003eUnlimited\u003csup id=\"cite_ref-7\" class=\"reference\"\u003e\u003ca href=\"#cite_note-7\"\u003e[7]\u003c/a\u003e\u003c/sup\u003e\u003c/td\u003e\n\u003ctd rowspan=\"2\"\u003e24\u003csup id=\"cite_ref-8\" class=\"reference\"\u003e\u003ca href=\"#cite_note-8\"\u003e[8]\u003c/a\u003e\u003c/sup\u003e\u003c/td\u003e\n\u003ctd rowspan=\"2\"\u003eAnd, Xor, Rot, Not\u003c/td\u003e\n\u003ctd style=\"background: #99FF99; color: black; vertical-align: middle; text-align: center;\" class=\"yes table-yes2\"\u003e112\u003cbr\u003e128\u003cbr\u003e192\u003cbr\u003e256\u003c/td\u003e\n\u003ctd\u003e-\n\u003c/td\u003e\u003c/tr\u003e\n\u003ctr style=\"text-align:center;vertical-align:top;\"\u003e\n\u003ctd\u003e\u003ci\u003eSHAKE128\u003c/i\u003e\u003cbr\u003e\u003ci\u003eSHAKE256\u003c/i\u003e\u003c/td\u003e\n\u003ctd\u003e\u003cspan class=\"nowrap\"\u003e\u003ci\u003ed\u003c/i\u003e (arbitrary)\u003c/span\u003e\u003cbr\u003e\u003cspan class=\"nowrap\"\u003e\u003ci\u003ed\u003c/i\u003e (arbitrary)\u003c/span\u003e\u003c/td\u003e\n\u003ctd\u003e1344\u003cbr\u003e1088\u003c/td\u003e\n\u003ctd style=\"background: #99FF99; color: black; vertical-align: middle; text-align: center;\" class=\"yes table-yes2\"\u003emin(\u003ci\u003ed\u003c/i\u003e/2, 128)\u003cbr\u003emin(\u003ci\u003ed\u003c/i\u003e/2, 256)\u003c/td\u003e\n\u003ctd\u003e-\n\u003c/td\u003e\u003c/tr\u003e\u003c/tbody\u003e\u003c/table\u003e\n\u003cp\u003eSince the various SHA-2 algorithm variants differ only slightly in digest length, number of rounds, and other minor details while sharing the same basic structure, and since there are many detailed explanations of SHA-256 available online, we will use SHA-256 as our working example here.\u003c/p\u003e","title":"SHA Message Digest"},{"content":"NAS Server Configuration Steps 1. Install Kali UEFI boot mode is recommended; otherwise there is a chance that after installation you end up stuck on a black screen with a _ blinking in the top-left corner when booting. That means the boot is broken, and the cause is unknown. Anyway, switch to UEFI boot in the BIOS and then install it again, and you will be able to enter the system.\n2. Pre-process the Hard Drive If you are using an old hard drive, consider deleting the partitions and then creating new partitions formatted as the ext4 file system as needed. If you run into problems with the steps below, try rebooting to refresh the file system.\nView partitions\n1 sudo fdisk -l Here, for example, sda, sdb represent whole hard drives, while sda1, sda2 are the different partitions on the drive sda.\nDelete partitions (be careful with deletion operations)\nBe extremely careful. My hand slipped in the past two days, and I deleted the /etc on the server and the /dev on the NAS\u0026hellip;\nDelete the specified partition:\n1 sudo fdisk /dev/sdb # 要删分区的硬盘。 Then type m to view the help, d to delete; you will need to select the partition number. If you want to delete all the partitions of a whole hard drive, just delete them one by one with the defaults; you can also directly format the whole hard drive, see below for the steps. After deleting, remember to type w to save, otherwise the partitions will not be deleted.\nCreate new partitions\nThe same partition operation commands:\n1 sudo fdisk /dev/sdb Type n to create a new partition, type p to create a primary partition, enter the partition number, and the following steps determine the partition start position and partition size. If you have no special needs, just accept all defaults and make one partition over the whole hard drive.\nWhen done, remember to type w to save as well.\nFormat partitions (be careful with deletion operations)\nThe command to format the specified partition is as follows:\n1 sudo mkfs.ext4 /dev/sdb1 The command to format the whole hard drive:\n1 sudo mkfs.ext4 /dev/sdb It will ask for confirmation; don\u0026rsquo;t let your hand slip.\n3. Mount the Hard Drive Create a new mount point\n1 mkdir -p /mnt/HDD_1 Temporary mount; canceled at shutdown\n1 mount /dev/sdb1 /mnt/HDD_1 Unmount a partition\n1 umount /dev/sdb1 Configure auto-mount at boot\nFirst view the UUID of the partition:\n1 sudo blkid The output is as follows:\n1 /dev/sdb1: UUID=\u0026#34;11263962-9715-473f-9421-0b604e895aaa\u0026#34; TYPE=\u0026#34;ext4\u0026#34; Configure auto-mount:\n1 nano /etc/fstab Add a line:\n1 UUID=11263962-9715-473f-9421-0b604e895aaa /mnt/HDD_1 ext4 defaults 0 0 1 2 3 4 5 6 7 8 9 \u0026lt;fs spec\u0026gt; \u0026lt;fs file\u0026gt; \u0026lt;fs vfstype\u0026gt; \u0026lt;fs mntops\u0026gt; \u0026lt;fs freq\u0026gt; \u0026lt;fs passno\u0026gt; 具体说明，以挂载/dev/sdb1为例： \u0026lt;fs spec\u0026gt;： 分区定位，可以给UUID或LABEL，例如：UUID=6E9ADAC29ADA85CD或LABEL=software \u0026lt;fs file\u0026gt;： 具体挂载点的位置，例如：/mnt/HDD_1 \u0026lt;fs vfstype\u0026gt;：挂载磁盘类型，linux分区一般为ext4，windows分区一般为ntfs \u0026lt;fs mntops\u0026gt;： 挂载参数，一般为defaults \u0026lt;fs freq\u0026gt;： 磁盘检查，默认为0 \u0026lt;fs passno\u0026gt;： 磁盘检查，默认为0，不需要检查 Test the mount and see whether all the partitions have been successfully mounted.\n1 sudo mount -a If there is a problem and you have rebooted, it may cause the system partition to fail to auto-mount and the system to fail to boot; the solution is to enter recovery mode and modify /etc/fstab.\n4. Enable Samba Before this, if you are not using the root user, other users will only have read-only permissions later, so depending on your needs, consider giving all users read, write and execute permissions:\n1 sudo chmod 777 /mnt/HDD_1 Install Samba\n1 sudo apt install samba Configure Samba\n1 nano /etc/samba/smb.conf If the default configuration is not needed, you can delete it all; configure it as follows:\n1 2 3 4 5 [HDD_1] # 展示的共享名 path = /mnt/3TB_HDD_1 # 共享路径 browseable = yes # 允许浏览 writeable = yes # 允许写入 read only = no # 关闭只读 Add an SMB account\n1 sudo smbpasswd -a \u0026lt;用户名\u0026gt; # 必须是系统中存在的用户名，譬如当前用户名。 Start the smb service\n1 2 sudo systemctl restart smbd.service sudo systemctl restart nmbd.service Enable auto-start at boot\n1 2 sudo systemctl enable smbd.service sudo systemctl enable nmbd.service Done. Now you can connect to the NAS over SMB and enjoy life.\n","permalink":"https://www.333rd.net/en/posts/tech/nas-server-configuration/","summary":"\u003ch1 id=\"nas-server-configuration\"\u003eNAS Server Configuration\u003c/h1\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-install-kali\"\u003e1. Install Kali\u003c/h3\u003e\n\u003cp\u003eUEFI boot mode is recommended; otherwise there is a chance that after installation you end up stuck on a black screen with a \u003ccode\u003e_\u003c/code\u003e blinking in the top-left corner when booting. That means the boot is broken, and the cause is unknown. Anyway, \u003cstrong\u003eswitch to UEFI boot\u003c/strong\u003e in the BIOS and then install it again, and you will be able to enter the system.\u003c/p\u003e","title":"NAS Server Configuration"},{"content":"Enabling HTTPS on Hugo with an Nginx Reverse Proxy Sure, a website works fine over HTTP, but the browser still warns that it\u0026rsquo;s not secure, which is a bit of a buzzkill. Here\u0026rsquo;s a quick rundown of how to serve your Hugo site over HTTPS by putting an Nginx reverse proxy in front of it. The content is borrowed from Sulv\u0026rsquo;s post Deploying a Hugo blog to a Tencent Cloud lightweight server\nSteps 1. Install Nginx First, make sure port 80 and port 443 are not in use. Check them with the commands below; if no output is shown, the ports are free.\n1 2 sudo netstat -tulpn | grep :80 sudo netstat -tulpn | grep :443 On Debian-based systems, install Nginx with the command below. For other systems, a quick Baidu or Google search should do.\n1 sudo apt update \u0026amp;\u0026amp; apt upgrade \u0026amp;\u0026amp; apt install nginx Set Nginx to start on boot:\n1 sudo systemctl enable nginx Start Nginx:\n1 sudo systemctl start nginx Check the status of Nginx (if something is wrong, check whether the port is occupied):\n1 sudo systemctl status nginx That\u0026rsquo;s it — Nginx is now installed.\n2. Other Things You\u0026rsquo;ll Need Some cloud providers enable a firewall by default, so remember to open ports 80 and 443. Run hugo --gc in the root directory of your blog to generate the static website files, which you\u0026rsquo;ll need later; the generated files are in the ./public directory. Apply for an SSL certificate. Buying a domain usually comes with a free one-year certificate; renewals can be pricey, but you can just apply for another free one and swap out the old one. You can also look for free SSL certificate providers — a quick online search will find plenty, so I won\u0026rsquo;t go into detail. Nginx needs two certificate files, ending in .crt and .key. 3. Configure the nginx.conf File This file is generated in the /etc/nginx directory by default, and you should also move the SSL certificate you applied for above into this directory.\nThe configuration is provided by Sulv, as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 # First place to configure: change the user to root, otherwise you may not have permission user root; worker_processes auto; error_log /var/log/nginx/error.log; pid /run/nginx.pid; include /usr/share/nginx/modules/*.conf; events { worker_connections 1024; } http { log_format main \u0026#39;$remote_addr - $remote_user [$time_local] \u0026#34;$request\u0026#34; \u0026#39; \u0026#39;$status $body_bytes_sent \u0026#34;$http_referer\u0026#34; \u0026#39; \u0026#39;\u0026#34;$http_user_agent\u0026#34; \u0026#34;$http_x_forwarded_for\u0026#34;\u0026#39;; access_log /var/log/nginx/access.log main; sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; types_hash_max_size 2048; include /etc/nginx/mime.types; default_type application/octet-stream; include /etc/nginx/conf.d/*.conf; # http configuration server { # Second place to configure: port 80 access listen 80 default_server; listen [::]:80 default_server; # Third place to configure: the domain name server_name www.sulvblog.cn; # Hide the version number server_tokens off; # Automatically redirect from http to https rewrite ^(.*) https://$server_name$1 permanent; # Fourth place to configure: point this to the public folder root /home/public; include /etc/nginx/default.d/*.conf; # Fifth place to configure location / { root /home/public; index index.html index.htm; } # Sixth place to configure error_page 404 /404.html; location = /40x.html { root /home/public; } error_page 500 502 503 504 /50x.html; location = /50x.html { } } # https configuration server { listen 443 ssl; # Seventh place to configure server_name www.sulvblog.cn; root /home/public; # Eighth place to configure ssl_certificate /etc/nginx/1_sulvblog.cn_bundle.crt; ssl_certificate_key /etc/nginx/2_sulvblog.cn.key; # Ninth place to configure: you can follow my way of writing ssl_session_timeout 10m; ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:HIGH:!aNULL:!MD5:!RC4:!DHE; ssl_prefer_server_ciphers on; # Tenth place to configure error_page 404 /404.html; location = /404.html { root /home/public; } include /etc/nginx/default.d/*.conf; } } Once you\u0026rsquo;ve made all the changes at the marked spots above, remember that the static website files generated by Hugo are in the ./public directory mentioned earlier, and make sure you don\u0026rsquo;t get the SSL certificate\u0026rsquo;s path and filename wrong.\nTest whether the configuration is correct:\n1 sudo nginx -t Then reload the configuration file with Nginx:\n1 sudo nginx -s reload Check the running status of Nginx:\n1 sudo systemctl status nginx Restart the Nginx service:\n1 sudo systemctl restart nginx The setup is now complete, and you can visit your blog to see the result. Going forward, after you finish writing a post, just regenerate the static website files and the update will show up on your site.\nReferences Deploying a Hugo blog to a Tencent Cloud lightweight server ","permalink":"https://www.333rd.net/en/posts/tech/hugo-nginx-https-reverse-proxy/","summary":"\u003ch1 id=\"enabling-https-on-hugo-with-an-nginx-reverse-proxy\"\u003eEnabling HTTPS on Hugo with an Nginx Reverse Proxy\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eSure, a website works fine over HTTP, but the browser still warns that it\u0026rsquo;s not secure, which is a bit of a buzzkill. Here\u0026rsquo;s a quick rundown of how to serve your Hugo site over HTTPS by putting an Nginx reverse proxy in front of it. The content is borrowed from Sulv\u0026rsquo;s post \u003ca href=\"https://www.sulvblog.cn/posts/blog/hugo_deploy/\" title=\"Deploying a Hugo blog to a Tencent Cloud lightweight server\"\u003eDeploying a Hugo blog to a Tencent Cloud lightweight server\u003c/a\u003e\u003c/em\u003e\u003c/p\u003e","title":"Enabling HTTPS on Hugo with an Nginx Reverse Proxy"},{"content":"Header-File Design and Usage Guidelines As is well known, compared with object-oriented C++, procedural C has no concept of encapsulation, inheritance, or polymorphism, let alone interfaces. In C programs, using the principle of separate compilation, header files (.h or .hpp) serve to provide declarations and interfaces, while source files (.c) provide implementations, thereby achieving modular engineering design and ensuring high cohesion and low coupling. This is undoubtedly very important.\nPreface Preface 1. Disclaimer The content of this article is largely derived from \u0026ldquo;C — Principles, Rules, and Recommendations for Header-File Inclusion\u0026rdquo;, but that article has poor formatting, so the content has been reformatted and excerpted here.\nPreface 2. On Declarations and Definitions In C programs, using the principle of separate compilation, header files (.h or .hpp) serve to provide declarations and interfaces, while source files (.c) provide implementations. The C compiler typically treats source files as compilation units. During compilation, identifiers are validated against declarations; only at the linking stage are the actual implementations located and linked together into a single whole.\nAdditionally, to avoid ambiguity, the principle for declarations and definitions is \u0026ldquo;multiple declarations (for external variables), a single definition.\u0026rdquo; In a C project, to avoid ambiguity, there must be only one definition. Multiple declarations are permitted to satisfy the requirement of declaring before defining within the same file, or to be used in other files as compilation units.\nDefinitions typically involve memory allocation, while declarations do not;\nA definition is simultaneously a declaration, for example:\n1 int v_g; // 既是定义，也是声明；(Both a definition and a declaration) Declarations typically use the extern keyword:\n1 extern int v_g; // 使用同文件中后面的外部变量或其它文件中定义的外部变量；(Uses an external variable defined later in the same file or in another file) If an extern declaration is accompanied by initialization, it becomes a definition:\n1 extern const int vc_g = 28; // const默认为内部链接，显式声明为extern后为外部链接；(const is internal linkage by default; explicitly declaring it as extern makes it external linkage) Source files implement the definitions of variables and functions and specify their linkage scope. Header files contain the declarations of global variables, functions, data types, and macros that need to be accessible externally.\nPreface 3. Common Extension Unix compilers and linkers commonly use a \u0026ldquo;common extension\u0026rdquo; that permits multiple definitions, provided that at most one definition is initialized.\nThis approach is called a \u0026ldquo;common extension\u0026rdquo; by the ANSI C standard. Some older systems may require explicit initialization to distinguish definitions from external declarations.\nThe common extension is explained in Computer Systems: A Programmer\u0026rsquo;s Perspective as follows: among multiply-defined symbols, at most one strong symbol is allowed. Functions and initialized global variables are strong symbols; uninitialized global variables are weak symbols. The Unix linker uses the following rules to process multiply-defined symbols:\nRule 1: Multiple strong symbols are not allowed. Global variables defined within header files that are included by multiple source files will be defined multiple times (during the preprocessor stage, the header file contents are expanded into the source files). If these definitions are explicitly assigned values (initialized), this rule is violated. Rule 2: If there is one strong symbol and multiple weak symbols, the strong symbol is chosen. Rule 3: If there are multiple weak symbols, any one of them is chosen. When global variables of the same name are defined in different files (even if their types and meanings differ), the variable shares the same memory block (same address). If the variable definitions are all initialized, a \u0026ldquo;multiple definition\u0026rdquo; linker error will occur. If a variable definition is uninitialized, there will be no linker error, though a compiler warning such as size of symbol 'XXX' changed may appear if the different types result in different sizes.\nIn the worst case, compilation and linking succeed normally, but different files reading and writing the same-named global variable will interfere with each other, causing very elusive problems. This risk is especially prominent when using third-party libraries whose source code cannot be accessed.\nPreface 4. Origin of This Article C language projects typically use files to achieve modularity. The requirement for modularity is \u0026ldquo;high cohesion, low coupling\u0026rdquo;, from which a series of header-file inclusion principles, rules, and recommendations can be derived.\nI once believed that a \u0026ldquo;.c file\u0026rdquo; (unless otherwise specified, .c and .cpp files are collectively referred to as source files) should correspond to a single \u0026ldquo;.h file\u0026rdquo; (unless otherwise specified, .h and .hpp files are collectively referred to as header files), and that a source file only needs to include its own header file. If a source file uses content from other files, it should include the needed headers in its header file. This approach seemed workable, and when the project had few files, no problems were apparent. However, as the number of project files grew, I discovered a flaw in this thinking: header files including each other led to a situation where I assumed that if a macro was declared, it would take effect, but in actual testing, some declared macros failed to work after compilation. (The header files used the extern \u0026quot;C\u0026quot; idiom to prevent duplicate inclusion.)\nThis is actually an incorrect programming habit. The correct approach is: a source file\u0026rsquo;s header file should only include other necessary header files — no unnecessary headers should be included. Meanwhile, source files should include all headers they use. This way, even if a source file ends up with duplicate inclusions, it causes no harm.\nNote: The Google C++ Style Guide requires that C++ header files be Self-contained. In plain language, header files should be self-sufficient — they should work as the first header included, without depending on other headers being included first.\nFor C, header-file planning largely reflects the soundness of the overall system design. Poor header-file design is one of the root causes of excessively long compilation times.\nNote: For example, if x.h depends on y.h, and y.h depends on z.h, dependency transitivity occurs. All source files that include x.h transitively depend on z.h through y.h. If any of these three header files changes, all source files including x.h must be recompiled. Moreover, overly long dependency chains increase header-file parsing time, even though most of the content may never be used. A simple chain dependency already causes such problems — let alone the complications of diamond dependencies.\nFortunately, some well-established design methods have been developed for planning header files rationally.\nPreface 5. Table of Contents Principles\nHeader files are suitable for declaring interfaces, not for containing implementations. Header files should have a single responsibility. Header-file partitioning principles Hierarchical semantic principle and use of common headers Semantic relevance principle for header files Include order within source files should go from most specific to most general. Minimization principle: Use forward declarations and extern function declarations; avoid including headers whenever possible. Rules\nEvery .c file should have a corresponding .h file that declares the interfaces intended for external use. Circular header-file dependencies are forbidden. Reduce nesting and cross-references, and try to avoid ordering dependencies. Prefer including headers in source files rather than in header files. .c/.h files must not include unused headers. Header files should be self-contained and self-sufficient. Which headers a header file includes should depend only on itself, not on the source files that include it. Always write include guards (#define guards). Defining variables in header files is forbidden. Other .c files\u0026rsquo; interfaces may only be used by including their header files; using extern to access external functions or variables directly in .c files is forbidden. Including header files inside extern \u0026quot;C\u0026quot; blocks is forbidden. Recommendations\nA module typically contains multiple .c files, which should be placed in the same directory. The directory name should be the module name. For the convenience of external users, it is recommended that each module provide a single .h file named after the directory. If a module contains multiple sub-modules, it is recommended that each sub-module provide a public .h file named after the sub-module. Header files should not use non-standard file extensions such as .inc. Within a single product, the header-file inclusion order should be consistent. 1. Principles Header files are suitable for declaring interfaces, not for containing implementations. Header files serve as the external interface of a module or unit. They should contain declarations intended for external use, such as function declarations, macro definitions, type definitions, and so on.\nDeclarations of internally used functions (equivalent to private methods in a class) should not be placed in header files.\nInternally used macros, enumerations, and struct definitions should not be placed in header files.\nVariable definitions should not be placed in header files; they should be placed in source files.\nVariable declarations should, as much as possible, not be placed in header files — that is, global variables should generally not be used as interfaces. Variables are internal implementation details of a module or unit; they should not be directly exposed externally by declaring them in header files. Instead, they should be exposed through function interfaces. Even when global variables must be used, they should be defined only in source files, with the header file merely declaring the variable as global.\nHeader files must not define variables or functions. They may only contain macros, types (typedef/struct/union/enum, etc.), and declarations of variables and functions. In special cases, basic-type global variables may be declared extern in a header file, allowing source files to access the global variable by including that header. However, header files should not use extern to declare custom-type (e.g., struct) global variables, as this would force source files that do not need to access the variable to include the header file that defines the custom type.\n「Principles for Using Global Variables」\nIf a global variable is accessed only within a single source file, it should be changed to a static global variable within that file;\nIf a global variable is accessed only by a single function, it should be changed to a static local variable within that function;\nAvoid using extern to declare global variables; it is better to provide functions to access these variables. Directly exposing global variables is unsafe, as external users may not fully understand the meaning of these variables.\nWhen designing and calling functions that access dynamic global variables, static global variables, or static local variables, reentrancy issues must be considered.\n","permalink":"https://www.333rd.net/en/posts/read/header-file-design-guidelines/","summary":"\u003ch1 id=\"header-file-design-and-usage-guidelines\"\u003eHeader-File Design and Usage Guidelines\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eAs is well known, compared with object-oriented C++, procedural C has no concept of encapsulation, inheritance, or polymorphism, let alone interfaces. In C programs, using the principle of separate compilation, header files (.h or .hpp) serve to provide declarations and interfaces, while source files (.c) provide implementations, thereby achieving modular engineering design and ensuring high cohesion and low coupling. This is undoubtedly very important.\u003c/em\u003e\u003c/p\u003e\n\u003ch2 id=\"preface\"\u003ePreface\u003c/h2\u003e\n\u003ch3 id=\"preface-1-disclaimer\"\u003ePreface 1. Disclaimer\u003c/h3\u003e\n\u003cp\u003e\u003cstrong\u003eThe content of this article is largely derived from \u0026ldquo;\u003ca href=\"https://www.toutiao.com/article/7073267570652054049/\" title=\"C — Principles, Rules, and Recommendations for Header-File Inclusion\"\u003eC — Principles, Rules, and Recommendations for Header-File Inclusion\u003c/a\u003e\u0026rdquo;, but that article has poor formatting, so the content has been reformatted and excerpted here.\u003c/strong\u003e\u003c/p\u003e","title":"Header-File Design and Usage Guidelines"},{"content":"Modulo and Remainder A brief analysis of these two operations.\nFor integers $a$ and $b$, the modulo or remainder operation proceeds in two steps:\nCompute the integer quotient: c=a/b Compute the modulus or remainder: r=a-(c*b) The difference lies in the first step.\nThe remainder operation truncates the quotient toward zero — so $3.3$ and $-3.3$ become $3$ and $-3$. The modulo operation truncates the quotient toward negative infinity — so $3.3$ and $-3.3$ become $3$ and $-4$. For example, 4/(-3) ≈ -1.33:\nWhen computing the remainder, the quotient is truncated toward zero to $-1$. When computing the modulo, the quotient is truncated toward negative infinity to $-2$. Therefore:\n1 2 4 rem (-3) = 1 4 mod (-3) = -2 ","permalink":"https://www.333rd.net/en/posts/read/modulo-and-remainder/","summary":"\u003ch1 id=\"modulo-and-remainder\"\u003eModulo and Remainder\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eA brief analysis of these two operations.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003eFor integers $a$ and $b$, the modulo or remainder operation proceeds in two steps:\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003eCompute the integer quotient: \u003ccode\u003ec=a/b\u003c/code\u003e\u003c/li\u003e\n\u003cli\u003eCompute the modulus or remainder: \u003ccode\u003er=a-(c*b)\u003c/code\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003eThe difference lies in the first step.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eThe remainder operation truncates the quotient toward zero — so $3.3$ and $-3.3$ become $3$ and $-3$.\u003c/li\u003e\n\u003cli\u003eThe modulo operation truncates the quotient toward negative infinity — so $3.3$ and $-3.3$ become $3$ and $-4$.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eFor example, \u003cstrong\u003e4/(-3) ≈ -1.33\u003c/strong\u003e:\u003c/p\u003e","title":"Modulo and Remainder"},{"content":"C++ Polymorphism Polymorphism, as the name suggests — many forms. In plain terms, it means \u0026ldquo;one interface, multiple implementations.\u0026rdquo; (At the time of writing, my knowledge framework was not yet fully developed; please feel free to point out any omissions or errors.)\n1. Why Do We Need Polymorphism? In engineering, we frequently encounter ever-changing requirements. If we design a separate interface and logic for every requirement, it would inevitably lead to a large amount of code redundancy and reduced maintainability.\nConsider the early days of mobile phone charging interfaces. At the beginning of the 21st century, every feature-phone manufacturer implemented proprietary charging interfaces. When you went out, no charger was as useful as a universal charger — it could charge your own device as well as someone else\u0026rsquo;s. In this case, the universal charger used a single interface to satisfy multiple charging needs, undoubtedly solving our charging problem efficiently.\nNowadays, although charging interfaces have largely been unified into the Type-C interface, the issue of incompatible fast-charging protocols still exists. While a single charger works everywhere when you\u0026rsquo;re out and about, the charging speed without fast charging is painfully slow. Naturally, devices and chargers supporting the PD fast-charging protocol become the preferred choice — they avoid the overhead of carrying an extra set of chargers while also solving the slow charging speed problem.\nIt is clear that a unified interface is extremely necessary. Programs should be the same way — this not only accommodates changing requirements but also avoids redundancy caused by duplicate code, achieving effectiveness through a constant approach to ever-changing situations.\n2. Programming Implementation of Polymorphism Let\u0026rsquo;s get straight to the code (source: \u0026ldquo;C++ Polymorphism | Runoob Tutorial\u0026rdquo;)\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 #include \u0026lt;iostream\u0026gt; using namespace std; class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } int area() { cout \u0026lt;\u0026lt; \u0026#34;Parent class area :\u0026#34; \u0026lt;\u0026lt;endl; return 0; } }; class Rectangle: public Shape{ public: Rectangle( int a=0, int b=0):Shape(a, b) { } int area () { cout \u0026lt;\u0026lt; \u0026#34;Rectangle class area :\u0026#34; \u0026lt;\u0026lt;endl; return (width * height); } }; class Triangle: public Shape{ public: Triangle( int a=0, int b=0):Shape(a, b) { } int area () { cout \u0026lt;\u0026lt; \u0026#34;Triangle class area :\u0026#34; \u0026lt;\u0026lt;endl; return (width * height / 2); } }; // Main function of the program int main( ) { Shape *shape; Rectangle rec(10,7); Triangle tri(10,5); // Store the address of the rectangle shape = \u0026amp;rec; // Call the rectangle\u0026#39;s area function shape-\u0026gt;area(); // Store the address of the triangle shape = \u0026amp;tri; // Call the triangle\u0026#39;s area function shape-\u0026gt;area(); return 0; } When the above code is compiled and executed, it produces the following output:\n1 2 Parent class area : Parent class area : Our original intention was to use the base class as a unified interface, deriving different subclasses for different requirements, pointing a base class pointer to an instantiated subclass object, and calling the subclass object\u0026rsquo;s method to produce the corresponding output. However, the actual output is the content of the base class method.\nThe program produces no errors, so why is the output different from what we expected? Because during compilation, the area method has already been set by the compiler to the version in the base class — this is called static linking, also known as early binding. At this point, the program determines the method to call based on the pointer\u0026rsquo;s type. This runs completely counter to our goal, so it naturally won\u0026rsquo;t work.\nSo how should we achieve this goal? We need to introduce the virtual keyword. Place the virtual keyword before the area method declaration in the base class Shape, as follows:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 class Shape { protected: int width, height; public: Shape( int a=0, int b=0) { width = a; height = b; } virtual int area() { cout \u0026lt;\u0026lt; \u0026#34;Parent class area :\u0026#34; \u0026lt;\u0026lt;endl; return 0; } }; At this point, the compilation and execution results are as follows:\n1 2 Rectangle class area : Triangle class area : As we can see, the program has achieved its goal: executing the methods of subclass objects and producing the corresponding output. Now we can use the base class as an interface, deriving subclasses for various different requirements to implement corresponding solutions.\nAt this point, with the virtual keyword, the compiler looks at the pointer\u0026rsquo;s content rather than its type. Therefore, since the addresses of tri and rec class objects are stored in *shape, their respective area() functions will be called.\nAs you can see, each subclass has its own independent implementation of the area() function. This is the general way polymorphism is used. With polymorphism, you can have multiple different classes, all with functions of the same name but with different implementations — the function parameters can even be the same.\nA virtual function is a function declared with the virtual keyword in the base class. When redefining a virtual function defined in the base class in a derived class, it tells the compiler not to statically link to that function. What we want is to be able to select the function to call based on the object type at any point during program execution — this operation is called dynamic linking, or late binding.\n3. From Virtual Functions to Polymorphism 3.1. \u0026ldquo;Overriding\u0026rdquo; Virtual Functions Earlier, we used the virtual keyword to modify the area method of the base class Shape. At this point, this method can be called a virtual function. And \u0026ldquo;virtual functions\u0026rdquo; are precisely one of the prerequisites for implementing polymorphism.\nIt is necessary here to discuss function overriding, overloading, and redefining (hiding) to lay the groundwork for subsequent content:\nOverloading: When function names are the same but parameter lists differ, and they are in the same scope, overloading is formed. The return value can be the same or different. Overriding: This is like a substitution — in the scope of a derived class, the overridden virtual function must have the same function name, parameter list, and return value as the base class virtual function (with exceptions for covariant return types and destructors). Redefining: This is name hiding in inheritance. When a function in a derived class has the same name as a function in the base class, regardless of whether the parameters are the same, as long as the function is not virtual, it does not constitute overriding but rather redefining (name hiding). Implement virtual functions as interfaces in the base class, \u0026ldquo;override\u0026rdquo; those functions in derived classes, and then you can call the overridden virtual functions through base class pointers or references to achieve polymorphism.\nWhen overriding a base class virtual function, even if the derived class\u0026rsquo;s virtual function does not use the virtual keyword, it can still constitute overriding (because after inheritance, the base class\u0026rsquo;s virtual function retains its virtual attribute in the derived class). However, this coding style is not standard and is not recommended.\n3.2. Exceptions to Virtual Function Overriding However, due to practical needs, there are exceptions to virtual function overriding:\nCovariant Return Types\nWhen a derived class overrides a base class virtual function, and the only difference from the base class virtual function is the return type — where the base class virtual function returns a pointer or reference to the base class object and the derived class virtual function returns a pointer or reference to the derived class object — this is called covariance. The following example is quoted from \u0026ldquo;C++ Polymorphism\u0026rdquo;:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 #include \u0026#34;iostream\u0026#34; using namespace std; class Person // Base class { public: virtual Person* pointer(void) // Returns Person* { cout \u0026lt;\u0026lt; \u0026#34;Person* pointer(void)\u0026#34; \u0026lt;\u0026lt; endl; return new Person; } }; class Student : public Person // Derived class inherits from base class { public: virtual Student* pointer(void) // Returns Student* { cout \u0026lt;\u0026lt; \u0026#34;Student* pointer(void)\u0026#34; \u0026lt;\u0026lt; endl; return new Student; } }; int main(void) { Person p, *ptr; Student s; ptr = \u0026amp;p; ptr-\u0026gt;pointer(); // Calls base class method ptr = \u0026amp;s; ptr-\u0026gt;pointer(); // Calls derived class method return 0; } // Output is as follows: // Person* pointer(void) // Student* pointer(void) As we can see, although the pointer() function returns different types, polymorphism is still achieved.\nDestructor Overriding\nBecause destructors have naming requirements, from a code perspective, the destructor names of derived and base classes are different, which would seem to break the requirements of polymorphism. However, the compiler performs special processing on all destructor names — after compilation, all destructor names are uniformly treated as destructor. Therefore, as long as the base class destructor is a virtual function, any derived class destructor will constitute overriding with the base class destructor simply by being defined.\nYou can easily see the existence of virtual destructors in code with inheritance relationships. A base class virtual destructor ensures that when a derived class object pointed to by a base class pointer is destroyed, destruction proceeds layer by layer from inside to outside until the base class object is fully destroyed, avoiding the erroneous destruction that would occur if only the base class destructor were called and the derived class destructor were missed. For example:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 #include \u0026#34;iostream\u0026#34; using namespace std; class Parent { public: Parent(void) { cout \u0026lt;\u0026lt; \u0026#34;Parent has been created!\u0026#34; \u0026lt;\u0026lt; endl; } ~Parent() { cout \u0026lt;\u0026lt; \u0026#34;Parent has been deleted!\u0026#34; \u0026lt;\u0026lt; endl; } }; class Child : public Parent { public: Child(void) { cout \u0026lt;\u0026lt; \u0026#34;Child has been created!\u0026#34; \u0026lt;\u0026lt; endl; } ~Child() { cout \u0026lt;\u0026lt; \u0026#34;Child has been deleted!\u0026#34; \u0026lt;\u0026lt; endl; } }; int main(void) { Parent* p = new Parent; cout \u0026lt;\u0026lt; \u0026#34;-------------------------\u0026#34; \u0026lt;\u0026lt; endl; Parent* c = new Child; cout \u0026lt;\u0026lt; \u0026#34;-------------------------\u0026#34; \u0026lt;\u0026lt; endl; delete p; cout \u0026lt;\u0026lt; \u0026#34;-------------------------\u0026#34; \u0026lt;\u0026lt; endl; delete c; return 0; } // Output is as follows: // Parent has been created! // ------------------------- // Parent has been created! // Child has been created! // ------------------------- // Parent has been deleted! // ------------------------- // Parent has been deleted! When releasing the derived class object, only the base class object was released, causing a memory leak. The base class destructor should be decorated with the virtual keyword to make it a virtual destructor. At this point, even without adding virtual, the derived class destructor inherits the base class\u0026rsquo;s virtual function attribute and becomes a virtual destructor (in practice, it is recommended to explicitly add virtual to declare it as a virtual function). The output is as follows, showing complete destruction with no memory leak.\n1 2 3 4 5 6 7 8 9 10 // Output is as follows: // Parent has been created! // ------------------------- // Parent has been created! // Child has been created! // ------------------------- // Parent has been deleted! // ------------------------- // Child has been deleted! // Parent has been deleted! But are virtual destructors always required? Not necessarily. Here we categorize situations where virtual destructors are not needed:\nWhen there is no need to use a base class pointer to point to derived class objects — that is, when the base class is not used as an interface — a virtual destructor is not needed. Because derived class objects undergo destruction from inside to outside — first destroying the derived class, then the base class. At this point, destroying the derived class object allows full destruction from inside to outside without omissions. Example:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 #include \u0026#34;iostream\u0026#34; using namespace std; class Parent { public: Parent(void) { cout \u0026lt;\u0026lt; \u0026#34;Parent has been created!\u0026#34; \u0026lt;\u0026lt; endl; } ~Parent() { cout \u0026lt;\u0026lt; \u0026#34;Parent has been deleted!\u0026#34; \u0026lt;\u0026lt; endl; } }; class Child : public Parent { public: Child(void) { cout \u0026lt;\u0026lt; \u0026#34;Child has been created!\u0026#34; \u0026lt;\u0026lt; endl; } ~Child() { cout \u0026lt;\u0026lt; \u0026#34;Child has been deleted!\u0026#34; \u0026lt;\u0026lt; endl; } }; int main(void) { Parent p; Child c; return 0; } // Output is as follows: // Parent has been created! // Parent has been created! // Child has been created! // Child has been deleted! // Parent has been deleted! // Parent has been deleted! Analysis: First, the base class object is instantiated, executing the base class constructor, then the derived class object is instantiated. Because during inheritance, constructors execute from outside to inside (opposite to destructors), the base class constructor executes first, followed by the derived class constructor. Then the derived class object and the base class object are destructed in order from inside to outside. As for the final base class destructor, it is determined by the stack-based execution of the program — last in, first out (data structure knowledge).\nWhen all derived classes do not perform resource cleanup or other closing operations in their destructors, a virtual destructor is not needed. This is easy to understand: the purpose of destructors is to perform closing work when an object ends, such as processing data and releasing resources. If these operations are not involved, whether the destructor is correct or not does not affect program execution.\nClasses that will not be publicly inherited do not need virtual destructors. Because private or protected inheritance prevents base class pointers from pointing to derived classes in non-friend functions and classes.\nIn the above situations, it is acceptable not to declare destructors as virtual. Moreover, virtual functions can only determine the object type at runtime, requiring dynamic linking, which has greater overhead and lower efficiency compared to static linking. However, it is difficult to guarantee that the above situations are 100% followed, so in actual development, a comprehensive approach is needed — there is no universal rule that applies everywhere.\n3.3. Notes on Virtual Functions The compiler does not allow setting constructors as virtual functions. Constructors are automatically called when objects are created and cannot be called through base class pointers or references, so constructors cannot be virtual functions.\nAdditionally, each virtual function corresponds to a virtual function table (vtable), and this vtable is stored in the object\u0026rsquo;s memory space. If a constructor were virtual, it would need to be called through the vtable, but the object has not yet been instantiated — meaning the memory space does not even exist, let alone being able to call through the vtable. Therefore, constructors cannot be virtual functions.\nThe virtual keyword only needs to be added at the virtual function\u0026rsquo;s declaration; it can be added or omitted at the function definition.\nOnly class member functions can be declared as virtual functions. Friend functions are not class member functions, so they cannot be declared as virtual functions.\nStatic member functions cannot be virtual functions. Static functions are bound at compile time, while virtual functions can only be determined at runtime. Furthermore, virtual functions have a hidden this pointer that belongs to the instantiated object and can only be called through an object; static member functions do not have a this pointer — they belong to the class, not to specific objects, and cannot be called through an object. Therefore, the two cannot coexist.\nInline functions cannot be virtual functions. Inlining expands function content at the function call site during compilation, trading space for time to improve runtime efficiency — it is static; while virtual functions are called dynamically, the caller is unknown at compile time, so they cannot be expanded inline — the compiler will ignore the inline request.\nIn the section on \u0026ldquo;Overriding\u0026rdquo; Virtual Functions, I mentioned redefining (name hiding). Here is a more detailed breakdown:\nIf a derived class function has the same name as a base class function and the parameters are different, then regardless of whether the virtual keyword is present, the base class function will be hidden (note: this is not equivalent to overloading). If a derived class function has the same name as a base class function and the parameters are the same, but the base class function does not have the virtual keyword, the base class function is hidden (note: this is not equivalent to overriding because the return value is not checked); if the base class function has the virtual keyword, the compiler considers this an override of the base class virtual function, and if the return type differs from the base class function, it will report an error (with the exception of covariant return types). If a derived class overrides a base class virtual function, then all other same-name functions in the derived class will be hidden, regardless of whether they are virtual functions. 3.4. Pure Virtual Functions Adding \u0026ldquo;= 0\u0026rdquo; after a virtual function makes it a pure virtual function. A class containing pure virtual functions is called an abstract class (also called an interface class), and abstract classes cannot be instantiated. Derived classes also cannot be instantiated after inheritance — only by overriding the pure virtual functions can derived classes be instantiated. Pure virtual functions mandate that derived classes must override them, further emphasizing the characteristic of interface inheritance.\n3.5. Extended Content on Virtual Functions Here we introduce two additional keywords that may be encountered; awareness is sufficient:\nfinal: When applied to a virtual function, it indicates that the virtual function cannot be inherited further. In plain terms, once the final keyword is added in the base class, it cannot be overridden in any derived class. override: Checks whether a derived class virtual function actually overrides a base class virtual function. If it does not, the compiler reports an error. This is exactly the opposite of the final keyword — it is used in derived classes. The significance of virtual functions for inheritance:\nImplementation Inheritance\nInheritance of regular functions is implementation inheritance — derived classes inherit the implementation of base class functions. Interface Inheritance\nVirtual functions are interface inheritance — derived classes inherit the interface of base class virtual functions, with the purpose of overriding them to achieve polymorphism. In particular, the creation of abstract classes forces derived classes to override base class pure virtual functions; otherwise, derived classes cannot be instantiated, greatly diminishing the class\u0026rsquo;s functionality. The virtual keyword can not only modify functions to make them virtual functions, but it can also be used to modify inheritance relationships to achieve virtual inheritance. Since I have not yet encountered a need for virtual inheritance, I will not elaborate on this topic here, leaving it for future discussion.\n4. Underlying Principles of Polymorphism The following section draws from \u0026ldquo;C++ — Understanding the Implementation of Polymorphism in One Article\u0026rdquo;:\nThe key to \u0026ldquo;polymorphism\u0026rdquo; lies in the fact that when calling a virtual function through a base class pointer or reference, the compiler cannot determine at compile time whether it is calling the base class or derived class function — this can only be determined at runtime. So what happens if we use sizeof to output the size of a class with virtual functions versus one without?\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 class A { public: int i; virtual void Print() { } // Virtual function }; class B { public: int n; void Print() { } }; int main() { cout \u0026lt;\u0026lt; sizeof(A) \u0026lt;\u0026lt; \u0026#34;,\u0026#34;\u0026lt;\u0026lt; sizeof(B); return 0; } // On a 64-bit system, the output is: // 16,4 // // Here we explain the difference of 12: // On a 64-bit operating system, pointer size is 8 bytes; due to memory alignment, // even if less than 8 bytes, 8 bytes of space is still allocated; // So the class object size is two vptr sizes: 2*8=16, not 4+8=12, // hence the 8-byte difference mentioned later. From the above results, we can see that the class with virtual functions has an extra 8 bytes. On a 64-bit machine, a pointer type is exactly 8 bytes — what is the purpose of these extra 8 bytes?\n4.1. Virtual Function Table (Vtable) Every class with \u0026ldquo;virtual functions\u0026rdquo; (or a derived class of such a class) has a virtual function table (vtable), and every object of that class contains a pointer to the virtual function table. The vtable lists the addresses of the class\u0026rsquo;s \u0026ldquo;virtual functions.\u0026rdquo; The extra 8 bytes are used to store the address of the virtual function table.\nFirst, as is customary — here is the code:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // Base class class Base { public: int i; virtual void Print() { } // Virtual function }; // Derived class class Derived : public Base { public: int n; virtual void Print() { } // Virtual function }; In the above, the Derived class inherits from the Base class, and both classes have \u0026ldquo;virtual functions.\u0026rdquo; Their vtable structure can be understood as follows:\nVirtual Function Table Polymorphic function call statements are compiled into a series of instructions that look up the virtual function address in the vtable using the address stored in the object pointed to by (or referenced by) the base class pointer, and then invoke the virtual function.\n4.2. Proving the Role of the Virtual Function Table Pointer Earlier, we used the sizeof operator to calculate the size of a class with virtual functions and found an extra 8 bytes (on 64-bit systems). These extra 8 bytes are the pointer to the virtual function table. The vtable lists the addresses of the class\u0026rsquo;s \u0026ldquo;virtual functions.\u0026rdquo;\nThe following code example proves the role of the \u0026ldquo;virtual function table pointer\u0026rdquo;:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 // Base class class A { public: virtual void Func() // Virtual function { cout \u0026lt;\u0026lt; \u0026#34;A::Func\u0026#34; \u0026lt;\u0026lt; endl; } }; // Derived class class B : public A { public: virtual void Func() // Virtual function { cout \u0026lt;\u0026lt; \u0026#34;B::Func\u0026#34; \u0026lt;\u0026lt; endl; } }; int main() { A a; A * pa = new B(); pa-\u0026gt;Func(); // Polymorphism // On 64-bit systems, pointers are 8 bytes int * p1 = (int *) \u0026amp; a; int * p2 = (int *) pa; * p2 = * p1; pa-\u0026gt;Func(); return 0; } // Output is as follows: // B::Func // A::Func Analysis:\nOn lines 25-26, the pa pointer points to a B class object, so pa-\u0026gt;Func() calls the B class object\u0026rsquo;s virtual function Func(), outputting B::Func; Lines 29-30 are designed to make p1 point to the first 8 bytes of the A class — the \u0026ldquo;virtual function table pointer\u0026rdquo; — and p2 point to the first 8 bytes of the B class — the \u0026ldquo;virtual function table pointer\u0026rdquo;; Line 32 assigns the A class\u0026rsquo;s \u0026ldquo;virtual function table pointer\u0026rdquo; to the B class\u0026rsquo;s \u0026ldquo;virtual function table pointer\u0026rdquo;, effectively replacing the B class\u0026rsquo;s \u0026ldquo;virtual function table pointer\u0026rdquo; with the A class\u0026rsquo;s \u0026ldquo;virtual function table pointer\u0026rdquo;; Due to the effect of line 32, line 33 calls the A class\u0026rsquo;s virtual function Func(), outputting A::Func. Through the above code and explanation, we can effectively prove the role of the \u0026ldquo;virtual function table pointer.\u0026rdquo;\n4.3. Summary of Polymorphism Implementation Principles The \u0026ldquo;virtual function table pointer\u0026rdquo; points to the \u0026ldquo;virtual function table,\u0026rdquo; and the vtable stores the addresses of the class\u0026rsquo;s \u0026ldquo;virtual functions.\u0026rdquo;\nWhen a virtual function is declared in a class, the compiler generates a virtual function table within the class (an array where each element is the entry address of a virtual function). The vtable is a data structure storing pointers to class member functions, automatically generated and maintained by the compiler. Member functions decorated with virtual are placed into the vtable by the compiler. If a derived class overrides a base class virtual function, the derived class virtual function\u0026rsquo;s entry address replaces the base class virtual function\u0026rsquo;s entry address in the table; otherwise, the base class virtual function\u0026rsquo;s entry address is used.\nThe index (subscript) of a base class virtual function in the vtable is fixed and does not change as the inheritance hierarchy increases. New virtual functions added by derived classes are placed at the end of the vtable.\nWhen virtual functions exist, every object contains a pointer to the virtual function table (the vptr, which is always located at the beginning of the object). This pointer points to the vtable of the class to which the object belongs. During a runtime call, the vptr is initialized based on the object\u0026rsquo;s type, allowing the vptr to correctly point to the vtable of the class it belongs to. By looking up the table to index the correct virtual function for invocation, the characteristics of polymorphism are realized.\nIt can be said that the vtable pointer enables the realization of polymorphism, and the correct initialization of the vtable pointer determines whether the implementation works properly. In other words, before the vtable pointer is correctly initialized, we cannot call virtual functions.\n5. Summary A base class pointer can behave in the manner of the base class or in the manner of a derived class — it has multiple forms, or multiple ways of expressing itself. We call this phenomenon polymorphism.\nSearching for the Chinese keyword \u0026ldquo;C++ Polymorphism\u0026rdquo; reveals the following conditions for forming polymorphism:\nAn inheritance relationship must exist; The inheritance relationship must have virtual functions with the same name, and they must be in an overriding relationship; A pointer or reference of the base class type must exist, through which virtual functions are referenced or called. By default, through a base class pointer, you can only access the derived class\u0026rsquo;s member variables, but cannot access the derived class\u0026rsquo;s member functions. To resolve this awkward limitation and allow base class pointers to access derived class member functions, C++ introduced virtual functions. In C++, the sole purpose of virtual functions is to form polymorphism.\nC++ provides polymorphism so that base class pointers can perform \u0026ldquo;comprehensive\u0026rdquo; access to the member variables and member functions of all derived classes (including direct and indirect derived classes), especially member functions. Without polymorphism, we can only access member variables.\nIn practical terms, for example, in a game where data needs to be updated after each round: without polymorphism, you would have to call each object\u0026rsquo;s update method individually; but with polymorphism, you can call the update method of derived class objects through base class pointers within a loop, reducing code volume while optimizing code logic.\nFurthermore, polymorphism, based on dynamic linking, allows old code to call new code, improving code reusability and enabling backward-compatible extension. Therefore, it is commonly used in framework development.\nPolymorphism has many other uses as well, which will not be enumerated here.\nHowever, polymorphism is not a universal solution — its high flexibility comes at the cost of efficiency, and polymorphic calls are slower than regular member functions using static linking. Therefore, for the sake of efficiency, there is no need to declare all member functions as virtual functions.\nThis concludes the discussion on polymorphism. Thanks to the articles written by fellow developers that provided ideas for this piece. Let us all make progress together. Keep going!\nReferences Thanks to the following individuals for sharing their knowledge — wisdom shines through dissemination.\nC++ Polymorphism | Runoob Tutorial C++: Polymorphism (Overriding, Polymorphism Principles, Single and Multiple Inheritance Virtual Function Tables) C++ Virtual Detailed Explanation C++ Polymorphism Dynamic Linking and Static Linking Is virtual in Destructors Necessary? C++ Virtual Function Notes Virtual Function Notes C++ — Understanding the Implementation of Polymorphism in One Article C++ Polymorphism Implementation Principles C++ Polymorphism and Virtual Functions ","permalink":"https://www.333rd.net/en/posts/read/cpp-polymorphism/","summary":"\u003ch1 id=\"c-polymorphism\"\u003eC++ Polymorphism\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003ePolymorphism, as the name suggests — many forms. In plain terms, it means \u0026ldquo;one interface, multiple implementations.\u0026rdquo; (At the time of writing, my knowledge framework was not yet fully developed; please feel free to point out any omissions or errors.)\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-why-do-we-need-polymorphism\"\u003e1. Why Do We Need Polymorphism?\u003c/h2\u003e\n\u003cp\u003eIn engineering, we frequently encounter ever-changing requirements. If we design a separate interface and logic for every requirement, it would inevitably lead to a large amount of code redundancy and reduced maintainability.\u003c/p\u003e","title":"C++ Polymorphism"},{"content":"ST7735 Display Orientation and Color Configuration The ST7735 is commonly used as the driver chip for small TFT screens. This article provides guidance on configuring the refresh direction (rotating or flipping the screen) and the RGB or BGR color format.\nAnalysis This configuration corresponds to register address 0x36 of the driver chip, which is covered on page 110 of the datasheet.\nThe commonly used initialization values are as follows (the refresh directions have not been strictly tested — please try them out yourself!):\nColor format \\ Refresh direction Portrait Landscape RGB 0x00, 0xC0 0xA0, 0x60, 0x70 BGR 0x08, 0xC8 0xA8, 0x68, 0x78 I\u0026rsquo;d also like to recommend an SPI screen library for the ESP32 implemented in pure C: esp-idf-ili9340.\nReferences ST7735 Rotation at Any Angle (90/180/360) TFT_LCD Color Screen ST7735S Driver: Horizontal Image Flip esp-idf-ili9340 ","permalink":"https://www.333rd.net/en/posts/tech/st7735-display-orientation-and-color/","summary":"\u003ch1 id=\"st7735-display-orientation-and-color-configuration\"\u003eST7735 Display Orientation and Color Configuration\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThe ST7735 is commonly used as the driver chip for small TFT screens. This article provides guidance on configuring the refresh direction (rotating or flipping the screen) and the RGB or BGR color format.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"analysis\"\u003eAnalysis\u003c/h2\u003e\n\u003cp\u003eThis configuration corresponds to register address \u003ccode\u003e0x36\u003c/code\u003e of the driver chip, which is covered on \u003cstrong\u003epage 110\u003c/strong\u003e of the \u003ca href=\"https://www.displayfuture.com/Display/datasheet/controller/ST7735.pdf\" title=\"Datasheet\"\u003edatasheet\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003eThe commonly used initialization values are as follows (\u003cstrong\u003ethe refresh directions have not been strictly tested\u003c/strong\u003e — please try them out yourself!):\u003c/p\u003e","title":"ST7735 Display Orientation and Color Configuration"},{"content":"Developing ESP32 with VSCode I stepped on the pitfalls so you do not have to — consider this a ladder to help you climb out.\n1. Avoid pitfalls when installing the VSCode extension I will skip the installation steps and go straight to the official getting-started guides: VS Code IDE Quick Start, Quick Installation Guide.\nBut watch out for the following points:\nIf you have enough space on your C drive, do not bother configuring things manually — let the extension install everything to the C drive automatically; save yourself the hassle. As long as either the code or the developer can run, it is fine. When installing, do not pick Github for Select download server; it is slow in China. The extension uses pip during installation, but pip\u0026rsquo;s official index is unusable in China, so you need to switch to a mirror manually. The command: 1 2 pip install pip -U pip config set global.index-url https://pypi.tuna.tsinghua.edu.cn/simple An error interrupts the extension installation and you need to reinstall, but the previously installed files remain, causing a \u0026ldquo;file exists\u0026rdquo; error. Go to the user directory, i.e. %HOMEPATH%\\esp, and delete the esp-idf folder. Reinstall and it succeeds in one go. If you use it on multiple devices and have VSCode sync enabled, be sure to enable ignore sync for the 6 configuration parameters in the extension\u0026rsquo;s settings. For the parameters and reasons, see the Issue I filed: Extension config modified incorrectly due to VSCode sync. 2. Usage notes Just read the official docs: ESP-IDF VS Code extension tutorial.\n","permalink":"https://www.333rd.net/en/posts/tech/vscode-esp32-development/","summary":"\u003ch1 id=\"developing-esp32-with-vscode\"\u003eDeveloping ESP32 with VSCode\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI stepped on the pitfalls so you do not have to — consider this a ladder to help you climb out.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-avoid-pitfalls-when-installing-the-vscode-extension\"\u003e1. Avoid pitfalls when installing the VSCode extension\u003c/h2\u003e\n\u003cp\u003eI will skip the installation steps and go straight to the official getting-started guides: \u003ca href=\"https://docs.espressif.com/projects/esp-idf/zh_CN/latest/esp32/get-started/vscode-setup.html\" title=\"VS Code IDE Quick Start\"\u003eVS Code IDE Quick Start\u003c/a\u003e, \u003ca href=\"https://github.com/espressif/vscode-esp-idf-extension/blob/master/docs/tutorial/install.md\" title=\"Quick Installation Guide\"\u003eQuick Installation Guide\u003c/a\u003e.\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003eBut watch out for the following points:\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eIf you have enough space on your C drive, do not bother configuring things manually — let the extension install everything to the C drive automatically; save yourself the hassle. \u003cdel\u003eAs long as either the code or the developer can run, it is fine.\u003c/del\u003e\u003c/li\u003e\n\u003cli\u003eWhen installing, do not pick Github for \u003ccode\u003eSelect download server\u003c/code\u003e; it is slow in China.\u003c/li\u003e\n\u003cli\u003eThe extension uses \u003ccode\u003epip\u003c/code\u003e during installation, but pip\u0026rsquo;s official index is unusable in China, so you need to switch to a mirror manually. The command:\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epip install pip -U\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003epip config \u003cspan class=\"nb\"\u003eset\u003c/span\u003e global.index-url https://pypi.tuna.tsinghua.edu.cn/simple\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cul\u003e\n\u003cli\u003eAn error interrupts the extension installation and you need to reinstall, but the previously installed files remain, causing a \u0026ldquo;\u003cstrong\u003efile exists\u003c/strong\u003e\u0026rdquo; error. Go to the user directory, i.e. \u003ccode\u003e%HOMEPATH%\\esp\u003c/code\u003e, and delete the \u003ccode\u003eesp-idf\u003c/code\u003e folder. Reinstall and it succeeds in one go.\u003c/li\u003e\n\u003cli\u003eIf you use it on multiple devices and have VSCode sync enabled, be sure to enable \u003cstrong\u003eignore sync\u003c/strong\u003e for the 6 configuration parameters in the extension\u0026rsquo;s settings. For the parameters and reasons, see the \u003ccode\u003eIssue\u003c/code\u003e I filed: \u003ca href=\"https://github.com/espressif/vscode-esp-idf-extension/issues/676\" title=\"Extension config modified incorrectly due to VSCode sync.\"\u003eExtension config modified incorrectly due to VSCode sync.\u003c/a\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch2 id=\"2-usage-notes\"\u003e2. Usage notes\u003c/h2\u003e\n\u003cp\u003eJust read the official docs: \u003ca href=\"https://github.com/espressif/vscode-esp-idf-extension/blob/master/docs/tutorial/toc.md\" title=\"ESP-IDF VS Code extension tutorial\"\u003eESP-IDF VS Code extension tutorial\u003c/a\u003e.\u003c/p\u003e","title":"Developing ESP32 with VSCode"},{"content":"Creating a Microsoft OneDrive API Compared with the shared API resources built into Rclone, a self-built private API can bring a better experience. If you need to use a private API, you should provide the tokens.\nSteps 1. Get the client_id Go to Microsoft Azure app registrations and click New registration.\nApp registration Configure as shown in the figure; for the Redirect URI, fill in http://localhost.\nRegister an application After the creation succeeds, you will see the following content, in which the Application (client) ID is the client_id.\nclient_id 2. Get the client_secret Add a client secret in the order shown in the figure; the expiry period is currently at most two years.\nAdd a client secret Then you can see the client_secret. Except for right after it is created, the client secret value cannot be viewed again afterwards. Be sure to save the secret at creation time before leaving the page.\nclient_secret 3. Set API Permissions Enable the corresponding API permissions as shown below.\nRequest API permissions The final result is as shown; check it against the figure. At this point, the creation and configuration of the Microsoft OneDrive API is complete.\nConfirm API permissions References Connect OneDrive Using a Self-built API ","permalink":"https://www.333rd.net/en/posts/tech/create-microsoft-onedrive-api/","summary":"\u003ch1 id=\"creating-a-microsoft-onedrive-api\"\u003eCreating a Microsoft OneDrive API\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eCompared with the shared API resources built into Rclone, a self-built private API can bring a better experience. If you need to use a private API, you should provide the tokens.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-get-the-client_id\"\u003e1. Get the client_id\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eGo to \u003ca href=\"https://portal.azure.com/#blade/Microsoft_AAD_IAM/ActiveDirectoryMenuBlade/RegisteredApps\" title=\"Microsoft Azure app registrations\"\u003eMicrosoft Azure app registrations\u003c/a\u003e and click \u003cstrong\u003eNew registration\u003c/strong\u003e.\u003cbr\u003e\n\n\n\n\u003cfigure class=\"zoom-container\"\u003e\n    \u003cimg src=\"https://img.333rd.net/%E6%8A%80%E6%9C%AF/%E5%88%9B%E5%BB%BA%E5%BE%AE%E8%BD%AFOneDrive%20API/%E5%BA%94%E7%94%A8%E6%B3%A8%E5%86%8C.png\" \n         alt=\"App registration\" title=\"App registration\"loading=\"lazy\" /\u003e\n    \n    \u003cfigcaption\u003eApp registration\u003c/figcaption\u003e\u003c/figure\u003e\n\u003c/p\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eConfigure as shown in the figure; for the \u003cstrong\u003eRedirect URI\u003c/strong\u003e, fill in \u003ccode\u003ehttp://localhost\u003c/code\u003e.\u003cbr\u003e\n\n\n\n\u003cfigure class=\"zoom-container\"\u003e\n    \u003cimg src=\"https://img.333rd.net/%E6%8A%80%E6%9C%AF/%E5%88%9B%E5%BB%BA%E5%BE%AE%E8%BD%AFOneDrive%20API/%E6%B3%A8%E5%86%8C%E5%BA%94%E7%94%A8%E7%A8%8B%E5%BA%8F.png\" \n         alt=\"Register an application\" title=\"Register an application\"loading=\"lazy\" /\u003e\n    \n    \u003cfigcaption\u003eRegister an application\u003c/figcaption\u003e\u003c/figure\u003e\n\u003c/p\u003e","title":"Creating a Microsoft OneDrive API"},{"content":"Linux: Mount OneDrive Cloud Drive with Rclone I believe I am not the only one. To save money, many people pick the basic server configuration when choosing a server, leaving only a pitiful few tens of GB of storage, or even just 10 GB. But sometimes you really do need a larger storage space. At such times, cloud drives that provide APIs — OneDrive, Google Drive and the like — can be mounted onto the server and used as local storage, which to some extent helps relieve our storage anxiety.\nSteps 1. Download and Install A Linux system is assumed in this article, but the operations are basically the same everywhere, and other systems can follow along too.\nRun the following command to download and install Rclone:\n1 curl https://rclone.org/install.sh | sudo bash 2. Configure Rclone After installation, run:\n1 rclone config The output is as follows:\n1 2 3 4 5 No remotes found - make a new one n) New remote s) Set configuration password q) Quit config n/s/q\u0026gt; Type n to create a new config. The name is up to you; here I entered:\n1 name\u0026gt; ODrive Next it asks you to choose the type of cloud drive service to mount:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 Option Storage. Type of storage to configure. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). Choose a number from below, or type in your own value. 1 / 1Fichier \\ \u0026#34;fichier\u0026#34; 2 / Alias for an existing remote \\ \u0026#34;alias\u0026#34; 3 / Amazon Drive \\ \u0026#34;amazon cloud drive\u0026#34; 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Minio, SeaweedFS, and Tencent COS \\ \u0026#34;s3\u0026#34; 5 / Backblaze B2 \\ \u0026#34;b2\u0026#34; 6 / Better checksums for other remotes \\ \u0026#34;hasher\u0026#34; 7 / Box \\ \u0026#34;box\u0026#34; 8 / Cache a remote \\ \u0026#34;cache\u0026#34; 9 / Citrix Sharefile \\ \u0026#34;sharefile\u0026#34; 10 / Compress a remote \\ \u0026#34;compress\u0026#34; 11 / Dropbox \\ \u0026#34;dropbox\u0026#34; 12 / Encrypt/Decrypt a remote \\ \u0026#34;crypt\u0026#34; 13 / Enterprise File Fabric \\ \u0026#34;filefabric\u0026#34; 14 / FTP Connection \\ \u0026#34;ftp\u0026#34; 15 / Google Cloud Storage (this is not Google Drive) \\ \u0026#34;google cloud storage\u0026#34; 16 / Google Drive \\ \u0026#34;drive\u0026#34; 17 / Google Photos \\ \u0026#34;google photos\u0026#34; 18 / Hadoop distributed file system \\ \u0026#34;hdfs\u0026#34; 19 / Hubic \\ \u0026#34;hubic\u0026#34; 20 / In memory object storage system. \\ \u0026#34;memory\u0026#34; 21 / Jottacloud \\ \u0026#34;jottacloud\u0026#34; 22 / Koofr \\ \u0026#34;koofr\u0026#34; 23 / Local Disk \\ \u0026#34;local\u0026#34; 24 / Mail.ru Cloud \\ \u0026#34;mailru\u0026#34; 25 / Mega \\ \u0026#34;mega\u0026#34; 26 / Microsoft Azure Blob Storage \\ \u0026#34;azureblob\u0026#34; 27 / Microsoft OneDrive \\ \u0026#34;onedrive\u0026#34; 28 / OpenDrive \\ \u0026#34;opendrive\u0026#34; 29 / OpenStack Swift (Rackspace Cloud Files, Memset Memstore, OVH) \\ \u0026#34;swift\u0026#34; 30 / Pcloud \\ \u0026#34;pcloud\u0026#34; 31 / Put.io \\ \u0026#34;putio\u0026#34; 32 / QingCloud Object Storage \\ \u0026#34;qingstor\u0026#34; 33 / SSH/SFTP Connection \\ \u0026#34;sftp\u0026#34; 34 / Sia Decentralized Cloud \\ \u0026#34;sia\u0026#34; 35 / Sugarsync \\ \u0026#34;sugarsync\u0026#34; 36 / Tardigrade Decentralized Cloud Storage \\ \u0026#34;tardigrade\u0026#34; 37 / Transparently chunk/split large files \\ \u0026#34;chunker\u0026#34; 38 / Union merges the contents of several upstream fs \\ \u0026#34;union\u0026#34; 39 / Uptobox \\ \u0026#34;uptobox\u0026#34; 40 / Webdav \\ \u0026#34;webdav\u0026#34; 41 / Yandex Disk \\ \u0026#34;yandex\u0026#34; 42 / Zoho \\ \u0026#34;zoho\u0026#34; 43 / http Connection \\ \u0026#34;http\u0026#34; 44 / premiumize.me \\ \u0026#34;premiumizeme\u0026#34; 45 / seafile \\ \u0026#34;seafile\u0026#34; Storage\u0026gt; 27 Here OneDrive is number 27, so type 27.\nNext it will ask you to enter the OAuth client ID, which consists of client_id and client_secret. You can refer to my other article Creating a Microsoft OneDrive API to learn how to obtain them:\n1 2 3 4 5 6 7 8 9 10 Option client_id. OAuth Client Id. Leave blank normally. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). client_id\u0026gt; 填入ID Option client_secret. OAuth Client Secret. Leave blank normally. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). client_secret\u0026gt; 填入密钥 Next, choose the region for OneDrive. Here I chose Global, i.e. 1.\n1 2 3 4 5 6 7 8 9 10 11 12 13 Option region. Choose national cloud region for OneDrive. Enter a string value. Press Enter for the default (\u0026#34;global\u0026#34;). Choose a number from below, or type in your own value. 1 / Microsoft Cloud Global \\ \u0026#34;global\u0026#34; 2 / Microsoft Cloud for US Government \\ \u0026#34;us\u0026#34; 3 / Microsoft Cloud Germany \\ \u0026#34;de\u0026#34; 4 / Azure and Office 365 operated by 21Vianet in China \\ \u0026#34;cn\u0026#34; region\u0026gt; 1 This step can just keep the default:\n1 2 3 4 Edit advanced config? y) Yes n) No (default) y/n\u0026gt; Note that this step requires logging in, so choose n:\n1 2 3 4 5 6 7 Use auto config? * Say Y if not sure * Say N if you are working on a remote or headless machine y) Yes (default) n) No y/n\u0026gt; n The output echoes as follows:\n1 2 3 4 5 6 7 8 9 10 Option config_token. For this to work, you will need rclone available on a machine that has a web browser available. For more help and alternate methods see: https://rclone.org/remote_setup/ Execute the following on the machine with the web browser (same rclone version recommended): rclone authorize \u0026#34;onedrive\u0026#34; \u0026#34;API参数\u0026#34; # 此处为之后需要执行的指令 Then paste the result. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). config_token\u0026gt; The authorization code needs to be obtained on a local computer. Download the Rclone build for your system version from Rclone downloads. After downloading, extract it and enter that directory, hold Shift and right-click the mouse, then select Open PowerShell window here. In the pop-up window, paste the command given by the program above; its format is roughly:\n1 .\\rclone.exe authorize \u0026#34;onedrive\u0026#34; \u0026#34;API参数\u0026#34; The browser will then open automatically. If you are already logged into a Microsoft account, the Request a permission window will pop up directly — if the account is not the one you intended, it is recommended to log out first, then follow the steps below. If you are not logged in, a login page will appear, and after logging in the Request a permission window will pop up. Choose Accept. The page will then say it is safe to close, while the command line outputs the authorization code; copy it and paste it back on the server.\nBut if you are using the VSCode terminal, you can directly choose the default Yes for this step, and the following content will be output:\n1 2 3 4 2022/03/19 00:08:19 NOTICE: Make sure your Redirect URL is set to \u0026#34;http://localhost:53682/\u0026#34; in your custom config. 2022/03/19 00:08:19 NOTICE: If your browser doesn\u0026#39;t open automatically go to the following link: http://127.0.0.1:53682/auth?state=为避免泄露已打码 2022/03/19 00:08:19 NOTICE: Log in and authorize rclone for access 2022/03/19 00:08:19 NOTICE: Waiting for code... VSCode forwards this port automatically. Then hold down CTRL and click the link on the 2nd line to open it automatically, or copy it and open it in the browser on your local computer. The Request a permission window will pop up in the browser on your local computer, and the remaining steps are the same as above.\nWhen done, the following content is output:\n1 2022/03/19 00:08:31 NOTICE: Got code Next, choose the type of OneDrive. Pick based on your own situation; since I use a business OneDrive, I chose 1.\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 Option config_type. Type of connection Enter a string value. Press Enter for the default (\u0026#34;onedrive\u0026#34;). Choose a number from below, or type in an existing value. 1 / OneDrive Personal or Business \\ \u0026#34;onedrive\u0026#34; 2 / Root Sharepoint site \\ \u0026#34;sharepoint\u0026#34; / Sharepoint site name or URL 3 | E.g. mysite or https://contoso.sharepoint.com/sites/mysite \\ \u0026#34;url\u0026#34; 4 / Search for a Sharepoint site \\ \u0026#34;search\u0026#34; 5 / Type in driveID (advanced) \\ \u0026#34;driveid\u0026#34; 6 / Type in SiteID (advanced) \\ \u0026#34;siteid\u0026#34; / Sharepoint server-relative path (advanced) 7 | E.g. /teams/hr \\ \u0026#34;path\u0026#34; config_type\u0026gt; config_type\u0026gt; 1 It will then look up the corresponding account based on your input; after confirming it is correct, press Enter to accept the default:\n1 2 3 4 5 6 7 8 Drive OK? Found drive \u0026#34;root\u0026#34; of type \u0026#34;business\u0026#34; URL: https://呷脯呷脯.喵喵喵 y) Yes (default) n) No y/n\u0026gt; At this point the configuration is finished. All the cloud drive config information will be output for confirmation; after confirming it is correct, press Enter to accept the default, then type q to quit. Then use this command to check whether the configuration succeeded; if successful, it will list the directories under the root of the cloud drive:\n1 rclone lsd \u0026lt;配置的网盘名称\u0026gt;:/ 3. Mount the OneDrive Cloud Drive Most Chinese and foreign search results only give a brief OneDrive mount command template, and since I do not plan to mount it myself either, I just copied a template. However, if you need to configure the mount parameters, you can refer to my other blog post Linux: Mount Google Drive Cloud Drive with Rclone, as well as the relevant documentation on the official Rclone website.\n1 rclone mount \u0026lt;配置的云盘名称\u0026gt;:\u0026lt;要挂载的云盘目录\u0026gt; \u0026lt;作为挂载点的本地目录\u0026gt; --copy-links --no-gzip-encoding --no-check-certificate --allow-other --allow-non-empty --umask 000 --daemon If it fails to mount, try the following command:\n1 apt install fuse After mounting, test with the following command; if the cloud drive directory appears, it succeeded:\n1 df -h To unmount, use the following command:\n1 fusermount -qzu \u0026lt;挂载路径\u0026gt; References Mounting OneDrive with Rclone on a Linux Server Mounting OneDrive with a Self-built Private API Connecting OneDrive Using a Self-built API ","permalink":"https://www.333rd.net/en/posts/tech/linux-rclone-mount-onedrive/","summary":"\u003ch1 id=\"linux-mount-onedrive-cloud-drive-with-rclone\"\u003eLinux: Mount OneDrive Cloud Drive with Rclone\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI believe I am not the only one. To save money, many people pick the basic server configuration when choosing a server, leaving only a pitiful few tens of GB of storage, or even just 10 GB. But sometimes you really do need a larger storage space. At such times, cloud drives that provide APIs — OneDrive, Google Drive and the like — can be mounted onto the server and used as local storage, which to some extent helps relieve our storage anxiety.\u003c/em\u003e\u003c/p\u003e","title":"Linux: Mount OneDrive Cloud Drive with Rclone"},{"content":"Which File-Sharing Protocol Should You Use for a NAS? Which one should I use: SMB, WebDAV, FTP, or SFTP? Many people have faced the problem of sharing files across devices. In the age of the Internet, networks provide a solution for scenarios like this. The four protocols mentioned above are currently the mainstream options, so let’s use the data to determine which one is best suited to your needs.\n1. Distinguishing the Similarities and Differences 1.1. Similarities There is not much to say here. Even though their application scenarios differ, their purpose is the same: sharing and transferring files over a network.\n1.2. Differences First, here are the scoring criteria. The security score is based on the CNNVD vulnerability classification standard, using the number of disclosures between March 15, 2017 and March 15, 2022 as the basis. After weighting, the score is calculated using statistical normalization (Chinese Wikipedia and normalization operations. The weights used are 0.5 for critical vulnerabilities, 0.25 for high-severity vulnerabilities, 0.15 for medium-severity vulnerabilities, and 0.1 for low-severity vulnerabilities. Finally, Min-Max scaling is used for normalization to obtain μ. The script used can be found here: Vulnerability Statistics. Please also note that this calculation is not rigorous and does not distinguish between results with the same name but different meanings.\nConvenience Score Description ★☆☆☆☆ Requires complex commands for deployment, needs multiple projects to work together, and has no Chinese documentation. ★★☆☆☆ Must be downloaded and configured manually, but offers quick deployment with a basic configuration and has detailed documentation in other languages for reference. ★★★☆☆ Must be downloaded and configured manually, but offers quick deployment with a basic configuration and has detailed Chinese documentation for reference. ★★★★☆ Offers one-click deployment or convenient Docker deployment. ★★★★★ Built into the system and can be deployed without commands. Compatibility Score Description ★☆☆☆☆ Supported only on specific devices and tightly coupled to the hardware and software. ★★☆☆☆ Limited support only after installing software on smart devices such as computers, tablets, and phones. ★★★☆☆ Limited support on common household devices after installing software. ★★★★☆ Good support only after installing software on smart devices such as computers, tablets, and phones. ★★★★★ Natively supported by common household devices, or well supported after installing software. Security Score Description ★☆☆☆☆ 1 \u0026gt; μ ≥ 0.8 ★★☆☆☆ 0.8 \u0026gt; μ ≥ 0.6 ★★★☆☆ 0.6 \u0026gt; μ ≥ 0.4 ★★★★☆ 0.8 \u0026gt; μ ≥ 0.2 ★★★★★ 0.2 \u0026gt; μ ≥ 0 Transfer Efficiency Score Description ★☆☆☆☆ Less than 60% of the available bandwidth ★★☆☆☆ 60% or more of the available bandwidth ★★★☆☆ 70% or more of the available bandwidth ★★★★☆ 80% or more of the available bandwidth ★★★★★ 90% or more of the available bandwidth 1.2.1. SMB Server Message Block (abbreviated as SMB), also known as Common Internet File System (abbreviated as CIFS, /ˈsɪfs/), is an application-layer network transport protocol developed by Microsoft. Its primary function is to enable machines on a network to share computer files, printers, serial ports, and communications resources.\nThe preceding information comes from Wikipedia. Based on its application scenarios, it is easy to see that SMB focuses on file sharing within a local network, while also offering broad protocol compatibility. While gathering information, I came across an interesting detail: the relationship between SMB and Samba. The Wikipedia entry for SMB says that SMB was originally developed by IBM, after which Microsoft made substantial changes to it, resulting in the protocol version most commonly used today. Samba, meanwhile, is free software that implements compatibility with SMB through reverse engineering and is used primarily on Linux.\nAt the same time, I found another article that gives the exact opposite account, claiming that Samba came first and that Microsoft\u0026rsquo;s SMB was a reimplementation created to avoid paying licensing fees.\nIn any case, the first account is the widely accepted one today, and I agree with it. SMB is a file-sharing protocol; whether it is Windows\u0026rsquo; SMB 3.0 or Linux\u0026rsquo;s Samba, each is simply an implementation of that protocol, which is why they are mutually compatible.\nConvenience: Windows supports SMB natively, making it easy to enable file sharing through SMB. Linux requires you to install Samba yourself, although the process is not particularly difficult overall.\nCompatibility: This is arguably one of the most widely compatible protocols available. From Windows to Linux, and from TV boxes to home computers, it is supported almost everywhere.\nSecurity: Windows SMB uses port 445, and the notorious EternalBlue was an exploit for vulnerabilities in that port and the SMB protocol. As a result, a large number of computers were infected with ransomware for a long time afterward, making everyone wary of it. It is therefore recommended only for use on a local network. The script output is as follows:\n1 2 3 4 5 6 漏洞总数：45 未分级数量：1 低危数量：0 中危数量：18 高危数量：20 超危数量：6 Transfer efficiency: Due to its inherent characteristics, SMB has somewhat higher overhead and is therefore slightly less efficient. For details, see the tests I conduct later. 1.2.2. WebDAV Web-based Distributed Authoring and Versioning (WebDAV) is an extension of the Hypertext Transfer Protocol (HTTP) that facilitates collaborative editing and management of documents stored on World Wide Web servers.\nThis is how Wikipedia describes WebDAV. It is easy to see that WebDAV is primarily intended for file sharing over the public Internet. Since it is based on HTTP, it also inherits HTTP\u0026rsquo;s advantages, including:\nOperating-system integration Easy traversal of firewalls Tight integration with Web services Free to use Version control Encrypted transmission Remote access Centralized storage Version control File locking However, because HTTP transmits data in plaintext, deployment with HTTPS is recommended, even on a local network.\nConvenience: There are quite a few Docker deployment options, so deployment is reasonably convenient. Compatibility: On common operating systems, phones, and computers, installing software provides good support; it can even be used, albeit with limitations, through a browser. However, TV boxes and many other specialized devices generally do not support it directly. Security: Using HTTPS provides good security. The script output is as follows: 1 2 3 4 5 6 漏洞总数：3 未分级数量：0 低危数量：0 中危数量：2 高危数量：1 超危数量：0 Transfer efficiency: WebDAV establishes only one TCP connection and supports GZIP compression. When sending and receiving large numbers of small files, some people have confirmed that FTP is faster; when sending and receiving large files, the two are equally fast and the overhead is negligible. This article will verify that claim again in a later experiment. 1.2.3. FTP File Transfer Protocol (English: File Transfer Protocol, abbreviated as FTP) is an application-layer protocol used to transfer files between clients and servers over a computer network.\n","permalink":"https://www.333rd.net/en/posts/read/nas-file-sharing-protocols/","summary":"\u003ch1 id=\"which-file-sharing-protocol-should-you-use-for-a-nas\"\u003eWhich File-Sharing Protocol Should You Use for a NAS?\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eWhich one should I use: SMB, WebDAV, FTP, or SFTP? Many people have faced the problem of sharing files across devices. In the age of the Internet, networks provide a solution for scenarios like this. The four protocols mentioned above are currently the mainstream options, so let’s use the data to determine which one is best suited to your needs.\u003c/em\u003e\u003c/p\u003e","title":"Which File-Sharing Protocol Should You Use for a NAS?"},{"content":"Tianyi Cloud Drive Command Line Client Can be used to back up server data and to expand storage automatically by checking in.\nSteps 1. Download the Corresponding Version and Extract It Download link: Releases\n2. Usage Project page: cloudpan189-go\n3. Enable Automatic Check-in and Back Up Server Files to Family Cloud Log in, update and switch to Family Cloud 1 2 3 cloudpan189-go login cloudpan189-go update cloudpan189-go family Automatic check-in every day at 0:00\nType the command in the command line\n1 crontab -e Below is the template for checking in automatically at 0:00 every day. Add the following content at the last line; press CTRL+X to save, y to confirm and Enter to exit:\n1 0 0 * * * /文件夹路径/cloudpan189-go sign Back up server files\nPack the files with tar\n1 tar -cvf 备份文件名_年-月-日.tar /待打包目录 Upload them to Tianyi Cloud Drive\n1 cloudpan189-go upload 备份文件名_年-月-日.tar /目标路径 References cloudpan189-go ","permalink":"https://www.333rd.net/en/posts/tech/tianyi-cloud-drive-cli/","summary":"\u003ch1 id=\"tianyi-cloud-drive-command-line-client\"\u003eTianyi Cloud Drive Command Line Client\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eCan be used to back up server data and to expand storage automatically by checking in.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-download-the-corresponding-version-and-extract-it\"\u003e1. Download the Corresponding Version and Extract It\u003c/h3\u003e\n\u003cp\u003eDownload link: \u003ca href=\"https://github.com/tickstep/cloudpan189-go/releases\" title=\"Releases\"\u003eReleases\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"2-usage\"\u003e2. Usage\u003c/h3\u003e\n\u003cp\u003eProject page: \u003ca href=\"https://github.com/tickstep/cloudpan189-go\" title=\"cloudpan189-go\"\u003ecloudpan189-go\u003c/a\u003e\u003c/p\u003e\n\u003ch3 id=\"3-enable-automatic-check-in-and-back-up-server-files-to-family-cloud\"\u003e3. Enable Automatic Check-in and Back Up Server Files to Family Cloud\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eLog in, update and switch to Family Cloud\u003c/strong\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecloudpan189-go login\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecloudpan189-go update\n\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003ecloudpan189-go family\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003e\u003cstrong\u003eAutomatic check-in every day at 0:00\u003c/strong\u003e\u003c/p\u003e","title":"Tianyi Cloud Drive Command Line Client"},{"content":"Zsh: The Efficiency Powerhouse for Linux For developers who use Linux regularly, the shell is one of the most frequently used programs. It acts as a bridge between developers and the Linux system, allowing us to perform complex operations with simple commands.\nBash is the default shell on Linux, while Zsh is a more user-friendly shell that is fully compatible with Bash. Using Zsh is as satisfying as scratching an itch, and it can greatly boost your efficiency when working with Linux.\nSteps 1. Installing Zsh On Debian-based Linux distributions, install Zsh with the following command; other Linux distributions are similar:\n1 apt install zsh 2. Installing Oh My Zsh The default Zsh configuration is a bit tedious, so a user named robbyrussell created a configuration framework Oh My Zsh on GitHub. It is by far the most popular Zsh configuration.\nYou can install it using either curl or wget. During installation, you will be asked whether to switch your default shell to zsh — enter y to accept:\nInstall with curl\nGitHub: 1 sh -c \u0026#34;$(curl -fsSL https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh)\u0026#34; Gitee (domestic mirror) 1 sh -c \u0026#34;$(curl -fsSL https://gitee.com/mirrors/oh-my-zsh/raw/master/tools/install.sh)\u0026#34; Install with wget\nGitHub: 1 sh -c \u0026#34;$(wget https://raw.github.com/robbyrussell/oh-my-zsh/master/tools/install.sh -O -)\u0026#34; Gitee (domestic mirror) 1 sh -c \u0026#34;$(wget -O- https://gitee.com/pocmon/mirrors/raw/master/tools/install.sh)\u0026#34; 3. Setting the Theme Run the following command to open and edit the Oh My Zsh configuration file:\n1 nano ~/.zshrc The ZSH_THEME=\u0026quot;robbyrussell\u0026quot; parameter controls the theme; change it to random to use a random theme.\nEvery time you open the terminal, you will see output like the following; note down any theme you like:\n1 [oh-my-zsh] Random theme \u0026#39;\u0026lt;theme name\u0026gt;\u0026#39; loaded 4. Other Oh My Zsh Operations Uninstall: 1 2 uninstall_oh_my_zsh Are you sure you want to remove Oh My Zsh? [y/N] Update: 1 upgrade_oh_my_zsh 5. Zsh Quick Start Use the following command to open the configuration file, find the plugins parameter, and enter the plugins you want to enable inside the parentheses, separated by spaces:\n1 2 3 4 nano ~/.zshrc # The following is my configuration plugins=(git z zsh-autosuggestions zsh-syntax-highlighting ) After enabling the plugins, refresh the shell with the following command:\n1 source ~/.zshrc Here is what these plugins do:\ngit: No configuration needed; it is enabled by default. It lets you enter git commands using shorthand aliases. Use the following command to view all the aliases: 1 cat ~/.oh-my-zsh/plugins/git/git.plugin.zsh Z: No configuration needed; it is enabled by default. It lets you quickly jump to directories you have visited before; just use z directory to jump there directly.\nzsh-syntax-highlighting: Highlights your command input — correct syntax is shown in green and syntax errors in red. You need to install it yourself; the install command is as follows:\n1 git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-syntax-highlighting zsh-autosuggestions: Finds prefix-matching entries from your command history; press the right arrow key ➡ to complete them quickly. It is especially handy. You need to install it yourself; the install command is as follows: 1 git clone https://github.com/zsh-users/zsh-autosuggestions ${ZSH_CUSTOM:-~/.oh-my-zsh/custom}/plugins/zsh-autosuggestions References Detailed Oh My Zsh Plugin Installation Tutorial and Common Plugins ","permalink":"https://www.333rd.net/en/posts/tech/zsh-and-oh-my-zsh-installation/","summary":"\u003ch1 id=\"zsh-the-efficiency-powerhouse-for-linux\"\u003eZsh: The Efficiency Powerhouse for Linux\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eFor developers who use Linux regularly, the shell is one of the most frequently used programs. It acts as a bridge between developers and the Linux system, allowing us to perform complex operations with simple commands.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eBash is the default shell on Linux, while Zsh is a more user-friendly shell that is fully compatible with Bash. Using Zsh is as satisfying as scratching an itch, and it can greatly boost your efficiency when working with Linux.\u003c/em\u003e\u003c/p\u003e","title":"Installing and Using Zsh and Oh My Zsh"},{"content":"Linux: Mount Google Drive Cloud Drive with Rclone I believe I am not the only one. To save money, many people pick the basic server configuration when choosing a server, leaving only a pitiful few tens of GB of storage, or even just 10 GB. But sometimes you really do need a larger storage space. At such times, cloud drives that provide APIs — OneDrive, Google Drive and the like — can be mounted onto the server and used as local storage, which to some extent helps relieve our storage anxiety.\nSteps 1. Download and Install A Linux system is assumed in this article, but the operations are basically the same everywhere, and other systems can follow along too.\nRun the following command to download and install Rclone:\n1 curl https://rclone.org/install.sh | sudo bash 2. Configure Rclone After installation, run:\n1 rclone config The output is as follows:\n1 2 3 4 5 No remotes found - make a new one n) New remote s) Set configuration password q) Quit config n/s/q\u0026gt; Type n to create a new config. The name is up to you; here I entered:\n1 name\u0026gt; GDrive Next it asks you to choose the type of cloud drive service to mount:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 Option Storage. Type of storage to configure. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). Choose a number from below, or type in your own value. 1 / 1Fichier \\ \u0026#34;fichier\u0026#34; 2 / Alias for an existing remote \\ \u0026#34;alias\u0026#34; 3 / Amazon Drive \\ \u0026#34;amazon cloud drive\u0026#34; 4 / Amazon S3 Compliant Storage Providers including AWS, Alibaba, Ceph, Digital Ocean, Dreamhost, IBM COS, Minio, SeaweedFS, and Tencent COS \\ \u0026#34;s3\u0026#34; 5 / Backblaze B2 \\ \u0026#34;b2\u0026#34; 6 / Better checksums for other remotes \\ \u0026#34;hasher\u0026#34; 7 / Box \\ \u0026#34;box\u0026#34; 8 / Cache a remote \\ \u0026#34;cache\u0026#34; 9 / Citrix Sharefile \\ \u0026#34;sharefile\u0026#34; 10 / Compress a remote \\ \u0026#34;compress\u0026#34; 11 / Dropbox \\ \u0026#34;dropbox\u0026#34; 12 / Encrypt/Decrypt a remote \\ \u0026#34;crypt\u0026#34; 13 / Enterprise File Fabric \\ \u0026#34;filefabric\u0026#34; 14 / FTP Connection \\ \u0026#34;ftp\u0026#34; 15 / Google Cloud Storage (this is not Google Drive) \\ \u0026#34;google cloud storage\u0026#34; 16 / Google Drive \\ \u0026#34;drive\u0026#34; 17 / Google Photos \\ \u0026#34;google photos\u0026#34; 18 / Hadoop distributed file system \\ \u0026#34;hdfs\u0026#34; 19 / Hubic \\ \u0026#34;hubic\u0026#34; 20 / In memory object storage system. \\ \u0026#34;memory\u0026#34; 21 / Jottacloud \\ \u0026#34;jottacloud\u0026#34; 22 / Koofr \\ \u0026#34;koofr\u0026#34; 23 / Local Disk \\ \u0026#34;local\u0026#34; 24 / Mail.ru Cloud \\ \u0026#34;mailru\u0026#34; 25 / Mega \\ \u0026#34;mega\u0026#34; 26 / Microsoft Azure Blob Storage \\ \u0026#34;azureblob\u0026#34; 27 / Microsoft OneDrive \\ \u0026#34;onedrive\u0026#34; 28 / OpenDrive \\ \u0026#34;opendrive\u0026#34; 29 / OpenStack Swift (Rackspace Cloud Files, Memset Memstore, OVH) \\ \u0026#34;swift\u0026#34; 30 / Pcloud \\ \u0026#34;pcloud\u0026#34; 31 / Put.io \\ \u0026#34;putio\u0026#34; 32 / QingCloud Object Storage \\ \u0026#34;qingstor\u0026#34; 33 / SSH/SFTP Connection \\ \u0026#34;sftp\u0026#34; 34 / Sia Decentralized Cloud \\ \u0026#34;sia\u0026#34; 35 / Sugarsync \\ \u0026#34;sugarsync\u0026#34; 36 / Tardigrade Decentralized Cloud Storage \\ \u0026#34;tardigrade\u0026#34; 37 / Transparently chunk/split large files \\ \u0026#34;chunker\u0026#34; 38 / Union merges the contents of several upstream fs \\ \u0026#34;union\u0026#34; 39 / Uptobox \\ \u0026#34;uptobox\u0026#34; 40 / Webdav \\ \u0026#34;webdav\u0026#34; 41 / Yandex Disk \\ \u0026#34;yandex\u0026#34; 42 / Zoho \\ \u0026#34;zoho\u0026#34; 43 / http Connection \\ \u0026#34;http\u0026#34; 44 / premiumize.me \\ \u0026#34;premiumizeme\u0026#34; 45 / seafile \\ \u0026#34;seafile\u0026#34; Storage\u0026gt; 16 Here Google Drive is number 16, so type 16.\nNext it will ask you to enter the OAuth client ID, which consists of client_id and client_secret. You can refer to my other article Creating a Google OAuth Client ID to learn how to obtain them:\n1 2 3 4 5 6 7 8 9 10 11 12 Option client_id. Google Application Client Id Setting your own is recommended. See https://rclone.org/drive/#making-your-own-client-id for how to create your own. If you leave this blank, it will use an internal key which is low performance. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). client_id\u0026gt; 填入ID Option client_secret. OAuth Client Secret. Leave blank normally. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). client_secret\u0026gt; 填入密钥 Next, choose the access scope of Rclone\u0026rsquo;s operations on the Google Drive files:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 Option scope. Scope that rclone should use when requesting access from drive. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). Choose a number from below, or type in your own value. 1 / Full access all files, excluding Application Data Folder. \\ \u0026#34;drive\u0026#34; 2 / Read-only access to file metadata and file contents. \\ \u0026#34;drive.readonly\u0026#34; / Access to files created by rclone only. 3 | These are visible in the drive website. | File authorization is revoked when the user deauthorizes the app. \\ \u0026#34;drive.file\u0026#34; / Allows read and write access to the Application Data folder. 4 | This is not visible in the drive website. \\ \u0026#34;drive.appfolder\u0026#34; / Allows read-only access to file metadata but 5 | does not allow any access to read or download file content. \\ \u0026#34;drive.metadata.readonly\u0026#34; scope\u0026gt; 1 Choose Full access to all files, excluding the Application Data Folder, i.e. 1.\nMost of the following options can keep the defaults; refer to the content below:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 Option root_folder_id. ID of the root folder. Leave blank normally. Fill in to access \u0026#34;Computers\u0026#34; folders (see docs), or for rclone to use a non root folder as its starting point. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). root_folder_id\u0026gt; Option service_account_file. Service Account Credentials JSON file path. Leave blank normally. Needed only if you want use SA instead of interactive login. Leading `~` will be expanded in the file name as will environment variables such as `${RCLONE_CONFIG_DIR}`. Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). service_account_file\u0026gt; Edit advanced config? y) Yes n) No (default) y/n\u0026gt; Note that this step requires logging in, so choose n:\n1 2 3 4 5 6 7 Use auto config? * Say Y if not sure * Say N if you are working on a remote or headless machine y) Yes (default) n) No y/n\u0026gt; n Copy the link to a browser and log in; after logging in it will give a config_verification_code, then paste it:\n1 2 3 4 5 6 Option config_verification_code. Verification code Go to this URL, authenticate then paste the code here. 此处为链接，复制后到浏览器完成登录步骤，会给一个“config_verification_code”。 Enter a string value. Press Enter for the default (\u0026#34;\u0026#34;). config_verification_code\u0026gt; 在此填入“config_verification_code”。 Choose a shared drive (team drive) or a personal drive; here I used a shared drive, so I chose y:\n1 2 3 4 5 Configure this as a Shared Drive (Team Drive)? y) Yes n) No (default) y/n\u0026gt; y At this point the configuration is finished. All the cloud drive config information will be output for confirmation; after confirming it is correct, press Enter to accept the default, then type q to quit. Then use this command to check whether the configuration succeeded; if successful, it will list the directories under the root of the cloud drive:\n1 rclone lsd \u0026lt;配置的网盘名称\u0026gt;:/ 3. Mount the Google Drive Cloud Drive Depending on your needs, you can modify the parameters yourself according to the table below. For other configuration parameters, refer to the official Rclone website:\nParameter Description \u0026ndash;allow-other Allow users other than the current rclone user to access \u0026ndash;attr-timeout 5m The duration of the file attribute cache (size, modification time, etc.). If the VPS \u0026ndash;vfs-cache-mode full Enable the VFS file cache, which reduces the interactions between rclone and the API and also improves file read/write efficiency \u0026ndash;vfs-cache-max-age 24h The VFS file cache duration; set to 24 hours here. If files rarely change, a longer duration is recommended \u0026ndash;vfs-cache-max-size 10G The upper limit of the VFS file cache size; adjust it according to the remaining disk space of the server \u0026ndash;vfs-read-chunk-size-limit 100M The chunk read size; 100M is set here to improve file read efficiency. For example, a 1 GB file is roughly read in 10 chunks, but this also increases the number of API requests \u0026ndash;buffer-size 32M The memory buffer size when reading each file; it controls the memory usage of rclone while uploading and mounting. Lowering it can prevent crashes caused by excessive memory usage, but setting it too low may affect the transfer speed of some files; set it according to the server\u0026rsquo;s memory size \u0026ndash;low-level-retries The number of retries when a file transfer has no speed. When there is no speed, it starts by sleeping 10ms before each retry; if it still fails, it sleeps for a longer period and retries again, which can slightly speed up file uploads \u0026ndash;transfers Controls the maximum number of concurrent transfer tasks. If your CPU performance is poor, lower it; but too low a value may affect the speed of transferring multiple files simultaneously \u0026ndash;daemon Run the program in the background Here is a template:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 rclone mount \u0026lt;配置的云盘名称\u0026gt;:\u0026lt;要挂载的云盘目录\u0026gt; \u0026lt;作为挂载点的本地目录\u0026gt; \\ --umask 0000 \\ --default-permissions \\ --allow-non-empty \\ --allow-other \\ --attr-timeout 5m \\ --transfers 4 \\ --buffer-size 32M \\ --low-level-retries 200 \\ --vfs-read-chunk-size 32M \\ --vfs-read-chunk-size-limit 128M \\ --vfs-cache-mode full \\ --vfs-cache-max-age 24h \\ --vfs-cache-max-size 10G \\ --daemon If it fails to mount, try the following command:\n1 apt install fuse After mounting, test with the following command; if the cloud drive directory appears, it succeeded:\n1 df -h To unmount, use the following command:\n1 fusermount -qzu \u0026lt;挂载路径\u0026gt; ","permalink":"https://www.333rd.net/en/posts/tech/linux-rclone-mount-google-drive/","summary":"\u003ch1 id=\"linux-mount-google-drive-cloud-drive-with-rclone\"\u003eLinux: Mount Google Drive Cloud Drive with Rclone\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eI believe I am not the only one. To save money, many people pick the basic server configuration when choosing a server, leaving only a pitiful few tens of GB of storage, or even just 10 GB. But sometimes you really do need a larger storage space. At such times, cloud drives that provide APIs — OneDrive, Google Drive and the like — can be mounted onto the server and used as local storage, which to some extent helps relieve our storage anxiety.\u003c/em\u003e\u003c/p\u003e","title":"Linux: Mount Google Drive Cloud Drive with Rclone"},{"content":"Creating a Google OAuth Client ID Compared with the shared API resources built into Rclone, a self-built private API can bring a better experience. If you need to use a private API, you should provide the OAuth client ID.\nSteps 1. Enable the Google Drive API Go to Google Drive API and enable it as shown in the figure.\nEnable Google Drive API 2. Create a New Project After enabling it, you will be prompted to create a new API project; go create it as shown in the figure:\nCreate a new project Project name is up to you; English is preferred. Project ID can be modified if needed; otherwise keep the defaults for everything else.\n3. Configure the Consent Screen When creating an OAuth client ID for the first time, you need to configure the consent screen, i.e. the OAuth consent screen in the left sidebar:\nConfigure the consent screen Create the OAuth consent screen Apart from the required fields, the rest can be left empty; there are no requirements, so just keep all the defaults.\nThen add account permissions; the steps are shown in the figure:\nAdd account permissions 4. Create the OAuth Client ID Create the OAuth client ID as shown in the figure:\nCreate the OAuth client ID Configure it as follows:\nConfigure the OAuth client ID Then you will get the OAuth client ID; please keep it safe:\nGet the OAuth client ID ","permalink":"https://www.333rd.net/en/posts/tech/create-google-oauth-client-id/","summary":"\u003ch1 id=\"creating-a-google-oauth-client-id\"\u003eCreating a Google OAuth Client ID\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eCompared with the shared API resources built into Rclone, a self-built private API can bring a better experience. If you need to use a private API, you should provide the OAuth client ID.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-enable-the-google-drive-api\"\u003e1. Enable the Google Drive API\u003c/h3\u003e\n\u003cp\u003eGo to \u003ca href=\"https://console.developers.google.com/apis/api/drive.googleapis.com/overview\" title=\"Google Drive API\"\u003eGoogle Drive API\u003c/a\u003e and enable it as shown in the figure.\u003cbr\u003e\n\n\n\n\u003cfigure class=\"zoom-container\"\u003e\n    \u003cimg src=\"https://img.333rd.net/%E6%8A%80%E6%9C%AF/%E5%88%9B%E5%BB%BA%E8%B0%B7%E6%AD%8COAuth%20client%20ID/%E5%90%AF%E7%94%A8Google%20Drive%20API.png\" \n         alt=\"Enable Google Drive API\" title=\"Enable Google Drive API\"loading=\"lazy\" /\u003e\n    \n    \u003cfigcaption\u003eEnable Google Drive API\u003c/figcaption\u003e\u003c/figure\u003e\n\u003c/p\u003e","title":"Creating a Google OAuth Client ID"},{"content":"Collaborative Development with VSCode and Keil Keil is a familiar face among hardware development IDEs — so much so that many people who started with C51 probably used Keil. But I have never been quite convinced by Keil\u0026rsquo;s editor. VSCode, backed by Microsoft, has undoubtedly been battle-tested by countless developers and feels far more comfortable to use. This article therefore introduces the steps for developing with VSCode together with Keil uVison 5.\nSteps 1. Install VSCode and Keil VSCode can be downloaded and installed from the VSCode official website. For Keil, there is a tutorial in another one of my articles — Installing C51 and MDK Arm in Keil together. If the tutorial does not clearly require anything specific, install according to your own needs; if there are no special requirements, an all-defaults installation is fine too.\nVSCode has a Chinese localization plugin. You can install it by following this tutorial: How to set Visual Studio Code to Chinese.\nSince the default character encoding is ANSI, Keil will show garbled text when editing or displaying Chinese. You can change the default character encoding to GB2312 in Keil\u0026rsquo;s settings to avoid the garbled Chinese. VSCode\u0026rsquo;s default configuration also cannot display Chinese properly, so you need to configure it as follows: Auto-detect character encoding Press the shortcut CTRL+, or open File -\u0026gt; Preferences -\u0026gt; Settings; Type Auto Guess Encoding in the search box and search; In the results, find the Auto Guess Encoding option and check it, and the encoding will then be adapted and detected automatically. 2. Install the required plugins in VSCode Install the Keil Assistant plugin as shown in the figure:\nKeil Assistant After installation, the install button turns into a gear icon. Click the gear and select Extension Settings to enter the plugin settings page, where you can see the two configuration parameters shown in the figure:\nKeil Assistant configuration Regardless of the version, there is an executable at /Path To Keil/UV4/UV4.exe in the Keil installation directory; fill its path into the corresponding configuration parameter. Fill the upper one with the path to the C51 version\u0026rsquo;s UV4.exe, and the lower one with the MDK-Arm version\u0026rsquo;s UV4.exe. If you have not installed a version, leave it blank; if you have installed both, fill in both.\n3. Write code in VSCode Compared with Keil\u0026rsquo;s clunky native editor, once you use VSCode you will never want to go back — it is truly a case of \u0026ldquo;having had the best, nothing else quite measures up.\u0026rdquo;\nCompiling, recompiling, and flashing of Keil projects is done by calling Keil\u0026rsquo;s command-line interface.\nAccording to the plugin author, the plugin essentially calls Keil\u0026rsquo;s command-line interface, so before writing code in VSCode you need to prepare the Keil project files in advance, and it is best to compile once and close Keil before writing code in VSCode.\nImport the Keil project file as shown in the figure:\nImport the Keil project file in VSCode The options for compiling, flashing to the device, and recompiling are shown in the figure:\nUsing the plugin 4. Afterword Since my computer\u0026rsquo;s runtime environment is well configured, I did not run into many problems, so this article is not written very rigorously and many small details are not mentioned. There are plenty of tutorials online, and I will not go on repeating them; here I will put one that I approve of. If you run into a problem not mentioned in this article, you can go there to check and solve it: Using the keil plugin on VS Code.\nProblems not mentioned roughly include the following:\nMissing plugins such as: C/C++ Missing a compiler — if you have not installed Visual Studio, the MinGW compiler is recommended; The following plugins are also recommended to assist with programming (the text in parentheses describes what they do; for details you can go to the official site and search): Tabnine AI Code Completion (Tab-key AI code completion) Visual Studio IntelliCode (AI-based smart code completion) C/C++ Extension Pack (C/C++ extension pack) References Keil Assistant How to use the keil plugin in VS Code Using the keil plugin on VS Code ","permalink":"https://www.333rd.net/en/posts/tech/vscode-and-keil-development/","summary":"\u003ch1 id=\"collaborative-development-with-vscode-and-keil\"\u003eCollaborative Development with VSCode and Keil\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eKeil is a familiar face among hardware development IDEs — so much so that many people who started with C51 probably used Keil. But I have never been quite convinced by Keil\u0026rsquo;s editor. VSCode, backed by Microsoft, has undoubtedly been battle-tested by countless developers and feels far more comfortable to use. This article therefore introduces the steps for developing with VSCode together with Keil uVison 5.\u003c/em\u003e\u003c/p\u003e","title":"Collaborative Development with VSCode and Keil"},{"content":"Installing C51 and MDK-Arm in Keil Together To this day, Keil has yet to unify the common hardware platforms into a single IDE. If the intention is to save disk space, I think it is unnecessary — after all, the installation packages for C51 and MDK-Arm together amount to less than 1.5 GB, which, compared with WeChat and QQ that easily take up tens of GB, is undoubtedly more like \u0026ldquo;small and beautiful\u0026rdquo;. This article will introduce the perfect solution for installing C51 and MDK-Arm at the same time\nSteps 1. Prerequisites For this installation, we need to prepare in advance: MDK-Arm, C51, Chinese localization file, cracking software (the software is for technical exchange purposes only; do not use it for commercial or illegal purposes. I shall not be held responsible for any resulting legal disputes).\n2. Install Keil 2.1. Create a folder On the drive where you want to install Keil, create a Keil folder, then create C51 and ARM folders inside the Keil folder. The directory structure is as follows:\n1 2 3 4 Keil ├─ARM │ └─Packs └─C51 2.2. Install C51 and MDK-Arm When installing, you need to install C51 and MDK-Arm into the C51 and ARM folders created in the previous step respectively. Keep good folder classification habits for easier future management.\nThere is no requirement on the installation order, but it is recommended to install C51 first, and then MDK-Arm. Because what we ultimately use is the UV4.exe provided by MDK-Arm, if we install C51 afterwards, the Keil icon on the desktop will link to the UV4.exe provided by C51, and we would need to create another shortcut to \\Keil\\ARM\\UV4\\UV4.exe and drag it to the desktop to replace it, which is an unnecessary hassle.\n3. Start merging Copy all the files inside \\Keil\\C51\\UV4 and paste them into \\Keil\\ARM\\UV4. If prompted about duplicate files, do not overwrite them — just skip them!\nThen copy the contents of the \\Keil\\C51\\TOOLS.INI file and paste them at the end of the \\Keil\\ARM\\TOOLS.INI file, without any modification needed. The rough structure is as follows:\n1 2 3 4 5 6 7 8 9 [UV2] Some MDK-Arm settings [ARM] Other MDK-Arm settings [UV2] Some C51 settings [C51] Other C51 settings 4. Localize to Chinese Download the localization file and replace \\Keil\\ARM\\UV4\\UV4.exe with it.\n5. Activation The activation steps are no different from the usual case, but there are a few points to note.\nOpen Keil from the desktop and go through the activation flow step by step. Note that there are two rows in the table:\nProduct License ID Code (LIC)/Product variant Support Period PK51 Prof. Developers Kit Registration code Support period MDK-ARM Plus Registration code Support period Therefore, you need to activate twice: select the Target dropdown of the cracking software as C51 and ARM respectively, and apply both generated activation codes to Keil.\nThat\u0026rsquo;s it — everything is complete, and you can begin your journey of hardware development.\nReferences [Windows] Keil uVision5.29 latest localization patch ","permalink":"https://www.333rd.net/en/posts/tech/keil-c51-mdk-arm-dual-installation/","summary":"\u003ch1 id=\"installing-c51-and-mdk-arm-in-keil-together\"\u003eInstalling C51 and MDK-Arm in Keil Together\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eTo this day, Keil has yet to unify the common hardware platforms into a single IDE. If the intention is to save disk space, I think it is unnecessary — after all, the installation packages for C51 and MDK-Arm together amount to less than 1.5 GB, which, compared with WeChat and QQ that easily take up tens of GB, is undoubtedly more like \u0026ldquo;small and beautiful\u0026rdquo;. This article will introduce \u003cstrong\u003ethe perfect solution for installing C51 and MDK-Arm at the same time\u003c/strong\u003e\u003c/em\u003e\u003c/p\u003e","title":"Installing C51 and MDK Arm in Keil Together"},{"content":"On Structs A struct is a quintessential example of an abstraction born out of engineering necessity — it was the demand that chose the struct, not the struct that created the demand.\n1. What Is a Struct? Wikipedia\u0026rsquo;s struct article describes it as follows:\nIn C, a struct (structure) is a data structure that falls under the category of composite (aggregate) data types.\nIn plain language, a struct is a composite data type created by combining basic data types as needed.\n2. What Are Structs Good For? In the early days of C, there was no struct. When the language needed to handle composite data types composed of multiple coupled data types, programmers had to use separate arrays of different types and figure out how to manage them. This was neither elegant nor easy to understand, so structs were born.\nEngineers love encapsulation at every layer — making the abstract concrete, or rather making the concrete abstract — and structs are one of the most typical forms of encapsulation. For any large-scale project, basic data types alone are insufficient and cannot accommodate every requirement. Structs, with their flexibility and power, instantly provided a solution to these problems.\nWe can use structs to combine multiple variables to describe complex objects. We can bundle a set of related data together and pass it into a function or return it as a function\u0026rsquo;s return value, rather than handling scattered data pieces individually.\nFor example, if you were writing a product management program for a supermarket, a struct is far more concise than writing a pile of arrays to store the data:\n1 2 3 4 5 6 struct goods //Product { char name[20]; //Product name float price; //Unit price float weight; //Weight }; C was originally created as a tool for the UNIX operating system. In UNIX, UNIX v3 introduced a compiler with struct support, and it was immediately put to use in subsequent versions.\nAs you can see, the birth of structs was no accident — it was an inevitability under specific conditions. Essentially, large-scale engineering projects meant that programmers had a strong need for abstraction and encapsulation, which led them to create structs. It was the demand that chose the struct, not the struct that created the demand.\n3. Things to Note When Using Structs Structs are not exactly the same in C and C++. Here are some examples to illustrate.\nDefining and declaring a struct in C: 1 2 3 4 5 6 7 8 struct goods //Product { char name[20]; //Product name float price; //Unit price float weight; //Weight }; struct goods var_goods; // struct goods is the type name, var_goods is the variable name Alternatively, use the typedef keyword to define a type alias:\n1 2 3 4 5 6 7 8 typedef struct goods //Product { char name[20]; //Product name float price; //Unit price float weight; //Weight } Good; Good var_goods; // \u0026#34;Good\u0026#34; is the type alias, var_goods is the variable name; using \u0026#34;Good\u0026#34; is similar to using \u0026#34;int\u0026#34; Defining and declaring a struct in C++: 1 2 3 4 5 6 7 8 struct goods //Product { char name[20]; //Product name float price; //Unit price float weight; //Weight }; goods var_goods; // goods is the type name, var_goods is the variable name In C++, both struct and class are essentially classes, so they are used similarly and typedef is not needed.\nThe difference between -\u0026gt; and . with structs\nTo quote Lion Yang from Zhihu:\nSimply put, one is a shortcut, and the other is syntactic sugar.\nLet\u0026rsquo;s illustrate with code:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include \u0026lt;iostream\u0026gt; using namespace std; struct goods //Product { char name[20]; //Product name float price; //Unit price float weight; //Weight }; int main(void) { goods s; //Struct instance goods* p; //Struct pointer p = new goods; s.price = 10; p-\u0026gt;price = 10; if ((\u0026amp;s)-\u0026gt;price == (*p).price) { cout \u0026lt;\u0026lt; \u0026#34;Prices are equal!\u0026#34; \u0026lt;\u0026lt; endl; } delete p; } As you can see, we used s.price and p-\u0026gt;price for assignment, and we could also use (\u0026amp;s)-\u0026gt;price and (*p).price for reading. Fundamentally, there is not much difference — in memory, both work by offsetting from the struct\u0026rsquo;s base address to reach the desired memory location for reading or writing data. However, there is a subtle distinction in usage: the left operand of -\u0026gt; must be a struct pointer variable, while the left operand of . must be a struct variable.\nThe output of the above program is:\n1 Prices are equal! These two operators are leftovers from history. Back then, programming was mostly done from the machine\u0026rsquo;s perspective, and the code was closer to machine logic than to human intuition. If machines could think, (*p).i would be like counting i characters forward from p on a number line; whereas p.i would require finding p\u0026rsquo;s position first before counting. Machines exist to serve humans, so the -\u0026gt; operator was introduced as a more intuitive alternative. Using -\u0026gt; lets us express (*p).i concisely as p-\u0026gt;i, which became the pragmatic compromise.\nReferences What\u0026rsquo;s the difference between \u0026ldquo;.\u0026rdquo; and \u0026ldquo;-\u0026gt;\u0026rdquo; in C? ","permalink":"https://www.333rd.net/en/posts/read/structs-in-c-and-cpp/","summary":"\u003ch1 id=\"on-structs\"\u003eOn Structs\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eA struct is a quintessential example of an abstraction born out of engineering necessity — it was the demand that chose the struct, not the struct that created the demand.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-what-is-a-struct\"\u003e1. What Is a Struct?\u003c/h2\u003e\n\u003cp\u003eWikipedia\u0026rsquo;s \u003ca href=\"https://zh.wikipedia.org/wiki/%E7%BB%93%E6%9E%84%E4%BD%93_%28C%E8%AF%AD%E8%A8%80%29\" title=\"struct\"\u003estruct\u003c/a\u003e article describes it as follows:\u003c/p\u003e\n\u003cblockquote\u003e\n\u003cp\u003eIn C, a struct (structure) is a data structure that falls under the category of composite (aggregate) data types.\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eIn plain language, a struct is a composite data type created by combining basic data types as needed.\u003c/p\u003e","title":"A Few Things About Structs"},{"content":"What Is a Function Pointer? Pointers are one of the most important concepts in C/C++, and also one of the hardest to grasp. Their purpose can be roughly explained with this analogy: just as we can find a friend\u0026rsquo;s new address after they move by looking up their street number, we can use a pointer to locate and access data at a specific address in memory. We won\u0026rsquo;t dive deeper into pointers here, though — we\u0026rsquo;ll focus specifically on function pointers.\n1. What Is a Function Pointer? Data types such as int and float aren\u0026rsquo;t the only things you can work with using pointers in C/C++. Functions can also be manipulated via pointers.\nWhen you define a function in a program, the compiler allocates a block of storage for that function\u0026rsquo;s code. The starting address of that block is called the function\u0026rsquo;s address. The function name itself represents this address. Since it\u0026rsquo;s an address, we can store it in a pointer variable. This pointer variable is called a function pointer variable, or simply a function pointer.\nIts declaration looks like this:\n1 2 //函数返回值类型 (* 指针变量名) (函数参数列表); int (*function_ptr_name) (int); 2. Why Do Function Pointers Exist? Let\u0026rsquo;s start with a piece of code:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 #include \u0026lt;iostream\u0026gt; using namespace std; class Animal { public: void Eat(string food, void(*ClassificationOfAnimals)(string)) { ClassificationOfAnimals(food); cout \u0026lt;\u0026lt; \u0026#34;非常美味！\u0026#34; \u0026lt;\u0026lt; endl; } }; //草食动物-\u0026gt;羊 void Sheep(string food) { cout \u0026lt;\u0026lt; \u0026#34;羊吃\u0026#34; \u0026lt;\u0026lt; food \u0026lt;\u0026lt; endl; } //肉食动物-\u0026gt;狼 void Wolf(string food) { cout \u0026lt;\u0026lt; \u0026#34;狼吃\u0026#34; \u0026lt;\u0026lt; food \u0026lt;\u0026lt; endl; } //杂食动物-\u0026gt;人 void Human(string food) { cout \u0026lt;\u0026lt; \u0026#34;人吃\u0026#34; \u0026lt;\u0026lt; food \u0026lt;\u0026lt; endl; } int main(void) { Animal animal; animal.Eat(\u0026#34;草\u0026#34;, Sheep); animal.Eat(\u0026#34;羊\u0026#34;, Wolf); animal.Eat(\u0026#34;青椒肉丝\u0026#34;, Human); cout \u0026lt;\u0026lt; \u0026#34;大家都吃饱了！\u0026#34; \u0026lt;\u0026lt; endl; } The output is:\n1 2 3 4 5 6 7 羊吃草 非常美味！ 狼吃羊 非常美味！ 人吃青椒肉丝 非常美味！ 大家都吃饱了！ Here I defined an Animal class with an Eat method, and three different feeding functions for different animals. The Eat method uses a function pointer to call the appropriate feeding function. When the food is grass, it calls the sheep\u0026rsquo;s feeding function; when the food is sheep, it calls the wolf\u0026rsquo;s feeding function; and so on. We can extend this to more animals, or even group them into herbivore, carnivore, and omnivore categories. Yet we only need a single class and a single method — that\u0026rsquo;s the power of function pointers.\nWithout function pointers, for functions with fixed return types and parameter types but differing behavior, you\u0026rsquo;d need to create a subclass for each variation to handle ever-changing requirements. But with function pointers, you simply declare a function pointer inside a method as an interface to call various pre-written functions. A single class handles the entire task — this is what\u0026rsquo;s known as abstraction and encapsulation. Function pointers turn flow-based functions into variables that can be passed as arguments, making the code far more elegant.\nAnother classic application of function pointers is callback functions. Consider the example above: for different foods, I used different feeding functions. Sheep, wolves, and humans are all hungry, but sheep don\u0026rsquo;t like shredded pork with green peppers, and humans don\u0026rsquo;t eat wolves. They ask you to find food, and when you find it, you call them back (callback). You find grass and call the sheep back to execute its feeding function. Now you\u0026rsquo;ve found sheep, so it\u0026rsquo;s time to call the wolf back to do something…\nThose feeding functions are called callback functions, and you act as the Eat method of the Animal class. When they ask you to find food, that\u0026rsquo;s called registering a callback function. When you find the food, that\u0026rsquo;s triggering the associated event. When you call them back to eat, that\u0026rsquo;s invoking the callback function. And when they come to eat, that\u0026rsquo;s responding to the callback event.\n3. How to Use Function Pointers Refer to the code below:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 #include \u0026lt;iostream\u0026gt; using namespace std; //返回二者最大值 int Max(int x, int y) { return x \u0026gt; y ? x : y; } int main(void) { //指针function_ptr被初始化为指向函数Max所在的地址 int (*function_ptr)(int, int) = Max; int a = 22, b = 33, c = 44; cout \u0026lt;\u0026lt; \u0026#34;a、b中的最大值为：\u0026#34; \u0026lt;\u0026lt; function_ptr(a, b) \u0026lt;\u0026lt; endl; cout \u0026lt;\u0026lt; \u0026#34;a、b、c中的最大值为：\u0026#34; \u0026lt;\u0026lt; function_ptr(function_ptr(a, b), c) \u0026lt;\u0026lt; endl; } The output is:\n1 2 a、b中的最大值为：33 a、b、c中的最大值为：44 As you can see, the usage is no different from calling a regular function. But that doesn\u0026rsquo;t mean a function pointer is equivalent to a function name! A function name is just a symbol that gets replaced by the function\u0026rsquo;s entry address at compile time and remains fixed during execution. A function pointer, on the other hand, needs additional memory — word length — to store the function\u0026rsquo;s entry address, and its value can change during program execution.\nReferences Function Pointers: Definition and Usage What Are Function Pointers Used For in C++? What Are Callback Functions? ","permalink":"https://www.333rd.net/en/posts/read/function-pointers/","summary":"\u003ch1 id=\"what-is-a-function-pointer\"\u003eWhat Is a Function Pointer?\u003c/h1\u003e\n\u003cp\u003ePointers are one of the most important concepts in C/C++, and also one of the hardest to grasp. Their purpose can be roughly explained with this analogy: just as we can find a friend\u0026rsquo;s new address after they move by looking up their street number, we can use a \u003cstrong\u003epointer\u003c/strong\u003e to locate and access \u003cstrong\u003edata\u003c/strong\u003e at a specific address in memory. We won\u0026rsquo;t dive deeper into pointers here, though — we\u0026rsquo;ll focus specifically on function pointers.\u003c/p\u003e","title":"What Is a Function Pointer?"},{"content":"Adding Mermaid Diagrams to a Hugo Blog As a diagramming library officially supported by GitHub, mermaid is undoubtedly worth a try, whether it serves as a supplementary illustration for the text or adds a touch of flexibility to the layout. Next, using the method shared by Sulv, I\u0026rsquo;ll introduce how to use mermaid for diagramming in Hugo through Shortcodes.\nSteps 1. Add and edit mermaid.html Add and edit layouts/shortcodes/mermaid.html in the site root directory.\n1 2 mkdir layouts/shortcodes -p \\ \u0026amp;\u0026amp; nano layouts/shortcodes/mermaid.html Add the following content:\n1 2 3 \u0026lt;div class=\u0026#34;mermaid\u0026#34;\u0026gt; {{ .Inner }} \u0026lt;/div\u0026gt; 2. Modify extend_head.html In the site root directory, use the following command to copy extend_head.html to the layouts/partials directory and edit it:\n1 2 cp themes/PaperMod/layouts/partials/extend_head.html layouts/partials/extend_head.html \\ \u0026amp;\u0026amp; nano layouts/partials/extend_head.html Add the following content:\n1 2 3 4 5 6 7 8 9 10 {{ if or .Params.mermaid .Site.Params.mermaid }} \u0026lt;!-- MermaidJS support --\u0026gt; \u0026lt;script src=\u0026#34;https://cdn.bootcdn.net/ajax/libs/mermaid/9.1.6/mermaid.min.js\u0026#34;\u0026gt;\u0026lt;/script\u0026gt; \u0026lt;script\u0026gt; mermaid.initialize({ startOnLoad: true, theme: localStorage.getItem(\u0026#34;pref-theme\u0026#34;) === \u0026#34;dark\u0026#34; ? \u0026#34;dark\u0026#34; : \u0026#34;forest\u0026#34; // 自动匹配默认或夜间模式主题。 }); \u0026lt;/script\u0026gt; {{ end }} 3. Usage Add the following field to the front matter of the article in which you want to use mermaid for diagramming:\n1 mermaid: true #是否开启mermaid You can then use mermaid in the form of Shortcodes in the article body. At the same time, you can add the above setting to archetypes/defaults.md, so that the field is automatically added every time a new article is created. Also, changing true to false will disable mermaid diagramming.\nAn example of using this Shortcodes for diagramming is as follows:\n1 2 3 4 {{\u0026lt;mermaid\u0026gt;}} flowchart LR a --\u0026gt; b \u0026amp; c --\u0026gt; d {{\u0026lt;/mermaid\u0026gt;}} The result is as follows: flowchart LR a --\u003e b \u0026 c --\u003e d You can wrap this code with HTML to center it or apply other layouts:\n1 2 3 4 5 6 7 8 \u0026lt;div align=center\u0026gt; {{\u0026lt;mermaid\u0026gt;}} flowchart LR a --\u0026gt; b \u0026amp; c --\u0026gt; d {{\u0026lt;/mermaid\u0026gt;}} \u0026lt;/div\u0026gt; The result is as follows:\nflowchart LR a --\u003e b \u0026 c --\u003e d Here is the mermaid usage tutorial:\nOfficial documentation\n4. Supplementary Notes The mermaid theme does not yet change in real time with the web page theme; after switching to dark mode, you need to refresh the page to see the effect.\nIn addition, previously mermaid.js was loaded asynchronously with async, but that can no longer be done now, because the file must be loaded before the mermaid theme is set. It is unclear whether this affects website performance; if you have a better suggestion, feel free to email me to make changes.\nThis is mermaid diagramming implemented with Shortcodes. Later I found another tutorial that does not require Shortcodes: Make the Hugo PaperMod theme support Mermaid, but it turned out to be invalid in my tests. If you are interested, you can also give it a try.\nReferences Adding Mermaid Diagrams to a Hugo Blog Mermaid theme configuration Add Mermaid to Hugo with Dark Mode ","permalink":"https://www.333rd.net/en/posts/tech/hugo-mermaid-shortcodes/","summary":"\u003ch1 id=\"adding-mermaid-diagrams-to-a-hugo-blog\"\u003eAdding Mermaid Diagrams to a Hugo Blog\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eAs a diagramming library officially supported by GitHub, \u003cstrong\u003emermaid\u003c/strong\u003e is undoubtedly worth a try, whether it serves as a supplementary illustration for the text or adds a touch of flexibility to the layout. Next, using the method shared by Sulv, I\u0026rsquo;ll introduce how to use \u003cstrong\u003emermaid\u003c/strong\u003e for diagramming in Hugo through \u003ccode\u003eShortcodes\u003c/code\u003e.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-add-and-edit-mermaidhtml\"\u003e1. Add and edit mermaid.html\u003c/h3\u003e\n\u003cp\u003eAdd and edit \u003cstrong\u003elayouts/shortcodes/mermaid.html\u003c/strong\u003e in the site root directory.\u003c/p\u003e","title":"Adding Mermaid Diagrams to Hugo with Shortcodes"},{"content":"Hugo Sidebar Table of Contents The PaperMod theme places the article\u0026rsquo;s table of contents at the top by default, which is not convenient for jumping around while reading. So, referring to Sulv\u0026rsquo;s settings, I modified it into a sidebar TOC.\nSteps 1. Modify toc.html In a previous article I mentioned that modifications made at the site root have higher priority and override the theme\u0026rsquo;s original content. So use the following commands to generate and edit toc.html:\n1 2 mkdir layouts/partials -p \\ \u0026amp;\u0026amp; nano layouts/partials/toc.html Paste the following content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 {{- $headers := findRE \u0026#34;\u0026lt;h[1-6].*?\u0026gt;(.|\\n])+?\u0026lt;/h[1-6]\u0026gt;\u0026#34; .Content -}} {{- $has_headers := ge (len $headers) 1 -}} {{- if $has_headers -}} \u0026lt;aside id=\u0026#34;toc-container\u0026#34; class=\u0026#34;toc-container wide\u0026#34;\u0026gt; \u0026lt;div class=\u0026#34;toc\u0026#34;\u0026gt; \u0026lt;details {{if (.Param \u0026#34;TocOpen\u0026#34;) }} open{{ end }}\u0026gt; \u0026lt;summary accesskey=\u0026#34;c\u0026#34; title=\u0026#34;(Alt + C)\u0026#34;\u0026gt; \u0026lt;span class=\u0026#34;details\u0026#34;\u0026gt;{{- i18n \u0026#34;toc\u0026#34; | default \u0026#34;Table of Contents\u0026#34; }}\u0026lt;/span\u0026gt; \u0026lt;/summary\u0026gt; \u0026lt;div class=\u0026#34;inner\u0026#34;\u0026gt; {{- $largest := 6 -}} {{- range $headers -}} {{- $headerLevel := index (findRE \u0026#34;[1-6]\u0026#34; . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{- if lt $headerLevel $largest -}} {{- $largest = $headerLevel -}} {{- end -}} {{- end -}} {{- $firstHeaderLevel := len (seq (index (findRE \u0026#34;[1-6]\u0026#34; (index $headers 0) 1) 0)) -}} {{- $.Scratch.Set \u0026#34;bareul\u0026#34; slice -}} \u0026lt;ul\u0026gt; {{- range seq (sub $firstHeaderLevel $largest) -}} \u0026lt;ul\u0026gt; {{- $.Scratch.Add \u0026#34;bareul\u0026#34; (sub (add $largest .) 1) -}} {{- end -}} {{- range $i, $header := $headers -}} {{- $headerLevel := index (findRE \u0026#34;[1-6]\u0026#34; . 1) 0 -}} {{- $headerLevel := len (seq $headerLevel) -}} {{/* get id=\u0026#34;xyz\u0026#34; */}} {{- $id := index (findRE \u0026#34;(id=\\\u0026#34;(.*?)\\\u0026#34;)\u0026#34; $header 9) 0 }} {{- /* strip id=\u0026#34;\u0026#34; to leave xyz, no way to get regex capturing groups in hugo */ -}} {{- $cleanedID := replace (replace $id \u0026#34;id=\\\u0026#34;\u0026#34; \u0026#34;\u0026#34;) \u0026#34;\\\u0026#34;\u0026#34; \u0026#34;\u0026#34; }} {{- $header := replaceRE \u0026#34;\u0026lt;h[1-6].*?\u0026gt;((.|\\n])+?)\u0026lt;/h[1-6]\u0026gt;\u0026#34; \u0026#34;$1\u0026#34; $header -}} {{- if ne $i 0 -}} {{- $prevHeaderLevel := index (findRE \u0026#34;[1-6]\u0026#34; (index $headers (sub $i 1)) 1) 0 -}} {{- $prevHeaderLevel := len (seq $prevHeaderLevel) -}} {{- if gt $headerLevel $prevHeaderLevel -}} {{- range seq $prevHeaderLevel (sub $headerLevel 1) -}} \u0026lt;ul\u0026gt; {{/* the first should not be recorded */}} {{- if ne $prevHeaderLevel . -}} {{- $.Scratch.Add \u0026#34;bareul\u0026#34; . -}} {{- end -}} {{- end -}} {{- else -}} \u0026lt;/li\u0026gt; {{- if lt $headerLevel $prevHeaderLevel -}} {{- range seq (sub $prevHeaderLevel 1) -1 $headerLevel -}} {{- if in ($.Scratch.Get \u0026#34;bareul\u0026#34;) . -}} \u0026lt;/ul\u0026gt; {{/* manually do pop item */}} {{- $tmp := $.Scratch.Get \u0026#34;bareul\u0026#34; -}} {{- $.Scratch.Delete \u0026#34;bareul\u0026#34; -}} {{- $.Scratch.Set \u0026#34;bareul\u0026#34; slice}} {{- range seq (sub (len $tmp) 1) -}} {{- $.Scratch.Add \u0026#34;bareul\u0026#34; (index $tmp (sub . 1)) -}} {{- end -}} {{- else -}} \u0026lt;/ul\u0026gt; \u0026lt;/li\u0026gt; {{- end -}} {{- end -}} {{- end -}} {{- end }} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;#{{- $cleanedID -}}\u0026#34; aria-label=\u0026#34;{{- $header | plainify -}}\u0026#34;\u0026gt;{{- $header | safeHTML -}}\u0026lt;/a\u0026gt; {{- else }} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;#{{- $cleanedID -}}\u0026#34; aria-label=\u0026#34;{{- $header | plainify -}}\u0026#34;\u0026gt;{{- $header | safeHTML -}}\u0026lt;/a\u0026gt; {{- end -}} {{- end -}} \u0026lt;!-- {{- $firstHeaderLevel := len (seq (index (findRE \u0026#34;[1-6]\u0026#34; (index $headers 0) 1) 0)) -}} --\u0026gt; {{- $firstHeaderLevel := $largest }} {{- $lastHeaderLevel := len (seq (index (findRE \u0026#34;[1-6]\u0026#34; (index $headers (sub (len $headers) 1)) 1) 0)) }} \u0026lt;/li\u0026gt; {{- range seq (sub $lastHeaderLevel $firstHeaderLevel) -}} {{- if in ($.Scratch.Get \u0026#34;bareul\u0026#34;) (add . $firstHeaderLevel) }} \u0026lt;/ul\u0026gt; {{- else }} \u0026lt;/ul\u0026gt; \u0026lt;/li\u0026gt; {{- end -}} {{- end }} \u0026lt;/ul\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/details\u0026gt; \u0026lt;/div\u0026gt; \u0026lt;/aside\u0026gt; \u0026lt;script\u0026gt; let activeElement; let elements; window.addEventListener(\u0026#39;DOMContentLoaded\u0026#39;, function (event) { checkTocPosition(); elements = document.querySelectorAll(\u0026#39;h1[id],h2[id],h3[id],h4[id],h5[id],h6[id]\u0026#39;); // Make the first header active activeElement = elements[0]; const id = encodeURI(activeElement.getAttribute(\u0026#39;id\u0026#39;)).toLowerCase(); document.querySelector(`.inner ul li a[href=\u0026#34;#${id}\u0026#34;]`).classList.add(\u0026#39;active\u0026#39;); }, false); window.addEventListener(\u0026#39;resize\u0026#39;, function(event) { checkTocPosition(); }, false); window.addEventListener(\u0026#39;scroll\u0026#39;, () =\u0026gt; { // Check if there is an object in the top half of the screen or keep the last item active activeElement = Array.from(elements).find((element) =\u0026gt; { if ((getOffsetTop(element) - window.pageYOffset) \u0026gt; 0 \u0026amp;\u0026amp; (getOffsetTop(element) - window.pageYOffset) \u0026lt; window.innerHeight/2) { return element; } }) || activeElement elements.forEach(element =\u0026gt; { const id = encodeURI(element.getAttribute(\u0026#39;id\u0026#39;)).toLowerCase(); if (element === activeElement){ document.querySelector(`.inner ul li a[href=\u0026#34;#${id}\u0026#34;]`).classList.add(\u0026#39;active\u0026#39;); } else { document.querySelector(`.inner ul li a[href=\u0026#34;#${id}\u0026#34;]`).classList.remove(\u0026#39;active\u0026#39;); } }) }, false); const main = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--article-width\u0026#39;), 10); const toc = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--toc-width\u0026#39;), 10); const gap = parseInt(getComputedStyle(document.body).getPropertyValue(\u0026#39;--gap\u0026#39;), 10); function checkTocPosition() { const width = document.body.scrollWidth; if (width - main - (toc * 2) - (gap * 4) \u0026gt; 0) { document.getElementById(\u0026#34;toc-container\u0026#34;).classList.add(\u0026#34;wide\u0026#34;); } else { document.getElementById(\u0026#34;toc-container\u0026#34;).classList.remove(\u0026#34;wide\u0026#34;); } } function getOffsetTop(element) { if (!element.getClientRects().length) { return 0; } let rect = element.getBoundingClientRect(); let win = element.ownerDocument.defaultView; return rect.top + win.pageYOffset; } \u0026lt;/script\u0026gt; {{- end }} Press CTRL+X, type y to save, and press Enter to exit.\n2. Modify the css file Use the following commands to generate and edit blank.css:\n1 2 mkdir assets/css/extended -p \\ \u0026amp;\u0026amp; nano assets/css/extended/blank.css Paste the following content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 :root { --nav-width: 1380px; --article-width: 650px; --toc-width: 300px; } .toc { margin: 0 2px 40px 2px; border: 1px solid var(--border); background: var(--entry); border-radius: var(--radius); padding: 0.4em; } .toc-container.wide { position: absolute; height: 100%; border-right: 1px solid var(--border); left: calc((var(--toc-width) + var(--gap)) * -1); top: calc(var(--gap) * 2); width: var(--toc-width); } .wide .toc { position: sticky; top: var(--gap); border: unset; background: unset; border-radius: unset; width: 100%; margin: 0 2px 40px 2px; } .toc details summary { cursor: zoom-in; margin-inline-start: 20px; padding: 12px 0; } .toc details[open] summary { font-weight: 500; } .toc-container.wide .toc .inner { margin: 0; } .active { font-size: 110%; font-weight: 600; } .toc ul { list-style-type: circle; } .toc .inner { margin: 0 0 0 20px; padding: 0px 15px 15px 20px; font-size: 16px; } .toc li ul { margin-inline-start: calc(var(--gap) * 0.5); list-style-type: none; } .toc li { list-style: none; font-size: 0.95rem; padding-bottom: 5px; } .toc li a:hover { color: var(--secondary); } Save and exit with the same steps as above — the sidebar TOC modification is now complete.\nIf you also want the long-press image zoom feature, you can continue adding the following to blank.css:\n1 2 3 img:active { transform: scale(1.35,1.35) } References Hugo blog TOC on the side | PaperMod theme ","permalink":"https://www.333rd.net/en/posts/tech/hugo-sidebar-toc/","summary":"\u003ch1 id=\"hugo-sidebar-table-of-contents\"\u003eHugo Sidebar Table of Contents\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThe PaperMod theme places the article\u0026rsquo;s table of contents at the top by default, which is not convenient for jumping around while reading. So, referring to Sulv\u0026rsquo;s settings, I modified it into a sidebar TOC.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-modify-tochtml\"\u003e1. Modify toc.html\u003c/h3\u003e\n\u003cp\u003eIn a previous article I mentioned that modifications made at the site root have higher priority and override the theme\u0026rsquo;s original content. So use the following commands to generate and edit \u003ccode\u003etoc.html\u003c/code\u003e:\u003c/p\u003e","title":"Hugo Sidebar Table of Contents"},{"content":"The yield Keyword in Python This article avoids heavy jargon and introduces the topic in plain language. If you have any hardware development experience, you may already find the yield keyword familiar — it behaves much like an interrupt on a microcontroller.\n1. What is yield? As a software developer you are probably well acquainted with return: it terminates a function and passes back a value. yield does something similar — it suspends a function and passes back a value. Note the word suspends; that is the key difference. You can resume execution right where it left off (i.e. at the yield statement) by calling the next method. Sound a lot like a hardware interrupt?\nA function that contains yield can also serve as a generator — it produces an iterable object that you can iterate over with an iterator.\n2. Why does yield exist? Imagine you are eating when suddenly your phone rings — it is your boss calling. You stop eating, take the call, and then resume eating afterward. The call has a higher priority than eating, so you respond immediately. That is essentially an interrupt.\ngraph TB subgraph Function a[Eating] --\u003e|Nothing happens| b[Eating] end subgraph Interrupt source a --\u003e|Phone rings| c[Take call] --\u003e|Call ends| b end In programming, this pattern is called a coroutine. During function execution, when something requires waiting or has a higher priority, the current execution is paused, the other work is completed, and then the function continues.\nConsider another scenario: a function that keeps computing and returning results. You could collect the results in a list and return it, but if the number of computations is enormous the list grows very large and consumes a lot of memory.\nWith yield, each result is returned one at a time. Iterating over the generator consumes far less memory and is more flexible to use.\n3. Why should you care about yield? Starting with Python 3.5, the asynchronous coroutine features in asyncio gained the new async / await syntax — a cleaner sugar coating that makes asynchronous code much more readable. Although yield is used less often now, there is a big difference between \u0026ldquo;knowing but not using\u0026rdquo; and \u0026ldquo;not knowing at all.\u0026rdquo; It is still worth understanding.\nReferences What are the usages and purposes of Python\u0026rsquo;s yield keyword? A brief analysis of Python yield usage Detailed explanation of yield usage in Python ","permalink":"https://www.333rd.net/en/posts/read/python-yield-keyword/","summary":"\u003ch1 id=\"the-yield-keyword-in-python\"\u003eThe \u003ccode\u003eyield\u003c/code\u003e Keyword in Python\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThis article avoids heavy jargon and introduces the topic in plain language. If you have any hardware development experience, you may already find the \u003ccode\u003eyield\u003c/code\u003e keyword familiar — it behaves much like an \u003cstrong\u003einterrupt\u003c/strong\u003e on a microcontroller.\u003c/em\u003e\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"1-what-is-yield\"\u003e1. What is \u003ccode\u003eyield\u003c/code\u003e?\u003c/h2\u003e\n\u003cp\u003eAs a software developer you are probably well acquainted with \u003ccode\u003ereturn\u003c/code\u003e: it terminates a function and passes back a value. \u003ccode\u003eyield\u003c/code\u003e does something similar — it suspends a function and passes back a value. Note the word \u003cstrong\u003esuspends\u003c/strong\u003e; that is the key difference. You can resume execution right where it left off (i.e. at the \u003ccode\u003eyield\u003c/code\u003e statement) by calling the \u003ccode\u003enext\u003c/code\u003e method. Sound a lot like a hardware interrupt?\u003c/p\u003e","title":"The yield Keyword in Python"},{"content":"Creating MMD with Blender Tutorial This article introduces:\nThe installation steps of the MMD plugins Cats and MMD Tools; How to use the localization plugin BLT; How to use the plugins to import MMD assets. Steps 1. Plugin Download Plugin download pages: Cats, MMD Tools, BLT. The downloaded plugins should be in compressed archive format.\n2. Plugin Installation Enter Blender and open: Edit-\u0026gt;Preferences-\u0026gt;Add-ons; Click the Install button in the top-right corner of the popup window; Find the downloaded plugin archive, select it and install the plugin. After installing, remember to check the checkbox in the add-ons page to enable the plugin, then restart Blender. Plugin Installation 3. Plugin Usage Tips Translation plugin: it generally translates automatically once installed. If it does not work, open: Edit-\u0026gt;Preferences-\u0026gt;Add-ons, search for BLT, and enable global translation as shown below.\nEnable Global Translation Import the model, motions and other required assets as shown in the figure, and split them apart for later use.\nImport Assets References Cats MMD Tools BLT Thanks to the Blender Foundation and to the contributors of the plugins above!\n","permalink":"https://www.333rd.net/en/posts/tech/blender-mmd-tutorial/","summary":"\u003ch1 id=\"creating-mmd-with-blender-tutorial\"\u003eCreating MMD with Blender Tutorial\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eThis article introduces:\u003c/em\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cem\u003eThe installation steps of the MMD plugins \u003cstrong\u003eCats\u003c/strong\u003e and \u003cstrong\u003eMMD Tools\u003c/strong\u003e;\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003e\u003cem\u003eHow to use the localization plugin \u003cstrong\u003eBLT\u003c/strong\u003e;\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003e\u003cem\u003eHow to use the plugins to import MMD assets.\u003c/em\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-plugin-download\"\u003e1. Plugin Download\u003c/h3\u003e\n\u003cp\u003ePlugin download pages: \u003ca href=\"https://github.com/absolute-quantum/cats-blender-plugin\" title=\"Cats\"\u003eCats\u003c/a\u003e, \u003ca href=\"https://mmd-blender.fandom.com/wiki/MMD_Tools\" title=\"MMD Tools\"\u003eMMD Tools\u003c/a\u003e, \u003ca href=\"https://www.blendermagic.cn/#/\" title=\"BLT\"\u003eBLT\u003c/a\u003e. The downloaded plugins should be in \u003cstrong\u003ecompressed archive\u003c/strong\u003e format.\u003c/p\u003e\n\u003ch3 id=\"2-plugin-installation\"\u003e2. Plugin Installation\u003c/h3\u003e\n\u003cul\u003e\n\u003cli\u003eEnter Blender and open: \u003cstrong\u003eEdit-\u0026gt;Preferences-\u0026gt;Add-ons\u003c/strong\u003e;\u003c/li\u003e\n\u003cli\u003eClick the \u003cstrong\u003eInstall\u003c/strong\u003e button in the top-right corner of the popup window;\u003c/li\u003e\n\u003cli\u003eFind the downloaded plugin archive, select it and install the plugin.\u003c/li\u003e\n\u003cli\u003eAfter installing, remember to \u003cstrong\u003echeck the checkbox\u003c/strong\u003e in the add-ons page to enable the plugin, then restart Blender.\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003e\n\n\n\u003cfigure class=\"zoom-container\"\u003e\n    \u003cimg src=\"https://img.333rd.net/%E6%8A%80%E6%9C%AF/%E4%BD%BF%E7%94%A8Blender%E5%88%B6%E4%BD%9CMMD/%E6%8F%92%E4%BB%B6%E5%AE%89%E8%A3%85.png\" \n         alt=\"Plugin Installation\" title=\"Plugin Installation\"loading=\"lazy\" /\u003e\n    \n    \u003cfigcaption\u003ePlugin Installation\u003c/figcaption\u003e\u003c/figure\u003e\n\u003c/p\u003e","title":"Creating MMD with Blender"},{"content":"Building a Personal Blog with Hugo (2) Picking up where we left off, the previous post briefly covered how to install and use Hugo. But stopping right there would feel a bit thin.\nTo ensure a good writing experience and stay focused on producing content, it\u0026rsquo;s worth spending more time up front — only then will you get twice the result with half the effort later. So this post goes deeper into optimization.\nI\u0026rsquo;d like to thank the bigshot Sulv for the generous help. His blog: https://www.sulvblog.cn/\nHugo Directory Overview First, let me introduce the directories that Hugo generates when creating a new site and what they do, so you have a sense of direction for the operations below. A website generated with Hugo contains the following files and folders:\narchetypes: stores .md template files; this folder takes priority over the /archetypes folder in the theme config.toml: the configuration file content: stores all the content of the site data: stores data files for templates to use layouts: stores .html templates; this folder takes priority over the /layouts folder in the theme static: stores static files such as images, css, and js; files in this directory are copied directly to /public, and this folder takes priority over the /static folder in the theme themes: stores themes public: stores the generated static files after running the hugo command Steps 1. Edit config.yml The config.yml file is the configuration for the whole site. It tells Hugo how to build the site, such as global parameters and menus. The file defaults to toml format; in the previous article, the -f yml argument in the Generate Site section of the commands changed the default configuration file to yaml format.\nThe default configuration provided by the official docs: Sample config.yml For the meaning of the parameters, refer to the theme Wiki: Features, Variables, as well as the relevant content in the official Hugo docs: Configure Hugo. The configuration file used here is the one given by Sulv; thanks again to Sulv. The content of the configuration file needs to be modified according to your actual situation; the main thing is to change site identifiers such as links and titles to your own. Also, logo.gif should be placed at static/img/logo.gif in the site\u0026rsquo;s root directory. 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 baseURL: https://www.sulvblog.cn # baseURL: https://www.sulvblog.cn # 绑定的域名 languageCode: zh-cn # en-us title: Sulv\u0026#39;s Blog theme: PaperMod # 主题名字，和themes文件夹下的一致 enableInlineShortcodes: true enableEmoji: true # 允许使用 Emoji 表情，建议 true enableRobotsTXT: true # 允许爬虫抓取到搜索引擎，建议 true hasCJKLanguage: true # 自动检测是否包含 中文日文韩文 如果文章中使用了很多中文引号的话可以开启 buildDrafts: false buildFuture: false buildExpired: false #googleAnalytics: UA-123-45 # 谷歌统计 # Copyright: Sulv paginate: 10 # 首页每页显示的文章数 minify: disableXML: true # minifyOutput: true permalinks: post: \u0026#34;/:title/\u0026#34; # post: \u0026#34;/:year/:month/:day/:title/\u0026#34; defaultContentLanguage: zh # 最顶部首先展示的语言页面 defaultContentLanguageInSubdir: true languages: zh: languageName: \u0026#34;Chinese\u0026#34; # contentDir: content/english weight: 1 profileMode: enabled: true title: (〃\u0026#39;▽\u0026#39;〃) subtitle: \u0026#34;🧨学而时习之，不亦说乎？有朋自远方来，不亦乐乎？\u0026lt;/br\u0026gt;👏🏼欢迎光临素履(Sulv)的博客\u0026lt;/br\u0026gt;👇联系方式\u0026#34; imageUrl: \u0026#34;img/logo.gif\u0026#34; imageTitle: imageWidth: 150 imageHeight: 150 buttons: - name: 👨🏻‍💻技术 url: posts/tech - name: 📕阅读 url: posts/read - name: 🏖生活 url: posts/life # - name: 🌹素履的博客 # url: https://www.xyming108.top menu: main: - identifier: search name: 🔍搜索 url: search weight: 1 - identifier: home name: 🏠主页 url: / weight: 2 - identifier: posts name: 📚文章 url: posts weight: 3 # - identifier: tech # name: 👨🏻‍💻技术文章 # url: posts/tech # weight: 5 # - identifier: life # name: 🏖记录生活 # url: posts/life # weight: 6 - identifier: archives name: ⏱时间轴 url: archives/ weight: 20 # - identifier: categories # name: 🧩分类 # url: categories # weight: 30 - identifier: tags name: 🔖标签 url: tags weight: 40 - identifier: about name: 🙋🏻‍♂️关于 url: about weight: 50 - identifier: links name: 🤝友链 url: links weight: 60 outputs: home: - HTML - RSS - JSON params: env: production # to enable google analytics, opengraph, twitter-cards and schema. # description: \u0026#34;这是一个纯粹的博客......\u0026#34; author: Sulv # author: [\u0026#34;Me\u0026#34;, \u0026#34;You\u0026#34;] # multiple authors defaultTheme: auto # defaultTheme: light or dark disableThemeToggle: false DateFormat: \u0026#34;2006-01-02\u0026#34; ShowShareButtons: true ShowReadingTime: true # disableSpecialistPost: true displayFullLangName: true ShowPostNavLinks: true ShowBreadCrumbs: true ShowCodeCopyButtons: true hideFooter: false # 隐藏页脚 ShowWordCounts: true VisitCount: true ShowLastMod: true #显示文章更新时间 ShowToc: true # 显示目录 TocOpen: true # 自动展开目录 comments: true socialIcons: - name: github url: \u0026#34;https://github.com/xyming108\u0026#34; - name: twitter url: \u0026#34;img/twitter.png\u0026#34; - name: facebook url: \u0026#34;https://www.facebook.com/profile.php?id=100027782410997\u0026#34; - name: instagram url: \u0026#34;img/instagram.png\u0026#34; - name: QQ url: \u0026#34;img/qq.png\u0026#34; - name: WeChat url: \u0026#34;img/wechat.png\u0026#34; # - name: Phone # url: \u0026#34;img/phone.png\u0026#34; - name: email url: \u0026#34;mailto:1931559710@qq.com\u0026#34; - name: RSS url: \u0026#34;index.xml\u0026#34; # editPost: # URL: \u0026#34;https://github.com/adityatelange/hugo-PaperMod/tree/exampleSite/content\u0026#34; # Text: \u0026#34;Suggest Changes\u0026#34; # edit text # appendFilePath: true # to append file path to Edit link # label: # text: \u0026#34;Home\u0026#34; # icon: icon.png # iconHeight: 35 # analytics: # google: # SiteVerificationTag: \u0026#34;XYZabc\u0026#34; assets: favicon: \u0026#34;img/logo.gif\u0026#34; favicon16x16: \u0026#34;img/logo.gif\u0026#34; favicon32x32: \u0026#34;img/logo.gif\u0026#34; apple_touch_icon: \u0026#34;logo.gif\u0026#34; safari_pinned_tab: \u0026#34;logo.gif\u0026#34; # cover: # hidden: true # hide everywhere but not in structured data # hiddenInList: true # hide on list pages and home # hiddenInSingle: true # hide on single page fuseOpts: isCaseSensitive: false shouldSort: true location: 0 distance: 1000 threshold: 1 minMatchCharLength: 0 keys: [\u0026#34;title\u0026#34;, \u0026#34;permalink\u0026#34;, \u0026#34;summary\u0026#34;] twikoo: version: 1.4.11 taxonomies: category: categories tag: tags series: series markup: goldmark: renderer: unsafe: true # HUGO 默认转义 Markdown 文件中的 HTML 代码，如需开启的话 highlight: # anchorLineNos: true codeFences: true guessSyntax: true lineNos: true # noClasses: false # style: monokai style: darcula # codeFences：代码围栏功能，这个功能一般都要设为 true 的，不然很难看，就是干巴巴的-代码文字，没有颜色。 # guessSyntax：猜测语法，这个功能建议设置为 true, 如果你没有设置要显示的语言则会自动匹配。 # hl_Lines：高亮的行号，一般这个不设置，因为每个代码块我们可能希望让高亮的地方不一样。 # lineNoStart：行号从编号几开始，一般从 1 开始。 # lineNos：是否显示行号，我比较喜欢显示，所以我设置的为 true. # lineNumbersInTable：使用表来格式化行号和代码,而不是 标签。这个属性一般设置为 true. # noClasses：使用 class 标签，而不是内嵌的内联样式 privacy: vimeo: disabled: false simple: true twitter: disabled: false enableDNT: true simple: true instagram: disabled: false simple: true youtube: disabled: false privacyEnhanced: true services: instagram: disableInlineCSS: true twitter: disableInlineCSS: true 2. Configure the 🔍 Search Page Original reference: Search Page\nPaperMod uses Fuse.js Basic to implement the search functionality\nThe search functionality is already enabled in the configuration file. You need to create a search.md containing the following content in the content/ directory in the site\u0026rsquo;s root. Additionally, you can customize it to your needs by referring to the theme documentation.\n1 2 3 4 5 6 7 8 --- title: \u0026#34;🔍搜索\u0026#34; # in any language you want layout: \u0026#34;search\u0026#34; # is necessary # url: \u0026#34;/archive\u0026#34; # description: \u0026#34;Description for Search\u0026#34; summary: \u0026#34;search\u0026#34; placeholder: \u0026#34;搜索框内的默认显示，自行修改\u0026#34; --- To hide a specific page from search, add the following to its front matter (the content enclosed by the two --- lines at the top):\n1 searchHidden: true 3. Configure the ⏱ Timeline Page Original reference: Archives Layout\nThe ⏱ Timeline page is the Archives Layout. The archive functionality is already enabled in the configuration file. You need to create an archives.md containing the following content in the content/ directory in the site\u0026rsquo;s root. Additionally, you can customize it to your needs by referring to the theme documentation.\n1 2 3 4 5 6 --- title: \u0026#34;⏱ Timeline\u0026#34; layout: \u0026#34;archives\u0026#34; url: \u0026#34;/archives/\u0026#34; summary: archives --- 4. Configure the 🔖 Tags Page The default language set in config.yml is Chinese, and support for other languages is not enabled. If needed, you can customize it yourself by referring to the official docs: Translations, Multilingual Mode . However, the 🔖 Tags page is not controlled by this setting, so you need to modify the corresponding html template yourself. Also, regarding the default language: the theme supports multilingual, meaning it provides some translations for its built-in page templates, e.g. Home -\u0026gt; 主页. The files that provide multilingual support are in themes/PaperMod/i18n/, where zh.yaml is Simplified Chinese. To prevent your modified files from being reverted when updating the theme, make the modifications in the site\u0026rsquo;s root directory, so they can override the theme\u0026rsquo;s original files. Once you understand the three points above, you can start the following modifications.\n4.1. Modify the default translations Create an i18n/ folder in the site\u0026rsquo;s root, copy themes/PaperMod/i18n/zh.yaml to i18n/zh.yaml, and edit it\n1 2 3 mkdir i18n \\ \u0026amp;\u0026amp; cp themes/PaperMod/i18n/zh.yaml i18n/zh.yaml \\ \u0026amp;\u0026amp; nano i18n/zh.yaml Change it to the following content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 - id: prev_page translation: \u0026#34;上一页\u0026#34; - id: next_page translation: \u0026#34;下一页\u0026#34; - id: read_time translation: one : \u0026#34;1 分钟\u0026#34; other: \u0026#34;{{ .Count }} 分钟\u0026#34; - id: toc translation: \u0026#34;目录\u0026#34; - id: translations translation: \u0026#34;语言\u0026#34; - id: home translation: \u0026#34;🏠主页\u0026#34; - id: edit_post translation: \u0026#34;编辑\u0026#34; - id: code_copy translation: \u0026#34;复制\u0026#34; - id: code_copied translation: \u0026#34;已复制！\u0026#34; 4.2. Modify the html template The template file for 🔖 Tags is themes/PaperMod/layouts/_default/terms.html. Follow the same steps as above: create a layouts/_default/ folder in the site\u0026rsquo;s root, copy themes/PaperMod/layouts/_default/terms.html to layouts/_default/terms.html, and edit it\n1 2 3 mkdir layouts/_default \\ \u0026amp;\u0026amp; cp themes/PaperMod/layouts/_default/terms.html layouts/_default/terms.html \\ \u0026amp;\u0026amp; nano layouts/_default/terms.html Change it to the following content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 {{- define \u0026#34;main\u0026#34; }} {{- if .Title }} \u0026lt;header class=\u0026#34;page-header\u0026#34;\u0026gt; {{- if eq .Title \u0026#34;Categories\u0026#34; }} \u0026lt;h1\u0026gt;🧩{{ .Title }}\u0026lt;/h1\u0026gt; {{- end }} {{- if eq .Title \u0026#34;Tags\u0026#34; }} \u0026lt;h1\u0026gt;🔖{{ \u0026#34;标签\u0026#34; }}\u0026lt;/h1\u0026gt; \u0026lt;!-- \u0026lt;h1\u0026gt;🔖{{ .Title }}\u0026lt;/h1\u0026gt; --\u0026gt; {{- end }} \u0026lt;!-- \u0026lt;h1\u0026gt;{{ .Title }}\u0026lt;/h1\u0026gt; --\u0026gt; {{- if .Description }} \u0026lt;div class=\u0026#34;post-description\u0026#34;\u0026gt; {{ .Description }} \u0026lt;/div\u0026gt; {{- end }} \u0026lt;/header\u0026gt; {{- end }} \u0026lt;!-- 原始 --\u0026gt; \u0026lt;ul class=\u0026#34;terms-tags\u0026#34;\u0026gt; {{- $type := .Type }} {{- range $key, $value := .Data.Terms.Alphabetical }} {{- $name := .Name }} {{- $count := .Count }} {{- with $.Site.GetPage (printf \u0026#34;/%s/%s\u0026#34; $type $name) }} \u0026lt;li\u0026gt; \u0026lt;a href=\u0026#34;{{ .Permalink }}\u0026#34;\u0026gt;{{ .Name }} \u0026lt;sup\u0026gt;\u0026lt;strong\u0026gt;\u0026lt;sup\u0026gt;{{ $count }}\u0026lt;/sup\u0026gt;\u0026lt;/strong\u0026gt;\u0026lt;/sup\u0026gt; \u0026lt;/a\u0026gt; \u0026lt;/li\u0026gt; {{- end }} {{- end }} \u0026lt;/ul\u0026gt; {{- end }}{{/* end main */ -}} 5. Customize the front matter Hugo provides a default template for generating articles. When you use hugo new content/posts/demo.md to generate a new page, it provides the most basic front matter, located at archetypes/default.md, with the following content:\n1 2 3 4 5 --- title: \u0026#34;{{ replace .Name \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#34; date: {{ .Date }} draft: true --- This template only contains the three required parameters: title, date, and draft (whether it is a draft?). For writing convenience, here is a template provided by the bigshot Sulv, which I have slightly modified.\nCreate a new archetypes/post.md and edit it,\n1 nano archetypes/post.md Paste the following content:\n1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 --- title: \u0026#34;{{ replace .Name \u0026#34;-\u0026#34; \u0026#34; \u0026#34; | title }}\u0026#34; date: {{ .Date }} lastmod: {{ .Date }} author: [\u0026#34;作者\u0026#34;] categories: - 分类1 - 分类2 tags: - 标签1 - 标签2 # summary-\u0026gt;在列表页展现的摘要内容，自动生成，内容默认前70个字符，可通过此参数自定义，一般无需专门设置 summary: \u0026#34;\u0026#34; # description-\u0026gt;需要自己编写的文章描述，是搜索引擎呈现在搜索结果链接下方的网页简介，建议设置 description: \u0026#34;\u0026#34; weight: # 输入1可以顶置文章，用来给文章展示排序，不填就默认按时间排序 slug: \u0026#34;\u0026#34; draft: false # 是否为草稿 comments: true showToc: true # 显示目录 TocOpen: true # 自动展开目录 hidemeta: false # 是否隐藏文章的元信息，如发布日期、作者等 disableShare: true # 底部不显示分享栏 showbreadcrumbs: true #顶部显示当前路径 cover: image: \u0026#34;\u0026#34; caption: \u0026#34;\u0026#34; alt: \u0026#34;\u0026#34; relative: false --- 此处内容将会出现在摘要（summary）里 \u0026lt;!--\\more--\u0026gt; # 此处的“\\”用于转义，否则无法正常显示，实际使用须删去。 此处开始为正文 6. Configure the 🙋🏻‍♂️ About Page In the previous section we configured the article generation template. Below I\u0026rsquo;ll show how to use the template to generate the 🙋🏻‍♂️ About page (about.md).\nFirst, generate about.md in the content/ directory\n1 hugo new --kind post content/about.md You\u0026rsquo;ll see an about.md with the template content; modify the front matter parameters according to the comments and your own needs.\nThen, starting at the very bottom, write the body using Markdown syntax to complete your own 🙋🏻‍♂️ About page\n7. Content Management from Novice to Pro The Hugo Directory Overview section introduced the paths in the site and their purposes. So where do we put our finished articles? The answer is the content/ directory.\nThe official docs Content Management explain in detail everything you need to know about managing blog content. Below I\u0026rsquo;ll cover what we need to know for now.\nOnce the blog is live, everything we see is Markdown files in the content/ directory. The content/ directory is the top-level directory that manages all the site\u0026rsquo;s content; it generally serves as the site\u0026rsquo;s homepage, with an access URL of: http://example.com. Also, files like about.md, archives.md, etc. are usually stored here. For a Markdown file created in the content/ directory, e.g. about.md, its access URL is: http://example.com/about/ . You may notice it has no extension. In Hugo the default behavior is to present content using Pretty URLs (Pretty URLs), i.e. the \u0026ldquo;.html\u0026rdquo; extension is omitted from links. If you have a peculiar taste, you can set uglyurls: true in config.yml to enable the Ugly URLs (Ugly URLs) environment variable, and the access URL will become: https://example.com/about.html/ For a directory created in the content/ directory, e.g. posts (by convention we store our blog articles under this path), its access URL is: http://example.com/posts/ , and other directories work the same way. In my blog, I created the following paths: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 content ├── about.md ├── archives.md ├── posts │ ├── _index.md │ ├── life │ │ └── _index.md │ ├── read │ │ └── _index.md │ └── tech │ ├── 1st.md │ ├── 2nd.md │ └── _index.md └── search.md Where posts/life/ corresponds to 🏖 Life; posts/read/ corresponds to 📕 Reading; posts/tech/ corresponds to 👨🏻‍💻 Tech. Let me emphasize here: _index.md. The official reference is Index Pages. In short, this file exists as a custom index page. Here\u0026rsquo;s one I use as a reference: 1 2 3 4 5 6 7 8 9 --- title: \u0026#34;📚文章\u0026#34; # description: \u0026#34;知识是学习来的，经验是总结来的。\u0026#34; hidemeta: true # 是否隐藏文章的元信息，如发布日期、作者等 --- 知识是学习来的，经验是总结来的。 \u0026lt;!--\\more--\u0026gt; # 此处的“\\”用于转义，否则无法正常显示，实际使用须删去。 Final Words At this point, the basic configuration of Hugo is essentially complete, and you can focus on creating. If you need more freedom or have other needs, you can also do deep customization. In the next section, I\u0026rsquo;ll introduce some Hugo writing workflows, a few additional usage tips, and share the script I use to back up Hugo with a Tianyi cloud drive.\nReferences PaperMod\u0026rsquo;s wiki Sulv\u0026rsquo;s Blog Customizing Taxonomy Pages in Hugo description and summary ","permalink":"https://www.333rd.net/en/posts/tech/deploying-a-hugo-blog-part-2/","summary":"\u003ch1 id=\"building-a-personal-blog-with-hugo-2\"\u003eBuilding a Personal Blog with Hugo (2)\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003ePicking up where we left off, the previous post briefly covered how to install and use Hugo. But stopping right there would feel a bit thin.\u003c/em\u003e\u003c/p\u003e\n\u003cp\u003e\u003cem\u003eTo ensure a good writing experience and stay focused on producing content, it\u0026rsquo;s worth spending more time up front — only then will you get twice the result with half the effort later. So this post goes deeper into optimization.\u003c/em\u003e\u003c/p\u003e","title":"Building a Personal Blog with Hugo (2)"},{"content":"Building a Personal Blog with Hugo (1) Hugo is a static website generator written in Go. Its features are as follows:\nSimple, easy to use, efficient, easy to extend, and quick to deploy. Hugo can be hosted with GitHub Pages. Steps 1. Install Hugo Download the binary or archive for your platform from Hugo Releases. The installation methods are as follows.\nGeneral\nUnpack and install the tar.gz archive: 1 2 3 tar -zxvf \u0026lt;your-platform\u0026gt;.tar.gz -C /usr/local/bin \\ \u0026amp;\u0026amp; cd /usr/local/bin \\ \u0026amp;\u0026amp; rm LICENSE README.md Debian-based\nInstall the .deb file (recommended):\n1 dpkg -i \u0026lt;your-platform\u0026gt;.deb Uninstalling is also easy; whether you need to keep the configuration files depends on your needs (it seems hugo doesn\u0026rsquo;t have a config file anyway XD).\n1 2 3 dpkg -l # List installed packages and find hugo\u0026#39;s package name. dpkg -r package-name # Remove the package but keep the configuration files. dpkg -P package-name # Remove the package and the configuration files. Install via APT (not recommended by the official docs because the version is older):\n1 apt install hugo Other systems: Refer to the official Installation Docs\nRun the following command; if version information is printed, the installation was successful.\n1 hugo version 2. Generate the Site 1 2 hugo new site /path/to/site -f yml \\ \u0026amp;\u0026amp; cd /path/to/site 3. Install a Theme This demo uses Hugo PaperMod. The installation guide is here, and we\u0026rsquo;ll go with Method 1 :\nJust run the command in the site\u0026rsquo;s root directory: 1 git clone https://github.com/adityatelange/hugo-PaperMod themes/PaperMod --depth=1 To update the theme: 1 2 cd themes/PaperMod git pull 4. Create a New Post Create an about page:\n1 hugo new about.md about.md is automatically generated at content/about.md. Open about.md and take a look:\n1 2 3 4 5 6 7 --- title: \u0026#34;About\u0026#34; date: 2022-01-22T10:45:17Z draft: true --- # Body The content is in Markdown format, and the content between the --- lines is in YAML format. Depending on your preference, you can switch to TOML format (using +++ delimiters) or JSON format. I personally recommend YAML.\nCreate your first post and put it in the post directory, so that aggregate pages can be generated later.\n1 hugo new posts/first.md You can now edit the content of first.md using Markdown syntax.\n5. Run Hugo and Go Live Run the following in the site\u0026rsquo;s root directory:\n1 hugo server --buildDrafts The command above is the default way to start. --buildDrafts controls whether draft posts are displayed. By default, only the local machine is allowed to access the server; if you want others to access it via IP, use the following command:\n1 hugo server -b \u0026#34;http://ip:8080\u0026#34; -p 8080 --bind \u0026#34;0.0.0.0\u0026#34; --gc -b http://ip:8080: because static resources default to localhost when other people access the site, this flag points to your own IP address and default port. -p 8080: the default port is 1313; this specifies port 8080. --bind \u0026quot;0.0.0.0\u0026quot;: the default startup only allows access from the local machine, so you need to bind it so that all machines can access the site. Official command documentation: Commands\nReferences Hugo Chinese Documentation Several Modes and Precautions for Starting the Hugo Server ","permalink":"https://www.333rd.net/en/posts/tech/deploying-a-hugo-blog-part-1/","summary":"\u003ch1 id=\"building-a-personal-blog-with-hugo-1\"\u003eBuilding a Personal Blog with Hugo (1)\u003c/h1\u003e\n\u003cp\u003e\u003cem\u003eHugo is a static website generator written in Go. Its features are as follows:\u003c/em\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cem\u003eSimple, easy to use, efficient, easy to extend, and quick to deploy.\u003c/em\u003e\u003c/li\u003e\n\u003cli\u003e\u003cem\u003eHugo can be hosted with GitHub Pages.\u003c/em\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003chr\u003e\n\u003ch2 id=\"steps\"\u003eSteps\u003c/h2\u003e\n\u003ch3 id=\"1-install-hugo\"\u003e1. Install Hugo\u003c/h3\u003e\n\u003cp\u003eDownload the binary or archive for your platform from \u003ca href=\"https://github.com/gohugoio/hugo/releases\" title=\"Hugo Releases\"\u003eHugo Releases\u003c/a\u003e. The installation methods are as follows.\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\n\u003cp\u003eGeneral\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eUnpack and install the tar.gz archive:\n\u003cdiv class=\"highlight\"\u003e\u003cdiv class=\"chroma\"\u003e\n\u003ctable class=\"lntable\"\u003e\u003ctr\u003e\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode\u003e\u003cspan class=\"lnt\"\u003e1\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e2\n\u003c/span\u003e\u003cspan class=\"lnt\"\u003e3\n\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\n\u003ctd class=\"lntd\"\u003e\n\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-bash\" data-lang=\"bash\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003etar -zxvf \u0026lt;your-platform\u0026gt;.tar.gz -C /usr/local/bin \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e  \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e \u003cspan class=\"nb\"\u003ecd\u003c/span\u003e /usr/local/bin \u003cspan class=\"se\"\u003e\\\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e    \u003cspan class=\"o\"\u003e\u0026amp;\u0026amp;\u003c/span\u003e rm LICENSE README.md\n\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/td\u003e\u003c/tr\u003e\u003c/table\u003e\n\u003c/div\u003e\n\u003c/div\u003e\u003c/li\u003e\n\u003c/ul\u003e\n\u003c/li\u003e\n\u003cli\u003e\n\u003cp\u003eDebian-based\u003c/p\u003e","title":"Building a Personal Blog with Hugo (1)"},{"content":"Arise, ye starvelings from your slumber\nArise, ye prisoners of want\nLet the blood in our veins boil; we shall fight for truth\nLet us sweep away the old world; slaves, arise, arise\nDo not say we have nothing; we shall be masters of the world\nThis is the final struggle; let us unite tomorrow\nThe Internationale will be the human race\nThis is the final struggle; let us unite tomorrow\nThe Internationale will be the human race\nThere are no saviors, neither gods nor emperors\nTo create human happiness, we rely entirely on ourselves\nWe shall reclaim the fruits of labor and break thought out of its cage\nMake the furnace glow red-hot; strike while the iron is hot to succeed\nThis is the final struggle; let us unite tomorrow\nThe Internationale will be the human race\nThis is the final struggle; let us unite tomorrow\nThe Internationale will be the human race\nThe most hateful beasts and vipers have consumed our flesh and blood\nOnce they are wiped out, the crimson sun will shine across the world\nThis is the final struggle; let us unite tomorrow\nThe Internationale will be the human race\nThis is the final struggle; let us unite tomorrow\nThe Internationale will be the human race\nThe Internationale will be the human race\n","permalink":"https://www.333rd.net/en/about/","summary":"\u003cp\u003eArise, ye starvelings from your slumber\u003c/p\u003e\n\u003cp\u003eArise, ye prisoners of want\u003c/p\u003e\n\u003cp\u003eLet the blood in our veins boil; we shall fight for truth\u003c/p\u003e\n\u003cp\u003eLet us sweep away the old world; slaves, arise, arise\u003c/p\u003e\n\u003cp\u003eDo not say we have nothing; we shall be masters of the world\u003c/p\u003e\n\u003cp\u003eThis is the final struggle; let us unite tomorrow\u003c/p\u003e\n\u003cp\u003eThe Internationale will be the human race\u003c/p\u003e\n\u003cp\u003eThis is the final struggle; let us unite tomorrow\u003c/p\u003e","title":"The Internationale"},{"content":" Lao Mi Loves Lua Why not try Lua too? LINUX DO A new ideal community Song of Good Work Better work, better mood. 95 Directory A directory and navigation site lurk Read whichever page the wind turns to Puo’s Garden A good personal learning garden Xiaodong Blog Xiaodong IT ","permalink":"https://www.333rd.net/en/links/","summary":"Links","title":"🤝Links"}]