refactor: update titles and descriptions to use double quotes for consistency
fix: add newlines and formatting improvements in blog posts refactor: update import paths to use alias for better readability style: update global CSS to use double quotes for consistency chore: update tsconfig to include path aliasing for cleaner imports
This commit is contained in:
parent
05dfaed605
commit
33083907fb
22 changed files with 618 additions and 490 deletions
|
|
@ -1,11 +1,11 @@
|
||||||
// @ts-check
|
// @ts-check
|
||||||
import { defineConfig } from 'astro/config';
|
import { defineConfig } from "astro/config";
|
||||||
|
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from "@tailwindcss/vite";
|
||||||
|
|
||||||
// https://astro.build/config
|
// https://astro.build/config
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
vite: {
|
vite: {
|
||||||
plugins: [tailwindcss()]
|
plugins: [tailwindcss()],
|
||||||
}
|
},
|
||||||
});
|
});
|
||||||
62
package-lock.json
generated
62
package-lock.json
generated
|
|
@ -13,7 +13,9 @@
|
||||||
"tailwindcss": "^4.1.18"
|
"tailwindcss": "^4.1.18"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.19"
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"prettier-plugin-astro": "^0.14.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@astrojs/compiler": {
|
"node_modules/@astrojs/compiler": {
|
||||||
|
|
@ -4438,6 +4440,37 @@
|
||||||
"node": ">=4"
|
"node": ">=4"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.8.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.1.tgz",
|
||||||
|
"integrity": "sha512-UOnG6LftzbdaHZcKoPFtOcCKztrQ57WkHDeRD9t/PTQtmT0NHSeWWepj6pS0z/N7+08BHFDQVUrfmfMRcZwbMg==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/prettier-plugin-astro": {
|
||||||
|
"version": "0.14.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier-plugin-astro/-/prettier-plugin-astro-0.14.1.tgz",
|
||||||
|
"integrity": "sha512-RiBETaaP9veVstE4vUwSIcdATj6dKmXljouXc/DDNwBSPTp8FRkLGDSGFClKsAFeeg+13SB0Z1JZvbD76bigJw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@astrojs/compiler": "^2.9.1",
|
||||||
|
"prettier": "^3.0.0",
|
||||||
|
"sass-formatter": "^0.7.6"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.15.0 || >=16.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/prismjs": {
|
"node_modules/prismjs": {
|
||||||
"version": "1.30.0",
|
"version": "1.30.0",
|
||||||
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
"resolved": "https://registry.npmjs.org/prismjs/-/prismjs-1.30.0.tgz",
|
||||||
|
|
@ -4761,6 +4794,23 @@
|
||||||
"fsevents": "~2.3.2"
|
"fsevents": "~2.3.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/s.color": {
|
||||||
|
"version": "0.0.15",
|
||||||
|
"resolved": "https://registry.npmjs.org/s.color/-/s.color-0.0.15.tgz",
|
||||||
|
"integrity": "sha512-AUNrbEUHeKY8XsYr/DYpl+qk5+aM+DChopnWOPEzn8YKzOhv4l2zH6LzZms3tOZP3wwdOyc0RmTciyi46HLIuA==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
|
"node_modules/sass-formatter": {
|
||||||
|
"version": "0.7.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/sass-formatter/-/sass-formatter-0.7.9.tgz",
|
||||||
|
"integrity": "sha512-CWZ8XiSim+fJVG0cFLStwDvft1VI7uvXdCNJYXhDvowiv+DsbD1nXLiQ4zrE5UBvj5DWZJ93cwN0NX5PMsr1Pw==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"suf-log": "^2.5.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/sax": {
|
"node_modules/sax": {
|
||||||
"version": "1.4.4",
|
"version": "1.4.4",
|
||||||
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
"resolved": "https://registry.npmjs.org/sax/-/sax-1.4.4.tgz",
|
||||||
|
|
@ -4926,6 +4976,16 @@
|
||||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/suf-log": {
|
||||||
|
"version": "2.5.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/suf-log/-/suf-log-2.5.3.tgz",
|
||||||
|
"integrity": "sha512-KvC8OPjzdNOe+xQ4XWJV2whQA0aM1kGVczMQ8+dStAO6KfEB140JEVQ9dE76ONZ0/Ylf67ni4tILPJB41U0eow==",
|
||||||
|
"dev": true,
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"s.color": "0.0.15"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/svgo": {
|
"node_modules/svgo": {
|
||||||
"version": "4.0.0",
|
"version": "4.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/svgo/-/svgo-4.0.0.tgz",
|
||||||
|
|
|
||||||
|
|
@ -6,7 +6,8 @@
|
||||||
"dev": "astro dev",
|
"dev": "astro dev",
|
||||||
"build": "astro build",
|
"build": "astro build",
|
||||||
"preview": "astro preview",
|
"preview": "astro preview",
|
||||||
"astro": "astro"
|
"astro": "astro",
|
||||||
|
"format": "prettier --write ."
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@tailwindcss/vite": "^4.1.18",
|
"@tailwindcss/vite": "^4.1.18",
|
||||||
|
|
@ -14,6 +15,8 @@
|
||||||
"tailwindcss": "^4.1.18"
|
"tailwindcss": "^4.1.18"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@tailwindcss/typography": "^0.5.19"
|
"@tailwindcss/typography": "^0.5.19",
|
||||||
|
"prettier": "^3.8.1",
|
||||||
|
"prettier-plugin-astro": "^0.14.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
import NavLink from "./NavLink.astro";
|
import NavLink from "@/components/NavLink.astro";
|
||||||
---
|
---
|
||||||
|
|
||||||
<header class="py-4 text-black dark:text-white w-full">
|
<header class="py-4 text-black dark:text-white w-full">
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
---
|
---
|
||||||
import { Image } from 'astro:assets';
|
import { Image } from 'astro:assets';
|
||||||
import profileImage from "../assets/images/profile.jpg";
|
import profileImage from "@/assets/images/profile.jpg";
|
||||||
import socialEmail from "../assets/images/social-email.svg";
|
import socialEmail from "@/assets/images/social-email.svg";
|
||||||
import socialFacebook from "../assets/images/social-facebook.svg";
|
import socialFacebook from "@/assets/images/social-facebook.svg";
|
||||||
import socialGithub from "../assets/images/social-github.svg";
|
import socialGithub from "@/assets/images/social-github.svg";
|
||||||
import socialLinkedIn from "../assets/images/social-linkedin.svg";
|
import socialLinkedIn from "@/assets/images/social-linkedin.svg";
|
||||||
import socialX from "../assets/images/social-x.svg";
|
import socialX from "@/assets/images/social-x.svg";
|
||||||
---
|
---
|
||||||
|
|
||||||
<div class="flex flex-col min-h-full items-center justify-center">
|
<div class="flex flex-col min-h-full items-center justify-center">
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 'CRDT - Conflict Free Replicated Data Types'
|
title: "CRDT - Conflict Free Replicated Data Types"
|
||||||
description: 'A brief intro to Conflict Free Replicated Data Types'
|
description: "A brief intro to Conflict Free Replicated Data Types"
|
||||||
date: 2025-02-24T09:58:08+07:00
|
date: 2025-02-24T09:58:08+07:00
|
||||||
draft: false
|
draft: false
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -10,6 +10,7 @@ tags:
|
||||||
---
|
---
|
||||||
|
|
||||||
# Background
|
# Background
|
||||||
|
|
||||||
When searching techniques for syncing data between peers, I stumbled upon CRDT (Conflict-free Replicated Data Types). It's basically a algorithm for syncing for distributed systems. CRDT ensures all data changes between peer will be synced with correct order and no data loss.
|
When searching techniques for syncing data between peers, I stumbled upon CRDT (Conflict-free Replicated Data Types). It's basically a algorithm for syncing for distributed systems. CRDT ensures all data changes between peer will be synced with correct order and no data loss.
|
||||||
|
|
||||||
Since I working with Dart (for Flutter project), I use a [CRDT library for Dart](https://github.com/cachapa/crdt). This library implements core concept of CRDT and it's pretty basic. Here some types of CRDT that often used:
|
Since I working with Dart (for Flutter project), I use a [CRDT library for Dart](https://github.com/cachapa/crdt). This library implements core concept of CRDT and it's pretty basic. Here some types of CRDT that often used:
|
||||||
|
|
@ -21,13 +22,20 @@ Since I working with Dart (for Flutter project), I use a [CRDT library for Dart]
|
||||||
5. **OR-Set (Observed Remove Set)**: A set that allows elements to be added and removed, using unique identifiers to track additions and removals.
|
5. **OR-Set (Observed Remove Set)**: A set that allows elements to be added and removed, using unique identifiers to track additions and removals.
|
||||||
6. **LWW-Register (Last Write Wins Register)**: A register that stores the last written value, using a timestamp to determine the most recent update.
|
6. **LWW-Register (Last Write Wins Register)**: A register that stores the last written value, using a timestamp to determine the most recent update.
|
||||||
7. **MV-Register (Multi-Value Register)**: A register that stores all values that have been written, using unique identifiers to track writes.
|
7. **MV-Register (Multi-Value Register)**: A register that stores all values that have been written, using unique identifiers to track writes.
|
||||||
|
|
||||||
# How it works -- basic version
|
# How it works -- basic version
|
||||||
|
|
||||||
Main components:
|
Main components:
|
||||||
|
|
||||||
- HLC (Hardware Logical Clock). Combines _wall clock/local time_, a counter that increments, and an optional _node ID_ for uniqueness sake.
|
- HLC (Hardware Logical Clock). Combines _wall clock/local time_, a counter that increments, and an optional _node ID_ for uniqueness sake.
|
||||||
- The data itself (usually contains a key, value, and the HLC object).
|
- The data itself (usually contains a key, value, and the HLC object).
|
||||||
|
|
||||||
## **Scenario: Two Devices Synchronizing Data**
|
## **Scenario: Two Devices Synchronizing Data**
|
||||||
|
|
||||||
We have two devices, **Device A** and **Device B**, which both maintain their own local datasets. Each device can modify data independently. When they synchronize, their CRDT implementations will merge their changes and resolve conflicts.
|
We have two devices, **Device A** and **Device B**, which both maintain their own local datasets. Each device can modify data independently. When they synchronize, their CRDT implementations will merge their changes and resolve conflicts.
|
||||||
|
|
||||||
## Initial State
|
## Initial State
|
||||||
|
|
||||||
- Both devices start with the same data:
|
- Both devices start with the same data:
|
||||||
|
|
||||||
| Key | Value | isDeleted | Last Modified |
|
| Key | Value | isDeleted | Last Modified |
|
||||||
|
|
@ -39,7 +47,9 @@ We have two devices, **Device A** and **Device B**, which both maintain their ow
|
||||||
- Device B's last modified HLC: `A2`.
|
- Device B's last modified HLC: `A2`.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Changes Made on Each Device
|
## Changes Made on Each Device
|
||||||
|
|
||||||
1. **Device A deletes Bob's record (key 2):**
|
1. **Device A deletes Bob's record (key 2):**
|
||||||
|
|
||||||
| Key | Value | isDeleted | Last Modified |
|
| Key | Value | isDeleted | Last Modified |
|
||||||
|
|
@ -49,11 +59,13 @@ We have two devices, **Device A** and **Device B**, which both maintain their ow
|
||||||
2. **Device B updates Alice's name to Alice Smith (key 1):**
|
2. **Device B updates Alice's name to Alice Smith (key 1):**
|
||||||
|
|
||||||
| Key | Value | isDeleted | Last Modified |
|
| Key | Value | isDeleted | Last Modified |
|
||||||
| --- | ----- | --------- | ------------- |
|
| --- | ----------- | --------- | ------------- |
|
||||||
| 1 | Alice Smith | false | HLC: B3 |
|
| 1 | Alice Smith | false | HLC: B3 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Synchronization and Merge
|
## Synchronization and Merge
|
||||||
|
|
||||||
- Device A sends its **changeset** to Device B:
|
- Device A sends its **changeset** to Device B:
|
||||||
|
|
||||||
| Key | Value | isDeleted | Last Modified |
|
| Key | Value | isDeleted | Last Modified |
|
||||||
|
|
@ -63,16 +75,17 @@ We have two devices, **Device A** and **Device B**, which both maintain their ow
|
||||||
- Device B sends its **changeset** to Device A:
|
- Device B sends its **changeset** to Device A:
|
||||||
|
|
||||||
| Key | Value | isDeleted | Last Modified |
|
| Key | Value | isDeleted | Last Modified |
|
||||||
| --- | ----- | --------- | ------------- |
|
| --- | ----------- | --------- | ------------- |
|
||||||
| 1 | Alice Smith | false | HLC: B3 |
|
| 1 | Alice Smith | false | HLC: B3 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step-by-Step Conflict Resolution
|
## Step-by-Step Conflict Resolution
|
||||||
|
|
||||||
The `merge` method processes these changes:
|
The `merge` method processes these changes:
|
||||||
|
|
||||||
1. **Validate Changeset**:
|
1. **Validate Changeset**:
|
||||||
Each incoming record is validated to ensure it matches the expected schema and contains valid HLC timestamps.
|
Each incoming record is validated to ensure it matches the expected schema and contains valid HLC timestamps.
|
||||||
|
|
||||||
2. **Compare Records for Key 1 (`Alice`)**:
|
2. **Compare Records for Key 1 (`Alice`)**:
|
||||||
|
|
||||||
| Key | Value | isDeleted | Last Modified |
|
| Key | Value | isDeleted | Last Modified |
|
||||||
|
|
@ -87,7 +100,7 @@ Incoming record from Device B:
|
||||||
**Conflict Resolution Rule**: The record with the higher `Last Modified` HLC wins. HLC `B3 > A1,` so Device A updates Alice's record to:
|
**Conflict Resolution Rule**: The record with the higher `Last Modified` HLC wins. HLC `B3 > A1,` so Device A updates Alice's record to:
|
||||||
|
|
||||||
| Key | Value | isDeleted | Last Modified |
|
| Key | Value | isDeleted | Last Modified |
|
||||||
| --- | ----- | --------- | ------------- |
|
| --- | ----------- | --------- | ------------- |
|
||||||
| 1 | Alice Smith | false | HLC: B3 |
|
| 1 | Alice Smith | false | HLC: B3 |
|
||||||
|
|
||||||
3. **Compare Records for Key 2 (`Bob`)**:
|
3. **Compare Records for Key 2 (`Bob`)**:
|
||||||
|
|
@ -111,14 +124,16 @@ Incoming record from Device A:
|
||||||
Both devices now have identical datasets after merging.
|
Both devices now have identical datasets after merging.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Final Merged Dataset on Both Devices
|
## Final Merged Dataset on Both Devices
|
||||||
|
|
||||||
| Key | Value | isDeleted | Last Modified |
|
| Key | Value | isDeleted | Last Modified |
|
||||||
| --- | ----- | --------- | ------------- |
|
| --- | ----------- | --------- | ------------- |
|
||||||
| 1 | Alice Smith | false | HLC: B3 |
|
| 1 | Alice Smith | false | HLC: B3 |
|
||||||
| 2 | null | true | HLC: A3 |
|
| 2 | null | true | HLC: A3 |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Summary of Conflict Resolution Rules
|
## Summary of Conflict Resolution Rules
|
||||||
|
|
||||||
1. **Higher HLC Wins**
|
1. **Higher HLC Wins**
|
||||||
|
|
@ -127,4 +142,3 @@ Incoming record from Device A:
|
||||||
A `null` value with `isDeleted: true` is treated as a soft delete. It wins if its HLC is higher.
|
A `null` value with `isDeleted: true` is treated as a soft delete. It wins if its HLC is higher.
|
||||||
3. **Deterministic Behavior**
|
3. **Deterministic Behavior**
|
||||||
All nodes independently apply the same conflict resolution logic, ensuring eventual consistency.
|
All nodes independently apply the same conflict resolution logic, ensuring eventual consistency.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 'Fix ADB Insufficient Permission'
|
title: "Fix ADB Insufficient Permission"
|
||||||
description: 'udev rules for fixing ADB Insufficient permission in Linux'
|
description: "udev rules for fixing ADB Insufficient permission in Linux"
|
||||||
date: 2025-02-24T11:29:30+07:00
|
date: 2025-02-24T11:29:30+07:00
|
||||||
draft: false
|
draft: false
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -10,6 +10,7 @@ tags:
|
||||||
---
|
---
|
||||||
|
|
||||||
# Why need this?
|
# Why need this?
|
||||||
|
|
||||||
Connecting an Android device to a Linux computer could has problem with permission. Listing devices with `adb devices` might show `unauthorized` status or `insufficient permission` error message.
|
Connecting an Android device to a Linux computer could has problem with permission. Listing devices with `adb devices` might show `unauthorized` status or `insufficient permission` error message.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -33,7 +34,7 @@ $ lsusb
|
||||||
Bus 001 Device 005: ID 18d1:4ee7 Google Inc. Nexus/Pixel Device
|
Bus 001 Device 005: ID 18d1:4ee7 Google Inc. Nexus/Pixel Device
|
||||||
```
|
```
|
||||||
|
|
||||||
`18d1` is *vendor id* and `4ee7` is the *product id*.
|
`18d1` is _vendor id_ and `4ee7` is the _product id_.
|
||||||
|
|
||||||
2. Create an `udev` rule for this device, I create mine in `/etc/udev/rules.d/51-android.rules`.
|
2. Create an `udev` rule for this device, I create mine in `/etc/udev/rules.d/51-android.rules`.
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 'Fix Infinix Air Pro+ Quad Speakers in Linux'
|
title: "Fix Infinix Air Pro+ Quad Speakers in Linux"
|
||||||
description: 'A fix for Infinix Air Pro+ -- only 2 of 4 speakers working in Linux'
|
description: "A fix for Infinix Air Pro+ -- only 2 of 4 speakers working in Linux"
|
||||||
date: 2025-02-23T23:27:49+07:00
|
date: 2025-02-23T23:27:49+07:00
|
||||||
draft: false
|
draft: false
|
||||||
tags:
|
tags:
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 'Fix Infinix Air Pro+ Screen Color'
|
title: "Fix Infinix Air Pro+ Screen Color"
|
||||||
description: 'Fixing Infinix Air Pro+ washed out screen color in Windows and Linux'
|
description: "Fixing Infinix Air Pro+ washed out screen color in Windows and Linux"
|
||||||
date: 2025-02-21T18:09:54+07:00
|
date: 2025-02-21T18:09:54+07:00
|
||||||
draft: false
|
draft: false
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -17,12 +17,15 @@ First time I noticed this issue is because I was using a pitch black wallpaper i
|
||||||
So, I was wondering if I create a script that will turn the brightness to above 50% and restore it to where it was every time my screen is waking up from a sleep. With a help from Google and ChatGPT, I create these scripts as a workaround for this annoying issue.
|
So, I was wondering if I create a script that will turn the brightness to above 50% and restore it to where it was every time my screen is waking up from a sleep. With a help from Google and ChatGPT, I create these scripts as a workaround for this annoying issue.
|
||||||
|
|
||||||
# Windows
|
# Windows
|
||||||
|
|
||||||
Before continuing, I'm sorry I can't give any screenshot for this Windows section because I already switched to Linux, but I hope I can write it clearly.
|
Before continuing, I'm sorry I can't give any screenshot for this Windows section because I already switched to Linux, but I hope I can write it clearly.
|
||||||
|
|
||||||
## Get screen wake up event
|
## Get screen wake up event
|
||||||
I need to listen to an event that tells me "Hey, the screen is turning on". Fortunately, Windows has [Event Viewer](https://learn.microsoft.com/en-us/shows/inside/event-viewer) that I can use for this. I found that an event from *Kernel-Power* with event ID *507* is the correct event that means the screen in turned back on.
|
|
||||||
|
I need to listen to an event that tells me "Hey, the screen is turning on". Fortunately, Windows has [Event Viewer](https://learn.microsoft.com/en-us/shows/inside/event-viewer) that I can use for this. I found that an event from _Kernel-Power_ with event ID _507_ is the correct event that means the screen in turned back on.
|
||||||
|
|
||||||
## Script
|
## Script
|
||||||
|
|
||||||
Next thing to do is create the script to control screen brightness. After trial and error, I found [NirCmd](https://www.nirsoft.net/utils/nircmd.html) can help me to change my screen brightness. Then I create this Powershell script.
|
Next thing to do is create the script to control screen brightness. After trial and error, I found [NirCmd](https://www.nirsoft.net/utils/nircmd.html) can help me to change my screen brightness. Then I create this Powershell script.
|
||||||
|
|
||||||
```powershell
|
```powershell
|
||||||
|
|
@ -64,17 +67,21 @@ if ($currentBrightness -lt 50) {
|
||||||
```
|
```
|
||||||
|
|
||||||
## Make a schedule
|
## Make a schedule
|
||||||
I use Windows' [Task Scheduler](https://www.windowscentral.com/how-create-automated-tasks-windows-11) to run the script each time *Kernel-Power* with event ID *507* occurs. I can't show the step-by-step guide because I'm on Linux now, but I have a backup file for this task. All you need is just to import [this task](/misc/Restore%20OLED%20Colors.xml) in Task Scheduler.
|
|
||||||
|
I use Windows' [Task Scheduler](https://www.windowscentral.com/how-create-automated-tasks-windows-11) to run the script each time _Kernel-Power_ with event ID _507_ occurs. I can't show the step-by-step guide because I'm on Linux now, but I have a backup file for this task. All you need is just to import [this task](/misc/Restore%20OLED%20Colors.xml) in Task Scheduler.
|
||||||
|
|
||||||
> Note: You have to change the command it executes to where you save the Powershell script. Also change the author into `YOUR_PC_NAME\YOUR_USERNAME`.
|
> Note: You have to change the command it executes to where you save the Powershell script. Also change the author into `YOUR_PC_NAME\YOUR_USERNAME`.
|
||||||
|
|
||||||
# Linux
|
# Linux
|
||||||
|
|
||||||
I'm using [EndeavourOS](https://endeavouros.com/) which use `systemd`. So this guide is applicable to `systemd` init system only. If your linux use something else, you need to adjust it with your init system.
|
I'm using [EndeavourOS](https://endeavouros.com/) which use `systemd`. So this guide is applicable to `systemd` init system only. If your linux use something else, you need to adjust it with your init system.
|
||||||
|
|
||||||
## Get screen wake up event
|
## Get screen wake up event
|
||||||
|
|
||||||
I already tried several ways to listen the screen wake up events. But I can't find any using `acpi` and `udev`. So I tried different approach. I check `dpms` property from screen device in `/sys/class/drm/card1-eDP-1/dpms`. It has `On` and `Off` value that I can use for triggering a script to fix the color.
|
I already tried several ways to listen the screen wake up events. But I can't find any using `acpi` and `udev`. So I tried different approach. I check `dpms` property from screen device in `/sys/class/drm/card1-eDP-1/dpms`. It has `On` and `Off` value that I can use for triggering a script to fix the color.
|
||||||
|
|
||||||
## Script
|
## Script
|
||||||
|
|
||||||
I have 2 scripts for this approach. One for checking `/sys/class/drm/card1-eDP-1/dpms` value and another one for fixing the color.
|
I have 2 scripts for this approach. One for checking `/sys/class/drm/card1-eDP-1/dpms` value and another one for fixing the color.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
|
|
@ -137,6 +144,7 @@ fi
|
||||||
```
|
```
|
||||||
|
|
||||||
## Make a systemd service
|
## Make a systemd service
|
||||||
|
|
||||||
Make a `systemd` service in `/etc/systemd/system/brightness-fix.service` to run the first script.
|
Make a `systemd` service in `/etc/systemd/system/brightness-fix.service` to run the first script.
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 'Android Devices Not Showing on Flutter Project'
|
title: "Android Devices Not Showing on Flutter Project"
|
||||||
description: "A fix for new Flutter project that doesn't have Android devices showing up"
|
description: "A fix for new Flutter project that doesn't have Android devices showing up"
|
||||||
date: 2025-02-22T09:21:22+07:00
|
date: 2025-02-22T09:21:22+07:00
|
||||||
draft: false
|
draft: false
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
title: 'Flutter Clean Architecture'
|
title: "Flutter Clean Architecture"
|
||||||
description: Dive into Clean Architecture for Flutter or Dart projects
|
description: Dive into Clean Architecture for Flutter or Dart projects
|
||||||
images:
|
images:
|
||||||
- /images/opengraph.png
|
- /images/opengraph.png
|
||||||
|
|
@ -13,14 +13,17 @@ tags:
|
||||||

|

|
||||||
|
|
||||||
# What is Clean Architecture?
|
# What is Clean Architecture?
|
||||||
Do you ever wondering how to manage your Flutter code? How to make it neat, modular, easy to maintain and test? Here where *clean architecture* comes in.
|
|
||||||
|
Do you ever wondering how to manage your Flutter code? How to make it neat, modular, easy to maintain and test? Here where _clean architecture_ comes in.
|
||||||
|
|
||||||
Basically, clean architecture is a way to organize your code into separated pieces that will make your project cleaner. It may looks complicated at first and a lot of boiler code for some reasons. But trust me, it will be a lot easier if you apply the clean architecture in your code, especially in medium to bigger projects.
|
Basically, clean architecture is a way to organize your code into separated pieces that will make your project cleaner. It may looks complicated at first and a lot of boiler code for some reasons. But trust me, it will be a lot easier if you apply the clean architecture in your code, especially in medium to bigger projects.
|
||||||
|
|
||||||
In this set of Clean Architecture articles, we will create a basic mobile app that uses [WeatherAPI](https://www.weatherapi.com/) to get current weather. Let's get started!
|
In this set of Clean Architecture articles, we will create a basic mobile app that uses [WeatherAPI](https://www.weatherapi.com/) to get current weather. Let's get started!
|
||||||
|
|
||||||
> Please note that this guide requires basic knowledge of Dart and Flutter. So I don't recommend going through this guide if you are completely new to the topic.
|
> Please note that this guide requires basic knowledge of Dart and Flutter. So I don't recommend going through this guide if you are completely new to the topic.
|
||||||
|
|
||||||
# Directory Structure
|
# Directory Structure
|
||||||
|
|
||||||
I use this directory structure to organize my code into clean architecture. Once you got the idea, you may modify the structure to match your needs.
|
I use this directory structure to organize my code into clean architecture. Once you got the idea, you may modify the structure to match your needs.
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
@ -57,9 +60,13 @@ your-flutter-project-dir
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Core
|
## Core
|
||||||
|
|
||||||
You'll stores all reusable code inside `core`. Things like abstract classes (maybe a model base, error base, etc), or maybe a base widgets, snackbars, dialogs, also your app router, anything that you need to access across your app are best to keep inside `core` directory.
|
You'll stores all reusable code inside `core`. Things like abstract classes (maybe a model base, error base, etc), or maybe a base widgets, snackbars, dialogs, also your app router, anything that you need to access across your app are best to keep inside `core` directory.
|
||||||
|
|
||||||
### Core - Data
|
### Core - Data
|
||||||
|
|
||||||
`core/data` stores base classes related to your data. Divided into `local` for locally-stored data (ex: configs, persistence, cache), and `remote` for data from external sources (ex: web API).
|
`core/data` stores base classes related to your data. Divided into `local` for locally-stored data (ex: configs, persistence, cache), and `remote` for data from external sources (ex: web API).
|
||||||
|
|
||||||
Let's create a local `config.dart` base class to store app configuration using [shared_preferences](https://pub.dev/packages/shared_preferences) .
|
Let's create a local `config.dart` base class to store app configuration using [shared_preferences](https://pub.dev/packages/shared_preferences) .
|
||||||
|
|
@ -179,12 +186,14 @@ class WeatherApiErrorModel {
|
||||||
_$WeatherApiErrorModelFromJson(json);
|
_$WeatherApiErrorModelFromJson(json);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Core - Domain
|
### Core - Domain
|
||||||
`core/domain` contains *use case* base class. If you unfamiliar with a *use case* (also called *unit-of-work*), it's a **single-purpose** class that has a method `execute/call` to do particular function in your app. We'll find out how it works in several sections ahead.
|
|
||||||
|
`core/domain` contains _use case_ base class. If you unfamiliar with a _use case_ (also called _unit-of-work_), it's a **single-purpose** class that has a method `execute/call` to do particular function in your app. We'll find out how it works in several sections ahead.
|
||||||
|
|
||||||
In this class we use [fpdart](https://pub.dev/packages/fpdart)'s `Either` class. In [Functional Programming](), `Either` means a function that will return a `Right` value for positive/success scenario, or `Left` when it fails. You can read about it in the previous links.
|
In this class we use [fpdart](https://pub.dev/packages/fpdart)'s `Either` class. In [Functional Programming](), `Either` means a function that will return a `Right` value for positive/success scenario, or `Left` when it fails. You can read about it in the previous links.
|
||||||
|
|
||||||
I'll try to explain briefly, `use_case.dart` below has 2 generics. `Type` is a return type when the *use case* is succesfully executed, and `Params` contains parameters that are required to execute the *use case*. Then in `call` method it has return type of `Either<Failure, Type>`. It means this method will returns `Type` if success, and `Failure` when things got ugly.
|
I'll try to explain briefly, `use_case.dart` below has 2 generics. `Type` is a return type when the _use case_ is succesfully executed, and `Params` contains parameters that are required to execute the _use case_. Then in `call` method it has return type of `Either<Failure, Type>`. It means this method will returns `Type` if success, and `Failure` when things got ugly.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
// lib/core/domain/use_case.dart
|
// lib/core/domain/use_case.dart
|
||||||
|
|
@ -199,7 +208,9 @@ abstract class UseCase<Type, Params> {
|
||||||
Future<Either<Failure, Type>> call(Params params);
|
Future<Either<Failure, Type>> call(Params params);
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Core - Error
|
### Core - Error
|
||||||
|
|
||||||
We'll use `core/error` dir to stores `Failure` classes. `Failure` used when the app throws errors and exceptions. It's like having a custom exception class.
|
We'll use `core/error` dir to stores `Failure` classes. `Failure` used when the app throws errors and exceptions. It's like having a custom exception class.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
|
@ -262,7 +273,9 @@ class UnauthorizedException implements Exception {
|
||||||
final String message;
|
final String message;
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Core - Network
|
### Core - Network
|
||||||
|
|
||||||
We will need a HTTP client to get data from [WeatherAPI](https://www.weatherapi.com/). I'll use [http](https://pub.dev/packages/http) package, but you can also use [dio](https://pub.dev/packages/dio) or another similar packages.
|
We will need a HTTP client to get data from [WeatherAPI](https://www.weatherapi.com/). I'll use [http](https://pub.dev/packages/http) package, but you can also use [dio](https://pub.dev/packages/dio) or another similar packages.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
|
@ -309,8 +322,10 @@ class NetworkImpl implements Network {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
If you are still new in programming, you may wonder: *Why I should create an abstract class here? It will be okay with a concrete Network class without inheritance*. I'll explain it later, but for now is enough for you to know that this abstract class will be used as a *mock* in testing.
|
If you are still new in programming, you may wonder: _Why I should create an abstract class here? It will be okay with a concrete Network class without inheritance_. I'll explain it later, but for now is enough for you to know that this abstract class will be used as a _mock_ in testing.
|
||||||
|
|
||||||
### Core - Presentation
|
### Core - Presentation
|
||||||
|
|
||||||
`core/presentation` contains UI widgets and other presentation related classes that will be used across your app. We can also have a UI-related business logic that will be used across the app. Since our app will have theme mode switching feature, we will add a `cubit` to do the theme mode switch here.
|
`core/presentation` contains UI widgets and other presentation related classes that will be used across your app. We can also have a UI-related business logic that will be used across the app. Since our app will have theme mode switching feature, we will add a `cubit` to do the theme mode switch here.
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
|
@ -364,9 +379,13 @@ class ThemeModeCubit extends Cubit<ThemeMode> {
|
||||||
emit(themeMode);
|
emit(themeMode);
|
||||||
}}
|
}}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Core - Routes
|
### Core - Routes
|
||||||
|
|
||||||
There is a package called [auto_route](https://pub.dev/packages/auto_route) that will ease you to manage routes in your app yet keep your code clean. Using the guide from their package page, we'll have `app_router.dart` inside `core/routes` directory. Since we don't have any page to route to yet, just leave it empty.
|
There is a package called [auto_route](https://pub.dev/packages/auto_route) that will ease you to manage routes in your app yet keep your code clean. Using the guide from their package page, we'll have `app_router.dart` inside `core/routes` directory. Since we don't have any page to route to yet, just leave it empty.
|
||||||
|
|
||||||
### Env File
|
### Env File
|
||||||
|
|
||||||
Storing secret directly in the code is a bad practice and we shouldn't do that. There are many ways to hardcode it into the code and one of them is to have an `env` file. You can read more about it [here](https://dart.dev/libraries/core/environment-declarations).
|
Storing secret directly in the code is a bad practice and we shouldn't do that. There are many ways to hardcode it into the code and one of them is to have an `env` file. You can read more about it [here](https://dart.dev/libraries/core/environment-declarations).
|
||||||
|
|
||||||
```dart
|
```dart
|
||||||
|
|
@ -387,9 +406,13 @@ class EnvImpl implements Env {
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Feature
|
### Feature
|
||||||
|
|
||||||
In clean architecture, we divide our application into **features**. For example, in this project will have a **weather** feature. Each feature will have its own (but not always neccessarily) `domain`, `data` and `presentation`.
|
In clean architecture, we divide our application into **features**. For example, in this project will have a **weather** feature. Each feature will have its own (but not always neccessarily) `domain`, `data` and `presentation`.
|
||||||
|
|
||||||
### Feature - Data
|
### Feature - Data
|
||||||
|
|
||||||
`data` as it's namesake, will deals with all data needed by the app. It contains (not limited to) `model`, `repository` implementation, and `data sources`. `Model` and `repository` classes are self-explanatory, and `data sources` will be used to access data from both local and remote sources.
|
`data` as it's namesake, will deals with all data needed by the app. It contains (not limited to) `model`, `repository` implementation, and `data sources`. `Model` and `repository` classes are self-explanatory, and `data sources` will be used to access data from both local and remote sources.
|
||||||
|
|
||||||
Let's create a model for [WeatherAPI](https://www.weatherapi.com/) current weather response.
|
Let's create a model for [WeatherAPI](https://www.weatherapi.com/) current weather response.
|
||||||
|
|
@ -504,7 +527,9 @@ class WeatherApiRemoteDataSourceImpl implements WeatherApiRemoteDataSource {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Feature - Domain
|
### Feature - Domain
|
||||||
|
|
||||||
`domain` stores `entities`, `use cases` and `abstract repository` classes, as they are the ‘domain’ or ‘subject’ area of an application. If you aren’t familiar with the term, you can think that this ‘domain’ is the base requirement of an application.
|
`domain` stores `entities`, `use cases` and `abstract repository` classes, as they are the ‘domain’ or ‘subject’ area of an application. If you aren’t familiar with the term, you can think that this ‘domain’ is the base requirement of an application.
|
||||||
|
|
||||||
First thing, we need to create an `entity` for current weather data. This entity will represent what kind of data we want to show to the user.
|
First thing, we need to create an `entity` for current weather data. This entity will represent what kind of data we want to show to the user.
|
||||||
|
|
@ -654,7 +679,9 @@ class WeatherApiRepositoryImpl implements WeatherApiRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Feature - Presentation
|
### Feature - Presentation
|
||||||
|
|
||||||
`presentation` stores **pages** and **widgets**. These are the 'presentation' or 'view' area of the application. If you aren't familiar with the term, you can think that this 'presentation' is the actual view of an application.
|
`presentation` stores **pages** and **widgets**. These are the 'presentation' or 'view' area of the application. If you aren't familiar with the term, you can think that this 'presentation' is the actual view of an application.
|
||||||
|
|
||||||
In this `presentation` layer, we use [auto_route](https://pub.dev/packages/auto_route) package to manage our pages routing. Then [flutter_bloc](https://pub.dev/packages/flutter_bloc) package will help us to manage state management hence keeping our code clean because we will separate the logic from the UI.
|
In this `presentation` layer, we use [auto_route](https://pub.dev/packages/auto_route) package to manage our pages routing. Then [flutter_bloc](https://pub.dev/packages/flutter_bloc) package will help us to manage state management hence keeping our code clean because we will separate the logic from the UI.
|
||||||
|
|
@ -1032,7 +1059,9 @@ class AppRouter extends RootStackRouter {
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Dependency Injection
|
## Dependency Injection
|
||||||
|
|
||||||
In clean architecture, we use **dependency injection** (or DI in short) to make our project cleaner. In a traditional way of creating an instance, we need to use **contructor injection** as we pass the required parameters to the constructor. It will make a mess if we are creating many instances throughout the project, because it will scattered anywhere.
|
In clean architecture, we use **dependency injection** (or DI in short) to make our project cleaner. In a traditional way of creating an instance, we need to use **contructor injection** as we pass the required parameters to the constructor. It will make a mess if we are creating many instances throughout the project, because it will scattered anywhere.
|
||||||
|
|
||||||
There are many ways to achieve dependency injection in Flutter, for this project I will use [GetIt](https://pub.dev/packages/get_it). Let's create our `injection_container`, this class is responsible for creating all the instances that we need in our project.
|
There are many ways to achieve dependency injection in Flutter, for this project I will use [GetIt](https://pub.dev/packages/get_it). Let's create our `injection_container`, this class is responsible for creating all the instances that we need in our project.
|
||||||
|
|
@ -1208,7 +1237,9 @@ class WeatherApp extends StatelessWidget {
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Testing
|
## Testing
|
||||||
|
|
||||||
.
|
.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 'Kuwot'
|
title: "Kuwot"
|
||||||
description: 'Flutter Daily Quote App'
|
description: "Flutter Daily Quote App"
|
||||||
date: 2025-02-20T15:26:04+07:00
|
date: 2025-02-20T15:26:04+07:00
|
||||||
draft: true
|
draft: true
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -11,9 +11,11 @@ tags:
|
||||||
I made [Kuwot](https://play.google.com/store/apps/details?id=com.dhemasnurjaya.kuwot) last year in my spare time. I want to share it, you can use it as an inspiration, practice app, or something else is up to you since I open-sourced the code (links below).
|
I made [Kuwot](https://play.google.com/store/apps/details?id=com.dhemasnurjaya.kuwot) last year in my spare time. I want to share it, you can use it as an inspiration, practice app, or something else is up to you since I open-sourced the code (links below).
|
||||||
|
|
||||||
# Introduction
|
# Introduction
|
||||||
|
|
||||||
The Flutter app itself was built using [Clean Architecture]({{< ref "/posts/flutter-clean-architecture/index.md" >}}). It has all basic features from a daily quote application. At first, my idea is to make a simple daily quote application. Showing random quote everytime the app is opened, and give it an image background to make it more appealing.
|
The Flutter app itself was built using [Clean Architecture]({{< ref "/posts/flutter-clean-architecture/index.md" >}}). It has all basic features from a daily quote application. At first, my idea is to make a simple daily quote application. Showing random quote everytime the app is opened, and give it an image background to make it more appealing.
|
||||||
|
|
||||||
# Hunting for quote data
|
# Hunting for quote data
|
||||||
|
|
||||||
First thing I did was searching some kind of quote data that available for free. I stumbled across several choices:
|
First thing I did was searching some kind of quote data that available for free. I stumbled across several choices:
|
||||||
|
|
||||||
- Use available quote API, eg: [Zen Quotes](https://zenquotes.io)
|
- Use available quote API, eg: [Zen Quotes](https://zenquotes.io)
|
||||||
|
|
@ -25,15 +27,17 @@ After many consideration, I'd like to use this [Quotes 500k](https://github.com/
|
||||||
First problem solved, since I have the quote dataset I was thinking whether I embed this dataset as SQLite database into the app or do something else. But then I remember that I still need to get the background image for the app and it will make no sense to embed the background images into the app as well. So I decided to create a simple REST API for Kuwot.
|
First problem solved, since I have the quote dataset I was thinking whether I embed this dataset as SQLite database into the app or do something else. But then I remember that I still need to get the background image for the app and it will make no sense to embed the background images into the app as well. So I decided to create a simple REST API for Kuwot.
|
||||||
|
|
||||||
# Building the API
|
# Building the API
|
||||||
|
|
||||||
Now I need a simple, easy to make REST API. I was thinking to use Python's [FastAPI](https://fastapi.tiangolo.com/) but after some onboarding tutorial, I just don't like it. Then I found [dart_frog](https://dartfrog.vgv.dev/)! It's a minimalist backend framework written in Dart. There are no reason to not use this since it use same language as my Flutter app, that was I thought.
|
Now I need a simple, easy to make REST API. I was thinking to use Python's [FastAPI](https://fastapi.tiangolo.com/) but after some onboarding tutorial, I just don't like it. Then I found [dart_frog](https://dartfrog.vgv.dev/)! It's a minimalist backend framework written in Dart. There are no reason to not use this since it use same language as my Flutter app, that was I thought.
|
||||||
|
|
||||||
Building the API is straight-forward, [dart_frog](https://dartfrog.vgv.dev/) has everything I need to build the API.
|
Building the API is straight-forward, [dart_frog](https://dartfrog.vgv.dev/) has everything I need to build the API.
|
||||||
|
|
||||||
# Reshaping dataset
|
# Reshaping dataset
|
||||||
|
|
||||||
The data I got from [Quotes 500k](https://github.com/ShivaliGoel/Quotes-500K) is looking like this:
|
The data I got from [Quotes 500k](https://github.com/ShivaliGoel/Quotes-500K) is looking like this:
|
||||||
|
|
||||||
| Quote | Author | Tags |
|
| Quote | Author | Tags |
|
||||||
| ----- | ------ | ---- |
|
| ---------------------------------------------------------------- | -------------- | ----------------------------------- |
|
||||||
| A friend is someone who knows all about you and still loves you. | Elbert Hubbard | friend, friendship, knowledge, love |
|
| A friend is someone who knows all about you and still loves you. | Elbert Hubbard | friend, friendship, knowledge, love |
|
||||||
|
|
||||||
Now, I didn't want the tags and I don't want a long quote. So I make a Python script to filter those quotes into a new dataset I need.
|
Now, I didn't want the tags and I don't want a long quote. So I make a Python script to filter those quotes into a new dataset I need.
|
||||||
|
|
@ -43,4 +47,5 @@ Now, I didn't want the tags and I don't want a long quote. So I make a Python sc
|
||||||
```
|
```
|
||||||
|
|
||||||
# UI Design
|
# UI Design
|
||||||
|
|
||||||
I want to make it as simple as possible while focusing on the functionality.
|
I want to make it as simple as possible while focusing on the functionality.
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 'Remap Infinix Air Pro+ Copilot Key in Linux'
|
title: "Remap Infinix Air Pro+ Copilot Key in Linux"
|
||||||
description: 'Re-using Copilot key for something else more useful'
|
description: "Re-using Copilot key for something else more useful"
|
||||||
date: 2025-03-01T22:41:32+07:00
|
date: 2025-03-01T22:41:32+07:00
|
||||||
draft: false
|
draft: false
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -15,10 +15,12 @@ tags:
|
||||||
Let me rephrase that! Even when I was using Windows, I never using this Copilot shortcut key in my keyboard 😬. Fortunately, using Linux I can remap this key for something else ~~more useful~~.
|
Let me rephrase that! Even when I was using Windows, I never using this Copilot shortcut key in my keyboard 😬. Fortunately, using Linux I can remap this key for something else ~~more useful~~.
|
||||||
|
|
||||||
# Requirements
|
# Requirements
|
||||||
|
|
||||||
- A laptop (I'm using Infinix Air Pro+) with a working keyboard.
|
- A laptop (I'm using Infinix Air Pro+) with a working keyboard.
|
||||||
- [keyd](https://github.com/rvaiya/keyd)
|
- [keyd](https://github.com/rvaiya/keyd)
|
||||||
|
|
||||||
# Finding what this Copilot key do
|
# Finding what this Copilot key do
|
||||||
|
|
||||||
- Open your favorite terminal and execute `sudo keyd monitor`. This command will print what events are triggered when a particular key is pressed.
|
- Open your favorite terminal and execute `sudo keyd monitor`. This command will print what events are triggered when a particular key is pressed.
|
||||||
- Press the Copilot key and read the output. In my laptop, it print out this:
|
- Press the Copilot key and read the output. In my laptop, it print out this:
|
||||||
|
|
||||||
|
|
@ -31,6 +33,7 @@ AT Translated Set 2 keyboard 0001:0001:70533846 f23 down
|
||||||
- Now I know that my copilot key triggers `leftmeta`, `leftshift`, and `f23`. It's seem legit combination of modifier keys and a function key. But unfortunately when I tried to use it in my desktop environment (I use KDE) to bind a shortcut, it only detect the modifier `meta` and `shift`.
|
- Now I know that my copilot key triggers `leftmeta`, `leftshift`, and `f23`. It's seem legit combination of modifier keys and a function key. But unfortunately when I tried to use it in my desktop environment (I use KDE) to bind a shortcut, it only detect the modifier `meta` and `shift`.
|
||||||
|
|
||||||
# `keyd` for the rescue
|
# `keyd` for the rescue
|
||||||
|
|
||||||
Edit `/etc/keyd/default.conf` file and I added these lines:
|
Edit `/etc/keyd/default.conf` file and I added these lines:
|
||||||
|
|
||||||
```plaintext
|
```plaintext
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
title: 'Welcome'
|
title: "Welcome"
|
||||||
description: 'Initial commit!'
|
description: "Initial commit!"
|
||||||
date: 2024-03-18T14:16:19+07:00
|
date: 2024-03-18T14:16:19+07:00
|
||||||
draft: false
|
draft: false
|
||||||
tags:
|
tags:
|
||||||
|
|
@ -15,7 +15,6 @@ Long story short, I decide to take an Information Technology major in my local u
|
||||||
|
|
||||||
I never really write anything in my 7 years journey and I feel kinda regretted it. So I decide to create this blog, where I can write anything I found interesting. Maybe it's a coding-related thing, technology heads up, or maybe just my random thought. Feel free read and share!
|
I never really write anything in my 7 years journey and I feel kinda regretted it. So I decide to create this blog, where I can write anything I found interesting. Maybe it's a coding-related thing, technology heads up, or maybe just my random thought. Feel free read and share!
|
||||||
|
|
||||||
|
|
||||||
# Bonus
|
# Bonus
|
||||||
|
|
||||||
This is a cat from my work, maybe someday she can be a good software tester to find ~~mice~~ bugs from my code.
|
This is a cat from my work, maybe someday she can be a good software tester to find ~~mice~~ bugs from my code.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import "../styles/global.css";
|
import "@/styles/global.css";
|
||||||
import Footer from "../components/Footer.astro";
|
import Footer from "@/components/Footer.astro";
|
||||||
import Header from "../components/Header.astro";
|
import Header from "@/components/Header.astro";
|
||||||
|
|
||||||
interface Props {
|
interface Props {
|
||||||
title: string;
|
title: string;
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import Welcome from "../components/Welcome.astro";
|
import Welcome from "@/components/Welcome.astro";
|
||||||
import Layout from "../layouts/Layout.astro";
|
import Layout from "@/layouts/Layout.astro";
|
||||||
|
|
||||||
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
|
// Welcome to Astro! Wondering what to do next? Check out the Astro documentation at https://docs.astro.build
|
||||||
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
|
// Don't want to use any of this? Delete everything in this file, the `assets`, `components`, and `layouts` directories, and start fresh.
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import { getCollection, render, type CollectionEntry, type RenderResult } from "astro:content";
|
import { getCollection, render, type CollectionEntry, type RenderResult } from "astro:content";
|
||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "@/layouts/Layout.astro";
|
||||||
import BlogPost from "../../components/BlogPost.astro";
|
import BlogPost from "@/components/BlogPost.astro";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = await getCollection("blog");
|
const posts = await getCollection("blog");
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "@/layouts/Layout.astro";
|
||||||
import BlogPostCard from "../../components/BlogPostCard.astro";
|
import BlogPostCard from "@/components/BlogPostCard.astro";
|
||||||
|
|
||||||
const posts = await getCollection("blog");
|
const posts = await getCollection("blog");
|
||||||
posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
posts.sort((a, b) => b.data.date.getTime() - a.data.date.getTime());
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
import { getCollection, type CollectionEntry } from "astro:content";
|
import { getCollection, type CollectionEntry } from "astro:content";
|
||||||
import BlogPostCard from "../../components/BlogPostCard.astro";
|
import BlogPostCard from "@/components/BlogPostCard.astro";
|
||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "@/layouts/Layout.astro";
|
||||||
|
|
||||||
export async function getStaticPaths() {
|
export async function getStaticPaths() {
|
||||||
const posts = await getCollection("blog");
|
const posts = await getCollection("blog");
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
---
|
---
|
||||||
import { getCollection } from "astro:content";
|
import { getCollection } from "astro:content";
|
||||||
import Layout from "../../layouts/Layout.astro";
|
import Layout from "@/layouts/Layout.astro";
|
||||||
|
|
||||||
const posts = await getCollection("blog");
|
const posts = await getCollection("blog");
|
||||||
const tagsMap = new Map<string, number>();
|
const tagsMap = new Map<string, number>();
|
||||||
|
|
|
||||||
|
|
@ -1,11 +1,11 @@
|
||||||
@import url('https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap');
|
@import url("https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,100..900;1,100..900&display=swap");
|
||||||
@import url('https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap');
|
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono&display=swap");
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@plugin '@tailwindcss/typography';
|
@plugin '@tailwindcss/typography';
|
||||||
|
|
||||||
@theme {
|
@theme {
|
||||||
--font-sans: 'Noto Sans', sans-serif;
|
--font-sans: "Noto Sans", sans-serif;
|
||||||
--font-mono: 'JetBrains Mono', monospace;
|
--font-mono: "JetBrains Mono", monospace;
|
||||||
}
|
}
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
|
|
|
||||||
|
|
@ -3,6 +3,10 @@
|
||||||
"include": [".astro/types.d.ts", "**/*"],
|
"include": [".astro/types.d.ts", "**/*"],
|
||||||
"exclude": ["dist"],
|
"exclude": ["dist"],
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"allowJs": true
|
"allowJs": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"@/*": ["src/*"]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue