<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Paul Contreras</title>
    <description>The latest articles on DEV Community by Paul Contreras (@paulcontr_).</description>
    <link>https://dev.to/paulcontr_</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1270579%2Ff5fc33eb-f21b-4e4c-a59d-0c60365864df.jpg</url>
      <title>DEV Community: Paul Contreras</title>
      <link>https://dev.to/paulcontr_</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/paulcontr_"/>
    <language>en</language>
    <item>
      <title>Why I Redesigned StrictBlock to Make Focus Feel Easier</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Sun, 21 Jun 2026 06:43:55 +0000</pubDate>
      <link>https://dev.to/paulcontr_/why-i-redesigned-strictblock-to-make-focus-feel-easier-50al</link>
      <guid>https://dev.to/paulcontr_/why-i-redesigned-strictblock-to-make-focus-feel-easier-50al</guid>
      <description>&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/0jAXUn527r4"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;I rebuilt StrictBlock (my app) from the ground up.&lt;/p&gt;

&lt;p&gt;StrictBlock is an iPhone app blocker and focus app designed to help people stop procrastinating, protect deep work, and build better focus habits.&lt;/p&gt;

&lt;p&gt;For this relaunch, I did not want to just “refresh the UI.” I wanted to redesign the full product experience around one question:&lt;/p&gt;

&lt;p&gt;How can I make starting a focus session feel simple, strict, and useful?&lt;/p&gt;

&lt;p&gt;The new version focuses on reducing friction. Users can create focus profiles for study, work, sleep, deep work, or Pomodoro sessions, then start blocking distracting apps and websites with less setup.&lt;/p&gt;

&lt;p&gt;I also redesigned the app around accountability. StrictBlock now includes streaks, trophies, weekly reports, widgets, session journaling, and consequences for ending sessions early.&lt;/p&gt;

&lt;p&gt;As a developer, this redesign was a good reminder that productivity apps are not only about features. They are about behavior. The UI, the flow, the defaults, and the friction all shape whether someone actually stays focused.&lt;/p&gt;

&lt;p&gt;StrictBlock is now live with a complete redesign.&lt;/p&gt;

&lt;p&gt;Would love feedback from other builders, iOS devs, and product engineers.&lt;/p&gt;

&lt;p&gt;Try it here: &lt;a href="https://apps.apple.com/app/apple-store/id6749779711?pt=127882070&amp;amp;ct=peerlist&amp;amp;mt=8" rel="noopener noreferrer"&gt;Download strictblock on appstore&lt;/a&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>showdev</category>
      <category>writing</category>
      <category>mobile</category>
    </item>
    <item>
      <title>Can You Beat Gemini Before Sunset? I Built a Solstice Puzzle Duel</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Sun, 21 Jun 2026 05:17:03 +0000</pubDate>
      <link>https://dev.to/paulcontr_/can-you-beat-gemini-before-sunset-i-built-a-solstice-puzzle-duel-3622</link>
      <guid>https://dev.to/paulcontr_/can-you-beat-gemini-before-sunset-i-built-a-solstice-puzzle-duel-3622</guid>
      <description>&lt;p&gt;&lt;em&gt;This is a submission for the &lt;a href="https://dev.to/challenges/june-game-jam-2026-06-03"&gt;June Solstice Game Jam&lt;/a&gt;&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What I Built
&lt;/h2&gt;

&lt;p&gt;I built &lt;strong&gt;Color Queens: Solstice Duel&lt;/strong&gt;, a small logic puzzle game where you race Gemini before the sun sets.&lt;/p&gt;

&lt;p&gt;The idea started with one simple question:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can human logic beat an AI in a puzzle duel?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Many games let players compete against bots, but I had rarely seen a game where the opponent is actually a LLM. That curiosity became the starting point for this project. I wanted to see what would happen if an LLM was not just powering dialogue behind the scenes, but actively participating in the gameplay as a rival.&lt;/p&gt;

&lt;p&gt;In the game, you and Gemini receive the same board. Your goal is to place queens across colored regions while following the rules: one queen per color, no attacks, no row conflicts, no column conflicts, and no diagonal conflicts.&lt;/p&gt;

&lt;p&gt;For the June Solstice theme, I added a light and shadow layer. The board is not only about placing queens correctly, it is also about balancing daylight before sunset. As time passes, the mood shifts from warm daylight into night, so the solstice becomes part of the pressure of the game.&lt;/p&gt;

&lt;p&gt;I also added Pride-inspired boards because Color Queens is already about color, identity, and pattern. I wanted those boards to feel joyful and intentional, not just decorative.&lt;/p&gt;

&lt;p&gt;The main goal was to make something that feels simple to understand, but still has a strong hook:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;You are not just solving a puzzle. You are dueling Gemini.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa30kh1h1uu9ggxy6hode.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa30kh1h1uu9ggxy6hode.png" alt="landing-demo" width="800" height="564"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Video Demo
&lt;/h2&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/h31tXgx1rZw"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The project is built as a web game so anyone can try it without installing anything.&lt;/p&gt;

&lt;p&gt;Live demo:&lt;br&gt;
&lt;a href="//colorqueens.xyz"&gt;Try now! colorqueens.xyz&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Source code: &lt;a href="https://github.com/pol-cova/colorqueens-game" rel="noopener noreferrer"&gt;https://github.com/pol-cova/colorqueens-game&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  How I Built It
&lt;/h2&gt;

&lt;p&gt;I built the game around a local puzzle engine.&lt;/p&gt;

&lt;p&gt;The most important decision was this:&lt;br&gt;
&lt;strong&gt;Gemini is a player, not the judge.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Gemini can suggest a move, but the game validates that move locally using the same rules that apply to the human player. If Gemini makes an illegal move, the board calls it out.&lt;/p&gt;

&lt;p&gt;That made the duel feel more interesting, because the AI is not treated as magically correct. It has to play by the rules too.&lt;/p&gt;

&lt;p&gt;The main systems are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A Color Queens board renderer&lt;/li&gt;
&lt;li&gt;A local move validator&lt;/li&gt;
&lt;li&gt;A light and shadow solstice rule&lt;/li&gt;
&lt;li&gt;A Gemini opponent mode&lt;/li&gt;
&lt;li&gt;A backtracking solver&lt;/li&gt;
&lt;li&gt;A Turing Tape replay system&lt;/li&gt;
&lt;li&gt;A small binary mini-game called Bombe Calibration&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;strong&gt;Turing Tape&lt;/strong&gt; was one of my favorite parts to build. After a puzzle, it shows how the solver thinks step by step: scanning regions, trying cells, rejecting conflicts, accepting candidates, and backtracking when a branch fails.&lt;/p&gt;

&lt;p&gt;I wanted the algorithm to feel visible, almost like watching the machine think.&lt;/p&gt;

&lt;p&gt;The &lt;strong&gt;Bombe Calibration&lt;/strong&gt; mini-game is a short binary challenge. Before unlocking a hint, the player taps a quick 0 and 1 sequence. It adds a little tension, sound, and movement while keeping the main game focused on logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prize Category
&lt;/h2&gt;

&lt;p&gt;I am submitting this for both additional prize categories.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Google AI Usage
&lt;/h3&gt;

&lt;p&gt;Gemini is used directly inside the game as an opponent.&lt;br&gt;
Instead of only using AI to generate text or assets, I wanted Gemini to become part of the core game loop. It receives the current board state, proposes a queen placement, and then the local validator checks whether that move is legal.&lt;/p&gt;

&lt;p&gt;That means Gemini can win, fail, recover, or get rejected by the rules.&lt;/p&gt;

&lt;p&gt;I liked this approach because it makes the AI interaction playable. It is not just a chatbot on the side. It is part of the duel.&lt;/p&gt;

&lt;h3&gt;
  
  
  Best Ode to Alan Turing
&lt;/h3&gt;

&lt;p&gt;The game connects to Alan Turing through logic, algorithms, and machine reasoning.&lt;/p&gt;

&lt;p&gt;The Turing Tape shows a backtracking solver working through the puzzle one step at a time. It checks constraints, rejects invalid cells, and searches for a valid solution.&lt;/p&gt;

&lt;p&gt;The Bombe Calibration mini-game is also a small nod to code-breaking and binary logic.&lt;/p&gt;

&lt;p&gt;I wanted the Turing tribute to live inside the mechanics, not just in the story text.&lt;/p&gt;

&lt;h2&gt;
  
  
  Final Thoughts
&lt;/h2&gt;

&lt;p&gt;This was a fun project because it mixed a few things I really care about: polished puzzle design, AI as part of the experience, and a simple idea that is easy to explain.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Can you beat Gemini before sunset?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;That is the whole challenge.&lt;/p&gt;

&lt;p&gt;Try the game, race the machine, and let me know who wins. Coming soon on the App Store. &lt;a href="//colorqueens.xyz"&gt;Join to the waitlist&lt;/a&gt;&lt;/p&gt;

</description>
      <category>devchallenge</category>
      <category>gamechallenge</category>
      <category>gamedev</category>
      <category>ai</category>
    </item>
    <item>
      <title>Button Remapping: Knowing When to Stop Reverse-Engineering</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Sun, 14 Jun 2026 10:18:59 +0000</pubDate>
      <link>https://dev.to/paulcontr_/button-remapping-knowing-when-to-stop-reverse-engineering-17b0</link>
      <guid>https://dev.to/paulcontr_/button-remapping-knowing-when-to-stop-reverse-engineering-17b0</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 5 of a series on building a native Razer mouse controller for macOS.&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhpmnvzoi1rez3wkd4pw.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffhpmnvzoi1rez3wkd4pw.png" alt="razer control ui" width="419" height="617"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;Last feature: remap the two side buttons. Make the forward one a left click, or copy, or whatever. Synapse does it, so the mouse can do it, so I just need the command. Right?&lt;/p&gt;

&lt;p&gt;This is the part where I spent an evening getting the device to say yes and do nothing, then threw the whole approach away and solved it in a completely different place. I think the second half is the more useful lesson.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Firmware Hunt
&lt;/h2&gt;

&lt;p&gt;There's no public table for "remap a DeathAdder V2 button."  So I built a probe tool: send an arbitrary class/id with arbitrary args, print the full response. Then I swept ranges of commands looking for anything that responded &lt;code&gt;0x02&lt;/code&gt; (OK) and looked button-shaped.&lt;/p&gt;

&lt;p&gt;I found the profile system. Class &lt;code&gt;0x05&lt;/code&gt; and &lt;code&gt;0x06&lt;/code&gt; commands all answered. There's a per-button read at &lt;code&gt;0x05 / 0x8A&lt;/code&gt; that echoes back which button you asked about. There's a write at &lt;code&gt;0x05 / 0x0A&lt;/code&gt;. Driver mode, profile slots, the works. It all looked exactly like where button bindings would live.&lt;/p&gt;

&lt;p&gt;So I tried the obvious write: button 4, action type mouse, parameter left-click. Sent it.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;status&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;OK&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pressed the button. Nothing changed. Read it back. Unchanged.&lt;/p&gt;

&lt;p&gt;Tried it in driver mode first. OK. Nothing. Tried selecting the profile slot before writing. OK. Nothing. Tried five different argument layouts. Every single one came back &lt;code&gt;0x02&lt;/code&gt;. Every single one did nothing.&lt;/p&gt;

&lt;p&gt;If you read part 3, you know exactly what this is. &lt;code&gt;0x02&lt;/code&gt; means the packet was well-formed and understood as &lt;em&gt;a&lt;/em&gt; command. It does not mean it did what I wanted. I was writing into a void the firmware was politely accepting.&lt;/p&gt;

&lt;p&gt;It got worse. One of my "reset to default" attempts used action type &lt;code&gt;0x00&lt;/code&gt;, which I assumed meant "firmware default." It actually means &lt;strong&gt;disabled&lt;/strong&gt;. I turned my own side button off and didn't realize for a minute why pressing it did nothing at all. (Fix: clear the profile slot entirely and let the firmware take back over. Or just unplug and replug.)&lt;/p&gt;

&lt;p&gt;After an evening of this I had a pile of commands that all returned OK and a mouse whose buttons were exactly as unremapped as when I started. The firmware path was a wall. Without capturing real Synapse USB traffic on Windows to see the exact bytes, I was guessing, and the device's "OK" gave me no signal to guess better.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Pivot
&lt;/h2&gt;

&lt;p&gt;Here's the reframe that fixed it. I don't actually need the &lt;em&gt;mouse&lt;/em&gt; to remap the button. I need the button press to &lt;em&gt;do something different on my Mac.&lt;/em&gt; Those aren't the same problem, and the second one doesn't involve Razer at all.&lt;/p&gt;

&lt;p&gt;macOS lets you intercept input events with a &lt;code&gt;CGEventTap&lt;/code&gt;. You watch for mouse button events, and when the one you care about fires, you swallow it and post a different event instead. This is how the commercial Mac mouse utilities do it. None of them reprogram firmware. They all sit in the event stream.&lt;/p&gt;

&lt;p&gt;First I just needed to know which button number the OS sees for each side button. A listen-only tap that logs:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;n&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntegerValueField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouseEventButtonNumber&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"otherMouseDown buttonNumber=&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;n&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Pressed both side buttons. The forward one (near the wheel) is &lt;code&gt;#4&lt;/code&gt;, the back one is &lt;code&gt;#3&lt;/code&gt;. Now I can target them.&lt;/p&gt;




&lt;h2&gt;
  
  
  Actually Remapping
&lt;/h2&gt;

&lt;p&gt;To &lt;em&gt;change&lt;/em&gt; an event, not just watch it, the tap has to be active (&lt;code&gt;.defaultTap&lt;/code&gt;), and that needs Accessibility permission, not Input Monitoring. Different permission, different System Settings pane. Worth knowing up front: a listen-only tap needs Input Monitoring, a tap that modifies or drops events needs Accessibility.&lt;/p&gt;

&lt;p&gt;The handler: if it's a button I manage and it's mapped to something, swallow the original and synthesize the replacement.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;nonisolated&lt;/span&gt; &lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;handle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGEventType&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;event&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;CGEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Unmanaged&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;CGEvent&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;button&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getIntegerValueField&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;mouseEventButtonNumber&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;action&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;MouseAction&lt;/span&gt;
    &lt;span class="k"&gt;switch&lt;/span&gt; &lt;span class="n"&gt;button&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;forwardAction&lt;/span&gt;
    &lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;backAction&lt;/span&gt;
    &lt;span class="k"&gt;default&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Unmanaged&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;passUnretained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;// not ours&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;passthrough&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kt"&gt;Unmanaged&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;passUnretained&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;type&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;otherMouseDown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nf"&gt;perform&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;at&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;event&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;location&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;nil&lt;/span&gt;   &lt;span class="c1"&gt;// swallow the original&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;perform&lt;/code&gt; posts whatever you mapped to: a left click is a synthesized &lt;code&gt;leftMouseDown&lt;/code&gt;/&lt;code&gt;leftMouseUp&lt;/code&gt;, copy is &lt;code&gt;⌘C&lt;/code&gt; as a key event, and so on. Press the side button, the click happens where your cursor is. It just works, immediately, the first time, with no guessing about byte layouts.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Tradeoff, Honestly
&lt;/h2&gt;

&lt;p&gt;Software remapping isn't strictly better than firmware remapping. The differences are real:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;It only works while my app is running. Firmware remapping would persist on the mouse, even on another computer or in a BIOS screen.&lt;/li&gt;
&lt;li&gt;It's Mac-only. The mouse's own memory doesn't know anything changed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For a menu bar app that launches at login, "only while the app runs" is basically always, so the tradeoff barely bites. And the firmware version had one fatal flaw the software version doesn't: it didn't work.&lt;/p&gt;

&lt;p&gt;That's the actual lesson from this whole feature. I spent an evening determined to solve the problem at the layer I'd decided it lived at. The device kept telling me OK and doing nothing, and I kept reading that as "almost there." The fix wasn't a better byte sequence. It was stepping up one layer to where the problem was tractable and the feedback was honest: press button, thing happens, or it doesn't.&lt;/p&gt;

&lt;p&gt;Reverse-engineering is fun right up until the point where it's just you and a device that says yes to everything. Knowing when to stop is part of the skill.&lt;/p&gt;




&lt;h2&gt;
  
  
  Where This Lands
&lt;/h2&gt;

&lt;p&gt;Five parts in, the app does what Synapse does for the things I care about: DPI with presets, static and animated lighting, side button remapping, all from a menu bar icon, no Razer account, no background service phoning home. The protocol work was the interesting half. The honest half was admitting one feature didn't belong in the protocol at all.&lt;/p&gt;

&lt;p&gt;The full code is at &lt;a href="//github.com/pol-cova/RazerControl"&gt;github.com/pol-cova/RazerControl&lt;/a&gt;&lt;/p&gt;

</description>
      <category>tutorial</category>
      <category>opensource</category>
      <category>learning</category>
      <category>showdev</category>
    </item>
    <item>
      <title>Wrapping It in a Menu Bar App (and Three Things That Broke)</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Wed, 10 Jun 2026 07:16:26 +0000</pubDate>
      <link>https://dev.to/paulcontr_/wrapping-it-in-a-menu-bar-app-and-three-things-that-broke-55lc</link>
      <guid>https://dev.to/paulcontr_/wrapping-it-in-a-menu-bar-app-and-three-things-that-broke-55lc</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 4 of a series on building a native Razer mouse controller for macOS.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;The protocol works from a command line tool. Now I want it living in the menu bar: a little mouse icon, click it, set DPI and color, no Terminal. SwiftUI has &lt;code&gt;MenuBarExtra&lt;/code&gt; for exactly this, so it should be quick.&lt;/p&gt;

&lt;p&gt;It was not quick. Three separate things broke, none of them about the mouse. This is the part where the OS fights you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Crash Before Anything Renders
&lt;/h2&gt;

&lt;p&gt;The starting point. An accessory app (menu bar only, no Dock icon) usually sets that with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;@main&lt;/span&gt;
&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;RazerControlApp&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;App&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;NSApp&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setActivationPolicy&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;accessory&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;Scene&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="o"&gt;...&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Launched it. Instant crash:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
RazerControlApp.swift:15
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;NSApp&lt;/code&gt; is &lt;code&gt;NSApplication!&lt;/code&gt;, an implicitly unwrapped optional. It's nil this early. The app object's &lt;code&gt;init()&lt;/code&gt; runs before the shared application is set up, so &lt;code&gt;NSApp&lt;/code&gt; is still nothing and the force-unwrap explodes.&lt;/p&gt;

&lt;p&gt;This bites harder on newer projects because the default actor isolation is &lt;code&gt;MainActor&lt;/code&gt;, and the timing of &lt;code&gt;init()&lt;/code&gt; lands before &lt;code&gt;NSApp&lt;/code&gt; exists. You can fight the lifecycle, or you can not use code for this at all.&lt;/p&gt;

&lt;p&gt;The fix is a plist key. &lt;code&gt;LSUIElement = YES&lt;/code&gt; (in build settings, &lt;code&gt;INFOPLIST_KEY_LSUIElement&lt;/code&gt;). It's read by the launch machinery before your &lt;code&gt;App&lt;/code&gt; struct is ever created, so there's no race and no &lt;code&gt;NSApp&lt;/code&gt; to unwrap. Delete the &lt;code&gt;init()&lt;/code&gt; entirely.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight properties"&gt;&lt;code&gt;&lt;span class="py"&gt;INFOPLIST_KEY_LSUIElement&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s"&gt;YES&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No code, no crash, no Dock icon. The thing I was writing code to do was a setting the whole time.&lt;/p&gt;




&lt;h2&gt;
  
  
  macOS Won't Let the App Touch the Mouse
&lt;/h2&gt;

&lt;p&gt;App launches now. Connects to the device. Immediately:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TCC deny IOHIDDeviceOpen
RazerControl: device not found
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IOHIDDeviceOpen&lt;/code&gt; on a mouse needs Input Monitoring permission. The command line tool worked the whole time because it ran from Terminal, and Terminal already had Input Monitoring. A fresh signed &lt;code&gt;.app&lt;/code&gt; has nothing.&lt;/p&gt;

&lt;p&gt;macOS won't pop the permission dialog on its own. You have to ask, explicitly, with the right call:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;access&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDCheckAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kIOHIDRequestTypeListenEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;access&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kIOHIDAccessTypeUnknown&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;IOHIDRequestAccess&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kIOHIDRequestTypeListenEvent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;access&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kIOHIDAccessTypeDenied&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// user said no earlier — send them to System Settings&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;kIOHIDRequestTypeListenEvent&lt;/code&gt; is imported as a plain constant, not a Swift enum case, so it's &lt;code&gt;kIOHIDRequestTypeListenEvent&lt;/code&gt; and not &lt;code&gt;.listenEvent&lt;/code&gt;. That cost me a compile error and a confused minute.&lt;/p&gt;

&lt;p&gt;First launch now shows the system prompt. If the user denied it before, the prompt won't come back, so the app needs to detect that case and point them at System Settings rather than silently failing.&lt;/p&gt;




&lt;h2&gt;
  
  
  MenuBarExtra Has Two Personalities
&lt;/h2&gt;

&lt;p&gt;The popover opened, but the layout was wrong. Everything stacked vertically like a dropdown menu. My &lt;code&gt;HStack&lt;/code&gt; of buttons rendered as separate rows. The color picker wouldn't open. Frame widths were ignored.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;MenuBarExtra&lt;/code&gt; has two render styles. The default is &lt;code&gt;.menu&lt;/code&gt;, which puts your content inside a real &lt;code&gt;NSMenu&lt;/code&gt;, so every view becomes a menu item and normal layout doesn't apply. The other is &lt;code&gt;.window&lt;/code&gt;, which renders your SwiftUI in an actual floating panel where layout works like everywhere else.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;MenuBarExtra&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"RazerControl"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"computermouse.fill"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;MenuBarView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;menuBarExtraStyle&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;window&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;One modifier. Suddenly HStacks are horizontal, widths apply, the popover looks like a popover. If your menu bar layout is behaving strangely, this is almost certainly why.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Color Picker That Wouldn't Open
&lt;/h2&gt;

&lt;p&gt;Last one. SwiftUI's &lt;code&gt;ColorPicker&lt;/code&gt; renders a little well; click it and the system color panel opens. Except in my app, clicking did nothing.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;ColorPicker&lt;/code&gt; relies on &lt;code&gt;NSColorPanel&lt;/code&gt;. An accessory app (the &lt;code&gt;LSUIElement&lt;/code&gt; thing from earlier) can't reliably bring up that panel, because it isn't allowed to become active the normal way, and the menu bar window dismisses the moment focus moves. So the panel either opens behind everything or never shows. The "selection" never happens, so the color never changes. One root cause, two symptoms.&lt;/p&gt;

&lt;p&gt;I could have fought the activation policy. Instead I asked what a menu bar RGB control actually needs, and the answer is not a full color wheel. It's a handful of good colors, one tap.&lt;/p&gt;

&lt;p&gt;So I replaced the picker with a row of preset swatches:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;private&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;palette&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;Color&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;red&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;orange&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;yellow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;green&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;cyan&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;blue&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;purple&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;pink&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;white&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="c1"&gt;// tap a swatch -&amp;gt; apply immediately, ring the selected one&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;No &lt;code&gt;NSColorPanel&lt;/code&gt;, no activation problem, and honestly better for a quick menu. Tap red, the logo's red. Done. The "fix" was less code and a nicer interaction than the thing that was broken.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Part 5 is the button remapping, which is the part I was most sure would be easy and turned out to be the hardest. I spent an evening trying to reverse-engineer the firmware command for it, got the device to accept my writes, and watched nothing happen. Then I gave up on the firmware entirely and solved it a different way. That story is its own post.&lt;/p&gt;

</description>
      <category>programming</category>
      <category>tutorial</category>
      <category>opensource</category>
      <category>showdev</category>
    </item>
    <item>
      <title>DPI, Color, and Why "OK" Doesn't Mean It Worked</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Mon, 08 Jun 2026 22:51:45 +0000</pubDate>
      <link>https://dev.to/paulcontr_/dpi-color-and-why-ok-doesnt-mean-it-worked-2nc</link>
      <guid>https://dev.to/paulcontr_/dpi-color-and-why-ok-doesnt-mean-it-worked-2nc</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 3 of a series on building a native Razer mouse controller for macOS.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;We have the envelope from part 2. Now the commands that actually do something: DPI and lighting. The DPI one is easy. The color one taught me the most useful lesson in this whole project, which is that the device telling you &lt;code&gt;0x02&lt;/code&gt; (OK) is not the device telling you it did what you wanted.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setting DPI
&lt;/h2&gt;

&lt;p&gt;Command class &lt;code&gt;0x04&lt;/code&gt;, id &lt;code&gt;0x05&lt;/code&gt;, data_size &lt;code&gt;0x07&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The value is the raw DPI, not divided by anything. 800 DPI is &lt;code&gt;0x0320&lt;/code&gt;, split into a high byte and a low byte. The mouse stores X and Y sensitivity separately, so you send the pair twice.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;dpiReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_&lt;/span&gt; &lt;span class="nv"&gt;dpi&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;v&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UInt16&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;dpi&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nv"&gt;repeating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x3F&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x07&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x04&lt;/span&gt;          &lt;span class="c1"&gt;// class: DPI&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x05&lt;/span&gt;          &lt;span class="c1"&gt;// id: set DPI&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;          &lt;span class="c1"&gt;// stage&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// X high&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// X low&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;11&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;// Y high&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;12&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;v&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt; &lt;span class="mh"&gt;0xFF&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;// Y low&lt;/span&gt;
    &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This one is fire-and-forget. You don't need to read a response. Set it, feel the cursor speed change.&lt;/p&gt;




&lt;h2&gt;
  
  
  Reading DPI Back
&lt;/h2&gt;

&lt;p&gt;There's a matching read command, class &lt;code&gt;0x04&lt;/code&gt;, id &lt;code&gt;0x85&lt;/code&gt;. Same idea, the id just has the high bit set, which is the convention for "get" versions of Razer commands.&lt;/p&gt;

&lt;p&gt;I didn't think I needed this until the GUI app shipped and immediately created a bug: every time you opened the app it reset your DPI to 800. The app had a default of 800 and pushed it on connect, stomping whatever you'd actually set.&lt;/p&gt;

&lt;p&gt;The fix was to read the device's real DPI on connect instead of forcing a default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;func&lt;/span&gt; &lt;span class="nf"&gt;getDPI&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// send class 0x04 id 0x85, read response&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;request&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;return&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;|&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;resp&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The DPI sits in the same byte positions in the response as you'd send it. Read first, then the UI reflects reality instead of overwriting it. Obvious in hindsight. Most of these bugs are.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Color Command That Lied to Me
&lt;/h2&gt;

&lt;p&gt;This is the part worth reading.&lt;/p&gt;

&lt;p&gt;I wanted to set the logo and scroll wheel to a static color. I found a command that looked right, class &lt;code&gt;0x03&lt;/code&gt;, id &lt;code&gt;0x03&lt;/code&gt;, packed in the zone and an RGB triple, sent it. Got &lt;code&gt;0x02&lt;/code&gt; back. OK. Accepted.&lt;/p&gt;

&lt;p&gt;Nothing happened. The LED didn't change.&lt;/p&gt;

&lt;p&gt;I checked the CRC. Fine. Checked the transaction ID. Fine. Checked the bytes ten times. The device kept saying &lt;code&gt;0x02&lt;/code&gt;. Accepted, accepted, accepted, and the logo stayed the color it already was.&lt;/p&gt;

&lt;p&gt;The problem: &lt;code&gt;0x03 / 0x03&lt;/code&gt; is not "set color." It's "set LED brightness." I was setting the brightness to a value, the device was happily setting the brightness to that value, and reporting success. It just wasn't a color command at all. I'd been sending a perfectly valid command for the wrong thing.&lt;/p&gt;

&lt;p&gt;The real static color command for this device lives in a different family. Class &lt;code&gt;0x0F&lt;/code&gt;, id &lt;code&gt;0x02&lt;/code&gt;, the "extended matrix effect" commands. The layout:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight c"&gt;&lt;code&gt;&lt;span class="n"&gt;data_size&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x09&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;        &lt;span class="c1"&gt;// varstore&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;led_id&lt;/span&gt;      &lt;span class="c1"&gt;// scroll = 0x01, logo = 0x04&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;        &lt;span class="c1"&gt;// effect: static&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x01&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;g&lt;/span&gt;
&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;8&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;b&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Sent that. Logo turned green. First time the lighting actually moved.&lt;/p&gt;

&lt;p&gt;The lesson stuck with me: a status of &lt;code&gt;0x02&lt;/code&gt; only tells you the packet was well-formed and the device understood it as &lt;em&gt;a&lt;/em&gt; command. It says nothing about whether it was the command you meant. A valid brightness-set and a valid color-set both return &lt;code&gt;0x02&lt;/code&gt;. If you're debugging by status code alone, you can burn an hour sending textbook-correct commands that do the wrong job.&lt;/p&gt;




&lt;h2&gt;
  
  
  Effects Come for Free
&lt;/h2&gt;

&lt;p&gt;Once you're in the &lt;code&gt;0x0F / 0x02&lt;/code&gt; family, the other lighting effects are just a different effect byte at &lt;code&gt;args[2]&lt;/code&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Effect&lt;/th&gt;
&lt;th&gt;args[2]&lt;/th&gt;
&lt;th&gt;data_size&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Off&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Static&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9 (+ rgb)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Breathing&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;9 (+ rgb)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Spectrum&lt;/td&gt;
&lt;td&gt;&lt;code&gt;0x03&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;6&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Spectrum is the rainbow cycle. You set it once and the mouse firmware animates it on its own, no polling from your app. Breathing pulses a single color. Off goes dark. Same envelope, one byte different. After the static color fight, adding three more effects took about ten minutes.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Part 4 leaves the protocol behind and wraps all this in a SwiftUI menu bar app. That sounds like the easy part. It was not. There's a crash that only happens because of a build setting, a permission macOS won't grant without being asked in a specific way, and a color picker that refuses to open. The protocol was more honest than the UI framework.&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>learning</category>
      <category>sideprojects</category>
      <category>tutorial</category>
    </item>
    <item>
      <title>The Packet Format: 90 Bytes and One Cursed Byte</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Sun, 07 Jun 2026 06:35:11 +0000</pubDate>
      <link>https://dev.to/paulcontr_/the-packet-format-90-bytes-and-one-cursed-byte-55pe</link>
      <guid>https://dev.to/paulcontr_/the-packet-format-90-bytes-and-one-cursed-byte-55pe</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 2 of a series on building a native Razer mouse controller for macOS.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;In part 1 we found the right HID interface and learned how to push a feature report at it. The report was just 90 zero bytes, which does nothing. This part is about what actually goes in those 90 bytes.&lt;/p&gt;

&lt;p&gt;Every Razer config command uses the same fixed layout. Once you have the layout, every feature (DPI, lighting, polling rate) is just different values in the same envelope.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Layout
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[0]      status
[1]      transaction_id
[2-3]    remaining_packets
[4]      protocol_type
[5]      data_size
[6]      command_class
[7]      command_id
[8-87]   arguments (80 bytes)
[88]     CRC
[89]     reserved
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When you send a command, byte &lt;code&gt;[0]&lt;/code&gt; (status) is 0. When the device replies, it writes a status code back into byte &lt;code&gt;[0]&lt;/code&gt;. Same buffer shape both ways.&lt;/p&gt;

&lt;p&gt;The two bytes that pick what the command does are &lt;code&gt;command_class&lt;/code&gt; (&lt;code&gt;[6]&lt;/code&gt;) and &lt;code&gt;command_id&lt;/code&gt; (&lt;code&gt;[7]&lt;/code&gt;). Think of class as the subsystem (DPI, lighting, profiles) and id as the specific action inside it. Everything from byte 8 onward is arguments, and what they mean depends entirely on the class/id pair.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;data_size&lt;/code&gt; (&lt;code&gt;[5]&lt;/code&gt;) is how many of the argument bytes are meaningful. If your command uses 3 argument bytes, data_size is 3. Get this wrong and the device usually ignores you.&lt;/p&gt;




&lt;h2&gt;
  
  
  The CRC
&lt;/h2&gt;

&lt;p&gt;Byte &lt;code&gt;[88]&lt;/code&gt; is a checksum. It's an XOR of every byte from index 2 through 87, inclusive.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That's the whole thing. No polynomial, no lookup table, just XOR. If the CRC is wrong the device rejects the packet, and you'll get a status that isn't &lt;code&gt;0x02&lt;/code&gt; or no response at all.&lt;/p&gt;

&lt;p&gt;The range matters. It's &lt;code&gt;2...87&lt;/code&gt;, not &lt;code&gt;0...89&lt;/code&gt;. The status and transaction ID at the front are excluded, and so are the CRC and reserved bytes at the end. I got this wrong the first time by including byte 1 and spent a while convinced my command bytes were the problem.&lt;/p&gt;




&lt;h2&gt;
  
  
  The Transaction ID
&lt;/h2&gt;

&lt;p&gt;Byte &lt;code&gt;[1]&lt;/code&gt; is the transaction ID, and it is the single most annoying byte in the whole format.&lt;/p&gt;

&lt;p&gt;It's not part of the command. It doesn't change behavior. It's a tag the device firmware checks before it will even look at the rest of the packet. If it's wrong, the device acts like you said nothing.&lt;/p&gt;

&lt;p&gt;The catch: the correct value is different across Razer devices, and nobody documents it per model. For the DeathAdder V2 it's &lt;code&gt;0x3F&lt;/code&gt;. Other devices use &lt;code&gt;0xFF&lt;/code&gt; or &lt;code&gt;0x1F&lt;/code&gt;. There's no command to ask the device what its transaction ID is. You either know it or you guess.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x3F&lt;/span&gt;   &lt;span class="c1"&gt;// DeathAdder V2&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you're porting this to another Razer mouse and nothing works even though your command bytes look right, this is the first thing to change. Try &lt;code&gt;0xFF&lt;/code&gt;, then &lt;code&gt;0x1F&lt;/code&gt;, then &lt;code&gt;0x3F&lt;/code&gt;. One of them will start returning &lt;code&gt;0x02&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;I hardcoded it. There's no reason to make it configurable in the app, because a wrong value just means a dead command.&lt;/p&gt;




&lt;h2&gt;
  
  
  A Real Command
&lt;/h2&gt;

&lt;p&gt;Here's the firmware version request, which is the simplest useful command and a good first target because it returns data you can sanity-check:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nv"&gt;repeating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x3F&lt;/span&gt;   &lt;span class="c1"&gt;// transaction id&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x02&lt;/span&gt;   &lt;span class="c1"&gt;// data_size&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;6&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x00&lt;/span&gt;   &lt;span class="c1"&gt;// command_class: standard&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;7&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mh"&gt;0x81&lt;/span&gt;   &lt;span class="c1"&gt;// command_id: get firmware&lt;/span&gt;
&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;88&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="mi"&gt;87&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;reduce&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;^&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Send it, wait 300ms, read the response. If the stack is working, byte &lt;code&gt;[0]&lt;/code&gt; of the response comes back &lt;code&gt;0x02&lt;/code&gt;, and the firmware version sits in bytes &lt;code&gt;[9]&lt;/code&gt; and &lt;code&gt;[10]&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"firmware: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;9&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;.&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// firmware: 2.0&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Seeing &lt;code&gt;2.0&lt;/code&gt; print instead of &lt;code&gt;0.0&lt;/code&gt; is the moment you know the whole chain works: right interface, right transaction ID, right CRC, right delay. Everything after this is just changing class, id, and arguments.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Part 3 builds the real commands on top of this envelope: setting DPI, reading it back, and the static color command that I got wrong twice before it worked. There's a good lesson buried in that one about why "command accepted" doesn't mean "command did anything."&lt;/p&gt;

</description>
      <category>discuss</category>
      <category>learning</category>
      <category>opensource</category>
      <category>programming</category>
    </item>
    <item>
      <title>USB HID on macOS: Talking to Devices with IOKit</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Mon, 01 Jun 2026 07:44:16 +0000</pubDate>
      <link>https://dev.to/paulcontr_/usb-hid-on-macos-talking-to-devices-with-iokit-3g2n</link>
      <guid>https://dev.to/paulcontr_/usb-hid-on-macos-talking-to-devices-with-iokit-3g2n</guid>
      <description>&lt;p&gt;&lt;em&gt;Part 1 of a series on building a native Razer mouse controller for macOS.&lt;/em&gt;&lt;/p&gt;




&lt;p&gt;USB HID (Human Interface Device) is the protocol your mouse, keyboard, and gamepad use to talk to the OS. Most of the time it's invisible; The OS handles it and your app gets abstracted events.&lt;/p&gt;

&lt;p&gt;But HID also has &lt;strong&gt;feature reports&lt;/strong&gt;: raw byte arrays you can send directly to a device outside of normal input events. Manufacturers use these for configuration. That's how Razer Synapse sets DPI and RGB — and it's what we need to replace it.&lt;/p&gt;

&lt;p&gt;On macOS, the API for this is &lt;code&gt;IOHIDManager&lt;/code&gt; inside &lt;code&gt;IOKit&lt;/code&gt;. No kernel extension required.&lt;/p&gt;




&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Command Line Tool target in Xcode, macOS, Swift. No sandbox  add &lt;code&gt;App Sandbox&lt;/code&gt; to entitlements and immediately remove it, or just confirm it's not there. IOKit feature reports fail silently with sandbox on.&lt;/p&gt;

&lt;p&gt;Link &lt;code&gt;IOKit.framework&lt;/code&gt; under target → General → Frameworks and Libraries.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;IOKit&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;IOKit&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;hid&lt;/span&gt;
&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;Foundation&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Finding a Device
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;IOHIDManager&lt;/code&gt; takes a matching dictionary. At minimum, vendor ID and product ID.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;manager&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDManagerCreate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kCFAllocatorDefault&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;IOOptionBits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kIOHIDOptionsTypeNone&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="kt"&gt;IOHIDManagerSetDeviceMatching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nv"&gt;kIOHIDVendorIDKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="mh"&gt;0x1532&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="nv"&gt;kIOHIDProductIDKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x0084&lt;/span&gt;
&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFDictionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kt"&gt;IOHIDManagerOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;IOOptionBits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kIOHIDOptionsTypeNone&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kIOReturnSuccess&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"failed to open manager"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;devices&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDManagerCopyDevices&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;Set&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;IOHIDDevice&lt;/span&gt;&lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;isEmpty&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"device not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you don't know your device's PID yet, filter by vendor ID only and print everything:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;IOHIDManagerSetDeviceMatching&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;manager&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;kIOHIDVendorIDKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mh"&gt;0x1532&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFDictionary&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// ... open manager ...&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDDeviceGetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kIOHIDProductKey&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;String&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="s"&gt;"?"&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;pid&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDDeviceGetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kIOHIDProductIDKey&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;: 0x&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pid&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;radix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  The Multiple Interfaces Problem
&lt;/h2&gt;

&lt;p&gt;One physical USB device often enumerates as multiple HID interfaces. A Razer DeathAdder V2 shows up as four:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;page: 0x1 | usage: 0x6 | maxFeatureReport: 1
page: 0x1 | usage: 0x6 | maxFeatureReport: 0
page: 0x59 | usage: 0x1 | maxFeatureReport: 64
page: 0x1 | usage: 0x2 | maxFeatureReport: 90
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;IOHIDManagerCopyDevices&lt;/code&gt; returns a &lt;code&gt;Set&lt;/code&gt;. &lt;code&gt;devices.first&lt;/code&gt; is non-deterministic  you have a 1-in-4 chance of hitting the right interface. The wrong interfaces accept your data and do nothing with it. The send call returns &lt;code&gt;0&lt;/code&gt; (success) and the device response is all zeros.&lt;/p&gt;

&lt;p&gt;Print all interfaces first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;usagePage&lt;/span&gt;  &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDDeviceGetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kIOHIDPrimaryUsagePageKey&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;usage&lt;/span&gt;      &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDDeviceGetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kIOHIDPrimaryUsageKey&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;maxFeature&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDDeviceGetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kIOHIDMaxFeatureReportSizeKey&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"page: 0x&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usagePage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;radix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; | usage: 0x&lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="kt"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;usage&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;radix&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;16&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt; | maxFeatureReport: &lt;/span&gt;&lt;span class="se"&gt;\(&lt;/span&gt;&lt;span class="n"&gt;maxFeature&lt;/span&gt;&lt;span class="se"&gt;)&lt;/span&gt;&lt;span class="s"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The interface you want for Razer configuration commands is the one with &lt;code&gt;maxFeatureReport: 90&lt;/code&gt; — that matches the 90-byte packet format Razer uses. Filter on it explicitly:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;device&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;devices&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;first&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;where&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="n"&gt;d&lt;/span&gt; &lt;span class="nf"&gt;in&lt;/span&gt;
    &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kt"&gt;IOHIDDeviceGetProperty&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kIOHIDMaxFeatureReportSizeKey&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="kt"&gt;CFString&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as?&lt;/span&gt; &lt;span class="kt"&gt;Int&lt;/span&gt; &lt;span class="p"&gt;??&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;
&lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"control interface not found"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;For other devices, the right interface depends on the protocol. &lt;code&gt;maxFeatureReport&lt;/code&gt; size is usually the clearest signal.&lt;/p&gt;




&lt;h2&gt;
  
  
  Opening and Sending
&lt;/h2&gt;

&lt;p&gt;After filtering, open the specific interface and send a feature report:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="k"&gt;guard&lt;/span&gt; &lt;span class="kt"&gt;IOHIDDeviceOpen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kt"&gt;IOOptionBits&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;kIOHIDOptionsTypeNone&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="n"&gt;kIOReturnSuccess&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"could not open device"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;report&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nv"&gt;repeating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;// ... fill report bytes ...&lt;/span&gt;

&lt;span class="k"&gt;let&lt;/span&gt; &lt;span class="nv"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;IOHIDDeviceSetReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kIOHIDReportTypeFeature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;report&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two things that caused me problems here:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Buffer size.&lt;/strong&gt; &lt;code&gt;IOHIDDeviceSetReport&lt;/code&gt; takes the report ID as a separate parameter (the &lt;code&gt;0&lt;/code&gt; above). Do not prepend it to the buffer. If &lt;code&gt;maxFeatureReport&lt;/code&gt; is 90, your buffer should be exactly 90 bytes. I was sending 91 and getting error &lt;code&gt;-536850432&lt;/code&gt; (&lt;code&gt;0xE0005000&lt;/code&gt;) back from IOKit with no further explanation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reading the response.&lt;/strong&gt; The response comes back in a separate &lt;code&gt;IOHIDDeviceGetReport&lt;/code&gt; call. The device needs a small delay between send and read — the amount varies, but 200–300ms works reliably:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kt"&gt;Thread&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sleep&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;forTimeInterval&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mf"&gt;0.3&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="kt"&gt;UInt8&lt;/span&gt;&lt;span class="p"&gt;](&lt;/span&gt;&lt;span class="nv"&gt;repeating&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;count&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;responseLen&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kt"&gt;CFIndex&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;90&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="kt"&gt;IOHIDDeviceGetReport&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;device&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;kIOHIDReportTypeFeature&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&lt;/span&gt;&lt;span class="n"&gt;responseLen&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use a fresh buffer for the response so you can tell whether the device wrote anything into it.&lt;/p&gt;




&lt;h2&gt;
  
  
  Interpreting the Response
&lt;/h2&gt;

&lt;p&gt;For Razer devices, byte &lt;code&gt;[0]&lt;/code&gt; of the response is a status code:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Value&lt;/th&gt;
&lt;th&gt;Meaning&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x00&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No response / read stale data&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x01&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Device busy&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x02&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Command accepted&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;0x05&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Command not understood&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;code&gt;0x02&lt;/code&gt; means the full stack is working. &lt;code&gt;0x05&lt;/code&gt; usually means wrong command bytes or wrong transaction ID for the device family.&lt;/p&gt;

&lt;p&gt;If you get all zeros back, you either read too fast, hit the wrong interface, or the buffer size was wrong.&lt;/p&gt;




&lt;h2&gt;
  
  
  What's Next
&lt;/h2&gt;

&lt;p&gt;Part 2 covers the actual Razer packet format — what goes in those 90 bytes, how the CRC is computed, and what the transaction ID is and why it matters.&lt;/p&gt;

</description>
      <category>swift</category>
      <category>opensource</category>
      <category>showdev</category>
      <category>learning</category>
    </item>
    <item>
      <title>LaunchNotes: Simple What's New Screens for SwiftUI Apps</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Mon, 01 Jun 2026 00:54:33 +0000</pubDate>
      <link>https://dev.to/paulcontr_/launchnotes-simple-whats-new-screens-for-swiftui-apps-2ahh</link>
      <guid>https://dev.to/paulcontr_/launchnotes-simple-whats-new-screens-for-swiftui-apps-2ahh</guid>
      <description>&lt;h2&gt;
  
  
  Show What's New in Your SwiftUI App with LaunchNotes
&lt;/h2&gt;

&lt;p&gt;Somewhere in the third app I shipped, I got tired of rebuilding the same "What's New" screen.&lt;br&gt;
It's never quite the same codebase, so copy-pasting doesn't work. And it's not interesting&lt;br&gt;
enough to engineer properly each time.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;LaunchNotes&lt;/strong&gt; to stop thinking about it.&lt;/p&gt;

&lt;p&gt;One view modifier. It compares the current bundle version to what's saved in &lt;code&gt;UserDefaults&lt;/code&gt;&lt;br&gt;
and shows a sheet if something changed.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="kd"&gt;import&lt;/span&gt; &lt;span class="kt"&gt;LaunchNotes&lt;/span&gt;

&lt;span class="kd"&gt;struct&lt;/span&gt; &lt;span class="kt"&gt;ContentView&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;var&lt;/span&gt; &lt;span class="nv"&gt;body&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kd"&gt;some&lt;/span&gt; &lt;span class="kt"&gt;View&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;HomeView&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
            &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;launchNotes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
                &lt;span class="kt"&gt;LaunchNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"New Stats"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"Track your progress with cleaner charts."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"chart.line.uptrend.xyaxis"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
                &lt;span class="kt"&gt;LaunchNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                    &lt;span class="s"&gt;"Smoother Animations"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="s"&gt;"The app feels faster now."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"sparkles"&lt;/span&gt;
                &lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You can also keep all release history in one place:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;launchNotes&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;LaunchNotesRelease&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"What's New in 1.2"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;LaunchNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"New Stats"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Cleaner progress charts."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"chart.line.uptrend.xyaxis"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;LaunchNotesRelease&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"1.1.0"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;LaunchNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"Search"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Find previous sessions faster."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"magnifyingglass"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also full-screen mode, style presets, custom accent colors, footer actions, and a&lt;br&gt;
manual trigger if you don't want the automatic behavior.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight swift"&gt;&lt;code&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launchNotes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nv"&gt;presentation&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;fullScreen&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;prominent&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kt"&gt;LaunchNote&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s"&gt;"New Stats"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s"&gt;"Cleaner progress charts."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nv"&gt;systemImage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="s"&gt;"chart.line.uptrend.xyaxis"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Package is at &lt;a href="https://github.com/pol-cova/LaunchNotes" rel="noopener noreferrer"&gt;https://github.com/pol-cova/LaunchNotes&lt;/a&gt;. Still early — feedback and PRs welcome.&lt;/p&gt;

</description>
      <category>ios</category>
      <category>swift</category>
      <category>mobile</category>
      <category>opensource</category>
    </item>
    <item>
      <title>The 5 MB Homework Limit That Turned Me Into an App-Builder</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Fri, 01 Aug 2025 22:13:33 +0000</pubDate>
      <link>https://dev.to/paulcontr_/the-5-mb-homework-limit-that-turned-me-into-an-app-builder-222a</link>
      <guid>https://dev.to/paulcontr_/the-5-mb-homework-limit-that-turned-me-into-an-app-builder-222a</guid>
      <description>&lt;p&gt;This semester I snapped a neat, high-res photo of my handwritten math assignment and went to upload it to my university platform.&lt;/p&gt;

&lt;p&gt;Error: &lt;strong&gt;“File must be under 5 MB.”&lt;/strong&gt;&lt;br&gt;
I tried again with a different angle—same error. Then the portal complained about the HEIC format my iPhone uses by default. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What I tried next&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google results sent me to web compressors that wanted my personal photos on their servers (nope).&lt;/li&gt;
&lt;li&gt;The first “free” iOS app threw three ads at me before I could pick a file.&lt;/li&gt;
&lt;li&gt;A second app asked for $4.99 per month, just to shrink a picture!&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I ended up opening Preview on my Mac, exporting, tweaking quality sliders, re-sending to my phone, and finally hitting “Submit” with seconds to spare. The whole thing took longer than writing the homework.&lt;/p&gt;

&lt;p&gt;That night I wrote Crunch, an offline photo compressor that fixes the exact pain I felt.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;What Crunch does now&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Shrinks JPEG, HEIC, PNG images by up to 70 %, totally on-device (no uploads)&lt;/li&gt;
&lt;li&gt;Converts formats so HEIC becomes JPG or PDF in one tap&lt;/li&gt;
&lt;li&gt;Cleans out location + EXIF data before you share a file&lt;/li&gt;
&lt;li&gt;Free: 10 photos a day. Pro: one-time $3.99—no subscriptions, ever&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Try Crunch for iOS now&lt;/strong&gt;: &lt;a href="https://apps.apple.com/app/id6748700457" rel="noopener noreferrer"&gt;Crunch on AppStore&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Follow me on x where I’m sharing progress, mistakes, your feedback will shape the next update—features, pricing, UX, anything.&lt;br&gt;
&lt;a href="https://x.com/3nginuity" rel="noopener noreferrer"&gt;@3nginuity&lt;/a&gt;&lt;/p&gt;

</description>
      <category>programming</category>
      <category>mobile</category>
      <category>showdev</category>
      <category>buildinpublic</category>
    </item>
    <item>
      <title>🔐 Building a Simple Password Generator with FastHTML</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Sat, 24 Aug 2024 01:25:13 +0000</pubDate>
      <link>https://dev.to/3nginuity_/building-a-simple-password-generator-with-fasthtml-4jhd</link>
      <guid>https://dev.to/3nginuity_/building-a-simple-password-generator-with-fasthtml-4jhd</guid>
      <description>&lt;p&gt;Are you looking to build a lightweight web application that’s both functional and stylish? In this tutorial, we’ll walk through creating a simple password generator using FastHTML — a minimalist Python-based HTML templating engine — and Tailwind CSS for sleek and responsive design.&lt;/p&gt;

&lt;h2&gt;
  
  
  🚀 What You’ll Build
&lt;/h2&gt;

&lt;p&gt;We’ll create a web application that allows users to input the desired length of a password and generate a strong, random password on the fly. This tutorial is perfect for developers who want to explore FastHTML and HTMX, combined with the power of Tailwind CSS for simple front-end styling.&lt;/p&gt;

&lt;h2&gt;
  
  
  👨‍💻 Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we dive in, make sure you have Python installed on your machine. We’ll be using the following libraries:&lt;/p&gt;

&lt;p&gt;FastHTML: For our application.&lt;br&gt;
You can install the required library with:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;pip install python-fasthtml
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;h2&gt;
  
  
  🛠️ Step 1: Setting Up Your FastHTML App
&lt;/h2&gt;

&lt;p&gt;Let’s start by setting up our FastHTML application. FastHTML is a lightweight HTML templating engine that allows you to create HTML elements directly in Python, making your code clean and easy to manage.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;# Importing libraries
from fasthtml.common import *
import string
import secrets

# Defining the FastHTML app
app = FastHTML(
    hdrs=(
        # Add Tailwind CSS
        Script(src="https://cdn.tailwindcss.com"),
    )
)
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Here, we import the necessary libraries and initialize our FastHTML app, embedding Tailwind CSS directly via CDN for rapid styling.&lt;/p&gt;
&lt;h2&gt;
  
  
  🎨 Step 2: Creating the User Interface
&lt;/h2&gt;

&lt;p&gt;Next, we’ll design a simple form where users can input the desired password length. Tailwind CSS will make our form both functional and visually appealing.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@app.route('/')
def home():
    title = "Password Generator - With FastHTML"
    form = Form(
        Input(id="length_input", type="number", placeholder="Enter the length",
              cls="border-2 border-gray-400 rounded-md p-2"),
        Button("Generate", cls="bg-slate-900 text-white p-2 rounded-md mt-2"),
        cls="flex flex-col items-center mt-10",
        hx_post="/generate",
        target_id="result",
        hx_swap="innerHTML"
    )
    result = Div(id="result", cls="text-center mt-10")

    return Title(title), Main(H1(title, cls="text-3xl text-center mt-10"), form, result)

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4jestlj5te8846mlow3.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fa4jestlj5te8846mlow3.png" alt="ui" width="623" height="307"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This function renders our homepage, which includes a form for password generation. The form leverages HTMX to send the input data to the server and dynamically update the result without a full page reload. This makes the user experience seamless and responsive.&lt;/p&gt;
&lt;h2&gt;
  
  
  🔒 Step 3: Implementing the Password Generator Logic
&lt;/h2&gt;

&lt;p&gt;Now, let’s implement the logic to generate a secure password based on the user’s input.&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;alph = string.ascii_letters + string.digits + string.punctuation


@app.route("/generate")
def post(length_input: int):
    length = length_input
    if length &amp;lt; 8:
        return "Password length should be greater than 8 characters"
    password = ''.join(secrets.choice(alph) for i in range(length))
    return password
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;In this snippet, we define the character set for our password and implement the generator logic. We ensure the password length is secure (minimum of 8 characters) and then generate a random password using Python’s &lt;strong&gt;secrets&lt;/strong&gt; module, which is designed for cryptographic applications.&lt;/p&gt;
&lt;h2&gt;
  
  
  ▶️ Step 4: Running Your Application
&lt;/h2&gt;

&lt;p&gt;To get your app up and running, simply run the following command:&lt;br&gt;
&lt;/p&gt;
&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if '__main__' == __name__:
    serve()
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;


&lt;p&gt;Now, visit &lt;strong&gt;&lt;a href="http://127.0.0.1:5001" rel="noopener noreferrer"&gt;http://127.0.0.1:5001&lt;/a&gt;&lt;/strong&gt; in your browser, and you'll be greeted with your new password generator!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcvddc21gd6w0jkbf8t7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Frcvddc21gd6w0jkbf8t7.png" alt="result" width="609" height="291"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzht5wue6jgg7c34yfm92.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fzht5wue6jgg7c34yfm92.gif" alt="result-demo" width="320" height="176"&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  🎉 Conclusion
&lt;/h2&gt;

&lt;p&gt;Congratulations! You’ve just built a fully functional password generator using FastHTML and Tailwind CSS. This project is a fantastic way to get acquainted with Python-based web development, HTMX for dynamic content, and Tailwind CSS for modern, responsive design.&lt;/p&gt;
&lt;h2&gt;
  
  
  💾 Check Out the Full Source Code
&lt;/h2&gt;

&lt;p&gt;Want to dive deeper into the code or contribute to the project? The full source code is available on GitHub. Feel free to clone, fork, or star the repository:&lt;br&gt;
&lt;/p&gt;
&lt;div class="ltag-github-readme-tag"&gt;
  &lt;div class="readme-overview"&gt;
    &lt;h2&gt;
      &lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fassets.dev.to%2Fassets%2Fgithub-logo-5a155e1f9a670af7944dd5e12375bc76ed542ea80224905ecaf878b9157cdefc.svg" alt="GitHub logo"&gt;
      &lt;a href="https://github.com/pol-cova" rel="noopener noreferrer"&gt;
        pol-cova
      &lt;/a&gt; / &lt;a href="https://github.com/pol-cova/password-generator-fasthtml" rel="noopener noreferrer"&gt;
        password-generator-fasthtml
      &lt;/a&gt;
    &lt;/h2&gt;
    &lt;h3&gt;
      Simple password generator using FastHTML
    &lt;/h3&gt;
  &lt;/div&gt;
  &lt;div class="ltag-github-body"&gt;
    
&lt;div id="readme" class="md"&gt;&lt;div class="markdown-heading"&gt;
&lt;h1 class="heading-element"&gt;🔐 Password Generator with FastHTML&lt;/h1&gt;
&lt;/div&gt;

&lt;p&gt;This repository contains a simple password generator web application built using &lt;a href="https://github.com/yourusername/fasthtml" rel="noopener noreferrer"&gt;FastHTML&lt;/a&gt; and styled with &lt;a href="https://tailwindcss.com/" rel="nofollow noopener noreferrer"&gt;Tailwind CSS&lt;/a&gt;. The application allows users to generate secure, random passwords based on a user-defined length.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🚀 Features&lt;/h2&gt;
&lt;/div&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Secure Password Generation&lt;/strong&gt;: Uses Python's &lt;code&gt;secrets&lt;/code&gt; module for cryptographic randomness.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Responsive Design&lt;/strong&gt;: Styled with Tailwind CSS for a clean and modern look.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dynamic Updates&lt;/strong&gt;: Utilizes HTMX for seamless, asynchronous password generation.&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;📦 Prerequisites&lt;/h2&gt;
&lt;/div&gt;

&lt;p&gt;Make sure you have Python installed on your machine. You can install the necessary dependencies with:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;pip install python-fasthtml&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;🛠️ Installation&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;Clone this repository to your local machine:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;git clone https://github.com/pol-cova/password-generator-fasthtml.git&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;Navigate to the project directory:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;&lt;span class="pl-c1"&gt;cd&lt;/span&gt; password-generator-fasthtml&lt;/pre&gt;

&lt;/div&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;
▶️ Running the Application&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;To start the application, simply run:&lt;/p&gt;

&lt;div class="highlight highlight-source-shell notranslate position-relative overflow-auto js-code-highlight"&gt;
&lt;pre&gt;python app.py&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;You can now access the application by navigating to &lt;code&gt;http://localhost:5001&lt;/code&gt; in your web browser.&lt;/p&gt;

&lt;div class="markdown-heading"&gt;
&lt;h2 class="heading-element"&gt;Tutorial&lt;/h2&gt;

&lt;/div&gt;

&lt;p&gt;You can find a detailed tutorial on how to build…&lt;/p&gt;&lt;/div&gt;


&lt;/div&gt;
&lt;br&gt;
  &lt;div class="gh-btn-container"&gt;&lt;a class="gh-btn" href="https://github.com/pol-cova/password-generator-fasthtml" rel="noopener noreferrer"&gt;View on GitHub&lt;/a&gt;&lt;/div&gt;
&lt;br&gt;
&lt;/div&gt;
&lt;br&gt;


</description>
      <category>webdev</category>
      <category>python</category>
      <category>fasthtml</category>
      <category>coding</category>
    </item>
    <item>
      <title>Introducción a C++: parte 4 Funciones</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Thu, 04 Apr 2024 08:20:23 +0000</pubDate>
      <link>https://dev.to/paulcontr_/introduccion-a-c-parte-4-funciones-27kp</link>
      <guid>https://dev.to/paulcontr_/introduccion-a-c-parte-4-funciones-27kp</guid>
      <description>&lt;p&gt;¡En esta cuarta parte, vamos a descubrir el poder de las funciones en C++! Imagina tener una caja de herramientas repleta de funciones listas para usar en tu código. ¡Y lo mejor de todo es que estas funciones te ayudarán a aplicar el principio DRY (Don't Repeat Yourself), haciendo que tu código sea más eficiente y fácil de mantener! ¿Emocionado? ¡Vamos a sumergirnos en el mundo de las funciones!.&lt;/p&gt;

&lt;h2&gt;
  
  
  Estructura basica de una función
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Estructura basica 
tipoDeRetorno nombreFuncion(argumentos){
// Bloque de codigo
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Donde:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;tipoDeRetorno&lt;/code&gt; es el tipo de dato que la función devolverá al final de su ejecución. Puede ser void si la función no devuelve ningún valor.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;nombreFuncion&lt;/code&gt; es el nombre que le das a tu función, mediante el cual la llamarás desde otras partes del programa.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;argumentos&lt;/code&gt; son los valores que la función puede recibir como entrada. Pueden ser cero o más, separados por comas si hay más de uno.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Dentro del bloque de código de la función, es donde escribes las instrucciones que deseas que la función realice cuando sea llamada. ¡Es como un pequeño programa dentro de tu programa!&lt;/p&gt;

&lt;h2&gt;
  
  
  Tipos de retorno
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;void&lt;/code&gt;: Indica que la función no devuelve ningún valor. Se utiliza cuando la función solo realiza tareas y no necesita devolver ningún resultado.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;int, float, double, char, etc&lt;/code&gt;: Indica que la función devuelve un valor de tipo entero, flotante, doble, caracter, etc. Puedes ajustar el tipo según lo que necesites que la función retorne.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;string&lt;/code&gt;: Indica que la función devuelve un valor de tipo string. Útil para funciones que generan o manipulan cadenas de caracteres.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;bool&lt;/code&gt;: Indica que la función devuelve un valor booleano, es decir, verdadero (true) o falso (false).&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Nombres de funciones
&lt;/h2&gt;

&lt;p&gt;Cuando nombras una función en C++, es importante seguir algunas convenciones simples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;CamelCase&lt;/code&gt;: Comienza con una letra minúscula y las palabras subsecuentes comienzan con mayúscula.&lt;/li&gt;
&lt;li&gt;Ejemplo: &lt;code&gt;calcularSuma&lt;/code&gt;, &lt;code&gt;ordenarArray&lt;/code&gt;, &lt;code&gt;imprimirMensaje&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Descriptivo: Usa nombres que describan claramente la función de la función.&lt;/li&gt;
&lt;li&gt;Evita abreviaturas excesivas: Usa abreviaturas solo si son claras y ampliamente entendidas.
Siguiendo estas pautas, tu código será más fácil de entender para ti y para otros desarrolladores.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Argumentos
&lt;/h2&gt;

&lt;p&gt;Cuando hablamos de argumentos en una función de C++, nos referimos a los valores que se pasan a la función cuando es llamada. Estos argumentos pueden ser variables, constantes u otros valores, y se utilizan para proporcionar datos a la función para que pueda realizar su tarea.&lt;/p&gt;

&lt;p&gt;Por ejemplo, en la función &lt;code&gt;sumar(int a, int b)&lt;/code&gt;, los argumentos son &lt;code&gt;a&lt;/code&gt; y &lt;code&gt;b&lt;/code&gt;, que son dos números enteros que queremos sumar. Cuando llamamos a esta función, pasamos los valores que queremos sumar como argumentos: &lt;code&gt;sumar(5, 3)&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Es importante recordar que los argumentos deben coincidir en tipo y cantidad con los parámetros definidos en la función. Además, en C++, los argumentos pueden ser expresiones, variables o incluso llamadas a otras funciones, siempre que el tipo coincida con el parámetro correspondiente de la función.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prototipo de funciones en c++
&lt;/h2&gt;

&lt;p&gt;En C++, los prototipos de funciones permiten declarar funciones antes de que se utilicen en el código. Esto es útil para proporcionar al compilador información sobre la firma de la función, como su tipo de retorno y los tipos de parámetros que espera.&lt;/p&gt;

&lt;h2&gt;
  
  
  Ejemplo
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8m12ntzdl8wjghxxxpbn.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8m12ntzdl8wjghxxxpbn.png" alt="prototype-code-example" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Para este ejemplo, el prototipo de la función Saludo se declara antes de la función main. La función Saludo se define después de main. Esto permite que el compilador sepa cómo es esta función antes de que sea utilizada en el código principal. La función Saludo simplemente imprime "¡Hola, mundo!" en la consola cuando se llama desde main.&lt;/p&gt;

</description>
      <category>beginners</category>
      <category>cpp</category>
      <category>tutorial</category>
      <category>spanish</category>
    </item>
    <item>
      <title>Insertion sort in C++</title>
      <dc:creator>Paul Contreras</dc:creator>
      <pubDate>Thu, 29 Feb 2024 03:06:48 +0000</pubDate>
      <link>https://dev.to/paulcontr_/insertion-sort-in-c-25np</link>
      <guid>https://dev.to/paulcontr_/insertion-sort-in-c-25np</guid>
      <description>&lt;h2&gt;
  
  
  Introducción
&lt;/h2&gt;

&lt;p&gt;En términos simples el insertion sort es como ordenar una mano de cartas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Empiezas con la segunda carta y la comparas con la primera. Si la segunda carta es más pequeña, las intercambias.&lt;/li&gt;
&lt;li&gt;Ahora, las dos primeras cartas están ordenadas.&lt;/li&gt;
&lt;li&gt;Pasas a la tercera carta y la comparas con la segunda. Si es más pequeña, la mueves a la posición correcta entre las cartas ya ordenadas.&lt;/li&gt;
&lt;li&gt;Repites este proceso con cada carta hasta que toda la mano esté ordenada.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Mira este gif para entenderle mejor: &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidaqame73idr1bexz4cz.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fidaqame73idr1bexz4cz.gif" alt="insertion-sort-gif" width="500" height="300"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Programa para ordenar un array con inserción
&lt;/h2&gt;

&lt;p&gt;El primer paso es identificar las funciones que podremos utilizar para este programa, las cuales serian: &lt;code&gt;printArray&lt;/code&gt;, &lt;code&gt;fillArray&lt;/code&gt;, &lt;code&gt;insertion&lt;/code&gt;.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;printArray&lt;/code&gt;: Esta función se utiliza para imprimir el arreglo.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;fillArray&lt;/code&gt;: Esta función se utiliza para llenar el arreglo con los datos que da el usuario.&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;code&gt;insertion&lt;/code&gt;: Esta función se utiliza para realizar el ordenamiento.&lt;/p&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  &lt;code&gt;printArray&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;La función &lt;code&gt;printArray&lt;/code&gt; recibe un arreglo de números enteros y su tamaño. Luego, imprime los elementos del arreglo en la consola, separados por comas y encerrados entre corchetes. Por ejemplo, si se pasa el arreglo &lt;code&gt;{1, 2, 3, 4, 5}&lt;/code&gt;, la función imprimirá &lt;code&gt;[1, 2, 3, 4, 5]&lt;/code&gt;. Esto permite visualizar fácilmente los elementos del arreglo en un formato legible.&lt;br&gt;
Codigo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Print array function
void printArray( const int *userArray , int size){
    // Imprimir cada elemento con un for loop
    cout &amp;lt;&amp;lt; "[" &amp;lt;&amp;lt; userArray[0];
    for (int i=1; i &amp;lt; size; ++i){
        cout &amp;lt;&amp;lt; "," &amp;lt;&amp;lt; userArray[i];
    }
    cout &amp;lt;&amp;lt; "]" &amp;lt;&amp;lt; endl;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;fillArray&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;La función &lt;code&gt;fillArray&lt;/code&gt; recibe como argumento un puntero a un arreglo de enteros y el tamaño del arreglo. Utiliza un bucle &lt;code&gt;for&lt;/code&gt; para iterar sobre cada posición del arreglo. En cada iteración, le pide al usuario que ingrese un valor para esa posición del arreglo utilizando &lt;code&gt;cin &amp;gt;&amp;gt; userArray[i];&lt;/code&gt;. De esta manera, la función permite al usuario llenar un arreglo con valores ingresados desde el teclado.&lt;br&gt;
Codigo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Fill array
void fillArray(int *userArray, int size){
    // For loop para ingresar elementos
    for (int i = 0; i &amp;lt; size; ++i) {
        // utilizo i+1 para facilitar al usuario el ingresar el elemento
        cout &amp;lt;&amp;lt; "Ingrese elemento " &amp;lt;&amp;lt; i+1 &amp;lt;&amp;lt; " : ";
        // leemos de teclado y lo almacenamos en el arreglo en la posición i
        cin &amp;gt;&amp;gt; userArray[i];
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  &lt;code&gt;insertion&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;La función &lt;code&gt;insertion&lt;/code&gt;  toma un arreglo de números enteros, lo copia, lo ordena utilizando el algoritmo de ordenación por inserción y luego muestra el arreglo original y el arreglo ordenado. Esto ayuda a los principiantes a comprender cómo funciona el algoritmo de inserción y cómo se pueden ordenar los arreglos en la práctica.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Insertion algorithm
void insertion(const int *userArray, int size){
    // Print header
    cout &amp;lt;&amp;lt; setfill('-') &amp;lt;&amp;lt; setw(40) &amp;lt;&amp;lt; "" &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; "|           Insertion sort          |" &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; setfill('-') &amp;lt;&amp;lt; setw(40) &amp;lt;&amp;lt; "" &amp;lt;&amp;lt; endl;
    // Puntero temporal asociado al arreglo
    auto *sortedArray = new int[size];

    // Copia del arreglo dado por el usuario
    for (int i=0; i&amp;lt;size; ++i){
        sortedArray[i] = userArray[i];
    }

    // Insertion algorithm
    for (int i=1; i&amp;lt;size; ++i){
        int k = sortedArray[i];
        int j = i-1;
        while (j &amp;gt;= 0 &amp;amp;&amp;amp; sortedArray[j] &amp;gt; k){
            sortedArray[j+1] = sortedArray[j];
            --j;
        }
        sortedArray[j+1] = k;
    }

    // Mostrar resultados
    cout &amp;lt;&amp;lt; "Original array: ";
    printArray(userArray,size);
    cout &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; "Sorted array: ";
    printArray(sortedArray,size);
    cout &amp;lt;&amp;lt; endl;
    // Deallocate memory
    delete [] sortedArray;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Implementación
&lt;/h2&gt;

&lt;p&gt;Ahora que ya sabemos cuales funciones utilizaremos, tendremos que saber como implementarlas, en el archivo &lt;code&gt;main.cpp&lt;/code&gt; tenemos esto:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Encabezados
// librerías estándar
#include &amp;lt;iostream&amp;gt;
#include &amp;lt;iomanip&amp;gt;
using namespace std;

// Prototipo de las funciones 
// Esto se utiliza para no tener problema con la definición de las funciones 
// cuando se utilizan en el código principal. Ayuda al compilador a conocer 
// la firma de las funciones antes de que se definan más adelante en el código.

void printArray( const int *userArray , int size);
void fillArray(int *userArray, int size);
void insertion(const int *userArray, int size);

// Funcion principal

int main() {
        // Variable para almacenar el tamaño deseado del arreglo
    int size;
        // Variable para almacenar el arreglo de enteros ingresados por el usuario
        int *array;

    // Solicitamos la cantidad de elementos 
    cout &amp;lt;&amp;lt; "Ingrese la cantidad de elementos para su arreglo: ";
    cin &amp;gt;&amp;gt; size;
    // Definimos arreglo con el tamaño ingresado por el usuario
    array = new int[size];
    // Llenamos el arreglo
    fillArray(array, size);
    // Ordenamos el arreglo
    insertion(array, size);
    // deallocate memory
    delete [] array;
    return 0;
}

// Funciones desarrolladas
// Print array function
void printArray( const int *userArray , int size){
    // Imprimir cada elemento con un for loop
    cout &amp;lt;&amp;lt; "[" &amp;lt;&amp;lt; userArray[0];
    for (int i=1; i &amp;lt; size; ++i){
        cout &amp;lt;&amp;lt; "," &amp;lt;&amp;lt; userArray[i];
    }
    cout &amp;lt;&amp;lt; "]" &amp;lt;&amp;lt; endl;
}

// Fill array
void fillArray(int *userArray, int size){
    // For loop para ingresar elementos
    for (int i = 0; i &amp;lt; size; ++i) {
        // utilizo i+1 para facilitar al usuario el ingresar el elemento
        cout &amp;lt;&amp;lt; "Ingrese elemento " &amp;lt;&amp;lt; i+1 &amp;lt;&amp;lt; " : ";
        // leemos de teclado y lo almacenamos en el arreglo en la posición i
        cin &amp;gt;&amp;gt; userArray[i];
    }
}

// Insertion algorithm
void insertion(const int *userArray, int size){
    // Print header
    cout &amp;lt;&amp;lt; setfill('-') &amp;lt;&amp;lt; setw(40) &amp;lt;&amp;lt; "" &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; "|           Insertion sort          |" &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; setfill('-') &amp;lt;&amp;lt; setw(40) &amp;lt;&amp;lt; "" &amp;lt;&amp;lt; endl;
    // Puntero temporal asociado al arreglo
    auto *sortedArray = new int[size];

    // Copia del arreglo dado por el usuario
    for (int i=0; i&amp;lt;size; ++i){
        sortedArray[i] = userArray[i];
    }

    // Insertion algorithm
    for (int i=1; i&amp;lt;size; ++i){
        int k = sortedArray[i];
        int j = i-1;
        while (j &amp;gt;= 0 &amp;amp;&amp;amp; sortedArray[j] &amp;gt; k){
            sortedArray[j+1] = sortedArray[j];
            --j;
        }
        sortedArray[j+1] = k;
    }

    // Mostrar resultados
    cout &amp;lt;&amp;lt; "Original array: ";
    printArray(userArray,size);
    cout &amp;lt;&amp;lt; endl;
    cout &amp;lt;&amp;lt; "Sorted array: ";
    printArray(sortedArray,size);
    cout &amp;lt;&amp;lt; endl;
    // Deallocate memory
    delete [] sortedArray;
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Ejemplo de uso:
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygguvqpsx8j2r53aiswm.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fygguvqpsx8j2r53aiswm.png" alt="use-insertion" width="800" height="346"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;La ordenación por inserción es un método sencillo y rápido para ordenar elementos en un arreglo. Aunque es eficiente para conjuntos de datos pequeños, puede volverse menos eficaz con conjuntos de datos grandes. Es importante elegir el algoritmo de ordenación adecuado según el tamaño y la naturaleza de los datos a ordenar.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Puedes encontrar el codigo completo en:&lt;/strong&gt;&lt;a href="https://github.com/pol-cova/sorting" rel="noopener noreferrer"&gt;repositorio&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Gif utilizado author Swfung8:&lt;/strong&gt;&lt;a href="https://upload.wikimedia.org/wikipedia/commons/9/9c/Insertion-sort-example.gif" rel="noopener noreferrer"&gt;gif&lt;/a&gt;&lt;/p&gt;

</description>
      <category>cpp</category>
      <category>beginners</category>
      <category>tutorial</category>
      <category>programming</category>
    </item>
  </channel>
</rss>
