diff --git a/config/__init__.py b/config/__init__.py
deleted file mode 100644
index f94e240..0000000
--- a/config/__init__.py
+++ /dev/null
@@ -1,6 +0,0 @@
-from .data import *
-
-# Import only specific names actually defined in data.py
-# This prevents circular imports by not importing everything
-
-from .data import APP_NAME, APP_NAME_CAP, CACHE_DIR, CONFIG_FILE, STATE_DIR
diff --git a/config/assets/accounts.json b/config/assets/accounts.json
deleted file mode 100644
index 9e26dfe..0000000
--- a/config/assets/accounts.json
+++ /dev/null
@@ -1 +0,0 @@
-{}
\ No newline at end of file
diff --git a/config/assets/config.json b/config/assets/config.json
deleted file mode 100644
index 2cdda50..0000000
--- a/config/assets/config.json
+++ /dev/null
@@ -1,58 +0,0 @@
-{
- "prefix_restart": "SHIFT ALT",
- "suffix_restart": "T",
- "prefix_bluetooth": "SHIFT ALT",
- "suffix_bluetooth": "B",
- "prefix_emoji": "SUPER",
- "suffix_emoji": "E",
- "prefix_cliphist": "SUPER",
- "suffix_cliphist": "V",
- "prefix_kanban": "SUPER",
- "suffix_kanban": "T",
- "prefix_toolbox": "SUPER",
- "suffix_toolbox": "S",
- "prefix_walls": "SUPER",
- "suffix_walls": "W",
- "prefix_launcher": "SUPER",
- "suffix_launcher": "D",
- "prefix_power": "SUPER",
- "suffix_power": "X",
- "prefix_tmux": "SUPER",
- "suffix_tmux": "Z",
- "prefix_notifications": "SUPER",
- "suffix_notifications": "N",
- "prefix_window_switcher": "ALT",
- "suffix_window_switcher": "W",
- "prefix_restart_inspector": "SUPER SHIFT",
- "suffix_restart_inspector": "T",
- "vertical": false,
- "vertical_right_align": false,
- "bottom_bar": true,
- "centered_bar": true,
- "terminal_command": "kitty -e",
- "wallpapers_dir": "/home/sankalp/Pictures/wallpapers",
- "dock_enabled": true,
- "dock_always_occluded": false,
- "dock_icon_size": 28,
- "osd_enabled": true,
- "bar_button_app_visible": true,
- "bar_tray_visible": true,
- "bar_workspaces_visible": true,
- "bar_metrics_visible": true,
- "bar_language_visible": true,
- "bar_date_time_visible": true,
- "bar_updates_visible": true,
- "bar_indicators_visible": true,
- "corners_visible": true,
- "bar_metrics_disks": [
- "/"
- ],
- "metrics_visible": {
- "cpu": true,
- "ram": true,
- "disk": true,
- "swap": true,
- "gpu": false
- },
- "enabled_osd": true
-}
\ No newline at end of file
diff --git a/config/assets/emoji.json b/config/assets/emoji.json
deleted file mode 100644
index fa174ff..0000000
--- a/config/assets/emoji.json
+++ /dev/null
@@ -1,15509 +0,0 @@
-{
- "๐": {
- "name": "grinning face",
- "slug": "grinning_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "grinning face with big eyes",
- "slug": "grinning_face_with_big_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "grinning face with smiling eyes",
- "slug": "grinning_face_with_smiling_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "beaming face with smiling eyes",
- "slug": "beaming_face_with_smiling_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "grinning squinting face",
- "slug": "grinning_squinting_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "grinning face with sweat",
- "slug": "grinning_face_with_sweat",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐คฃ": {
- "name": "rolling on the floor laughing",
- "slug": "rolling_on_the_floor_laughing",
- "group": "Smileys & Emotion",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "face with tears of joy",
- "slug": "face_with_tears_of_joy",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "slightly smiling face",
- "slug": "slightly_smiling_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "upside-down face",
- "slug": "upside_down_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ซ ": {
- "name": "melting face",
- "slug": "melting_face",
- "group": "Smileys & Emotion",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "winking face",
- "slug": "winking_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "smiling face with smiling eyes",
- "slug": "smiling_face_with_smiling_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "smiling face with halo",
- "slug": "smiling_face_with_halo",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅฐ": {
- "name": "smiling face with hearts",
- "slug": "smiling_face_with_hearts",
- "group": "Smileys & Emotion",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "smiling face with heart-eyes",
- "slug": "smiling_face_with_heart_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐คฉ": {
- "name": "star-struck",
- "slug": "star_struck",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "face blowing a kiss",
- "slug": "face_blowing_a_kiss",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "kissing face",
- "slug": "kissing_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โบ๏ธ": {
- "name": "smiling face",
- "slug": "smiling_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "kissing face with closed eyes",
- "slug": "kissing_face_with_closed_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "kissing face with smiling eyes",
- "slug": "kissing_face_with_smiling_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅฒ": {
- "name": "smiling face with tear",
- "slug": "smiling_face_with_tear",
- "group": "Smileys & Emotion",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "face savoring food",
- "slug": "face_savoring_food",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "face with tongue",
- "slug": "face_with_tongue",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "winking face with tongue",
- "slug": "winking_face_with_tongue",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐คช": {
- "name": "zany face",
- "slug": "zany_face",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "squinting face with tongue",
- "slug": "squinting_face_with_tongue",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "money-mouth face",
- "slug": "money_mouth_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "smiling face with open hands",
- "slug": "smiling_face_with_open_hands",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐คญ": {
- "name": "face with hand over mouth",
- "slug": "face_with_hand_over_mouth",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ซข": {
- "name": "face with open eyes and hand over mouth",
- "slug": "face_with_open_eyes_and_hand_over_mouth",
- "group": "Smileys & Emotion",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ซฃ": {
- "name": "face with peeking eye",
- "slug": "face_with_peeking_eye",
- "group": "Smileys & Emotion",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐คซ": {
- "name": "shushing face",
- "slug": "shushing_face",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "thinking face",
- "slug": "thinking_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ซก": {
- "name": "saluting face",
- "slug": "saluting_face",
- "group": "Smileys & Emotion",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "zipper-mouth face",
- "slug": "zipper_mouth_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐คจ": {
- "name": "face with raised eyebrow",
- "slug": "face_with_raised_eyebrow",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "neutral face",
- "slug": "neutral_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "expressionless face",
- "slug": "expressionless_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "face without mouth",
- "slug": "face_without_mouth",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ซฅ": {
- "name": "dotted line face",
- "slug": "dotted_line_face",
- "group": "Smileys & Emotion",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ถโ๐ซ๏ธ": {
- "name": "face in clouds",
- "slug": "face_in_clouds",
- "group": "Smileys & Emotion",
- "emoji_version": "13.1",
- "unicode_version": "13.1",
- "skin_tone_support": false
- },
- "๐": {
- "name": "smirking face",
- "slug": "smirking_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "unamused face",
- "slug": "unamused_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "face with rolling eyes",
- "slug": "face_with_rolling_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "grimacing face",
- "slug": "grimacing_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฎโ๐จ": {
- "name": "face exhaling",
- "slug": "face_exhaling",
- "group": "Smileys & Emotion",
- "emoji_version": "13.1",
- "unicode_version": "13.1",
- "skin_tone_support": false
- },
- "๐คฅ": {
- "name": "lying face",
- "slug": "lying_face",
- "group": "Smileys & Emotion",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ซจ": {
- "name": "shaking face",
- "slug": "shaking_face",
- "group": "Smileys & Emotion",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐โโ๏ธ": {
- "name": "head shaking horizontally",
- "slug": "head_shaking_horizontally",
- "group": "Smileys & Emotion",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐โโ๏ธ": {
- "name": "head shaking vertically",
- "slug": "head_shaking_vertically",
- "group": "Smileys & Emotion",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐": {
- "name": "relieved face",
- "slug": "relieved_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pensive face",
- "slug": "pensive_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "sleepy face",
- "slug": "sleepy_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐คค": {
- "name": "drooling face",
- "slug": "drooling_face",
- "group": "Smileys & Emotion",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "sleeping face",
- "slug": "sleeping_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "face with medical mask",
- "slug": "face_with_medical_mask",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "face with thermometer",
- "slug": "face_with_thermometer",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "face with head-bandage",
- "slug": "face_with_head_bandage",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐คข": {
- "name": "nauseated face",
- "slug": "nauseated_face",
- "group": "Smileys & Emotion",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐คฎ": {
- "name": "face vomiting",
- "slug": "face_vomiting",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐คง": {
- "name": "sneezing face",
- "slug": "sneezing_face",
- "group": "Smileys & Emotion",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅต": {
- "name": "hot face",
- "slug": "hot_face",
- "group": "Smileys & Emotion",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅถ": {
- "name": "cold face",
- "slug": "cold_face",
- "group": "Smileys & Emotion",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅด": {
- "name": "woozy face",
- "slug": "woozy_face",
- "group": "Smileys & Emotion",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "face with crossed-out eyes",
- "slug": "face_with_crossed_out_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ตโ๐ซ": {
- "name": "face with spiral eyes",
- "slug": "face_with_spiral_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "13.1",
- "unicode_version": "13.1",
- "skin_tone_support": false
- },
- "๐คฏ": {
- "name": "exploding head",
- "slug": "exploding_head",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ค ": {
- "name": "cowboy hat face",
- "slug": "cowboy_hat_face",
- "group": "Smileys & Emotion",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅณ": {
- "name": "partying face",
- "slug": "partying_face",
- "group": "Smileys & Emotion",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅธ": {
- "name": "disguised face",
- "slug": "disguised_face",
- "group": "Smileys & Emotion",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "smiling face with sunglasses",
- "slug": "smiling_face_with_sunglasses",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "nerd face",
- "slug": "nerd_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "face with monocle",
- "slug": "face_with_monocle",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "confused face",
- "slug": "confused_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ซค": {
- "name": "face with diagonal mouth",
- "slug": "face_with_diagonal_mouth",
- "group": "Smileys & Emotion",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "worried face",
- "slug": "worried_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "slightly frowning face",
- "slug": "slightly_frowning_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โน๏ธ": {
- "name": "frowning face",
- "slug": "frowning_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "face with open mouth",
- "slug": "face_with_open_mouth",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "hushed face",
- "slug": "hushed_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "astonished face",
- "slug": "astonished_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "flushed face",
- "slug": "flushed_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅบ": {
- "name": "pleading face",
- "slug": "pleading_face",
- "group": "Smileys & Emotion",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅน": {
- "name": "face holding back tears",
- "slug": "face_holding_back_tears",
- "group": "Smileys & Emotion",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "frowning face with open mouth",
- "slug": "frowning_face_with_open_mouth",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "anguished face",
- "slug": "anguished_face",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "fearful face",
- "slug": "fearful_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "anxious face with sweat",
- "slug": "anxious_face_with_sweat",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "sad but relieved face",
- "slug": "sad_but_relieved_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "crying face",
- "slug": "crying_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "loudly crying face",
- "slug": "loudly_crying_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "face screaming in fear",
- "slug": "face_screaming_in_fear",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "confounded face",
- "slug": "confounded_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "persevering face",
- "slug": "persevering_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "disappointed face",
- "slug": "disappointed_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "downcast face with sweat",
- "slug": "downcast_face_with_sweat",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "weary face",
- "slug": "weary_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "tired face",
- "slug": "tired_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅฑ": {
- "name": "yawning face",
- "slug": "yawning_face",
- "group": "Smileys & Emotion",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "face with steam from nose",
- "slug": "face_with_steam_from_nose",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "enraged face",
- "slug": "enraged_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "angry face",
- "slug": "angry_face",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐คฌ": {
- "name": "face with symbols on mouth",
- "slug": "face_with_symbols_on_mouth",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "smiling face with horns",
- "slug": "smiling_face_with_horns",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "angry face with horns",
- "slug": "angry_face_with_horns",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "skull",
- "slug": "skull",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ ๏ธ": {
- "name": "skull and crossbones",
- "slug": "skull_and_crossbones",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "pile of poo",
- "slug": "pile_of_poo",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐คก": {
- "name": "clown face",
- "slug": "clown_face",
- "group": "Smileys & Emotion",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "ogre",
- "slug": "ogre",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "goblin",
- "slug": "goblin",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "ghost",
- "slug": "ghost",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "alien",
- "slug": "alien",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "alien monster",
- "slug": "alien_monster",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "robot",
- "slug": "robot",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "grinning cat",
- "slug": "grinning_cat",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "grinning cat with smiling eyes",
- "slug": "grinning_cat_with_smiling_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "cat with tears of joy",
- "slug": "cat_with_tears_of_joy",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "smiling cat with heart-eyes",
- "slug": "smiling_cat_with_heart_eyes",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "cat with wry smile",
- "slug": "cat_with_wry_smile",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "kissing cat",
- "slug": "kissing_cat",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "weary cat",
- "slug": "weary_cat",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "crying cat",
- "slug": "crying_cat",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "pouting cat",
- "slug": "pouting_cat",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "see-no-evil monkey",
- "slug": "see_no_evil_monkey",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "hear-no-evil monkey",
- "slug": "hear_no_evil_monkey",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "speak-no-evil monkey",
- "slug": "speak_no_evil_monkey",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "love letter",
- "slug": "love_letter",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "heart with arrow",
- "slug": "heart_with_arrow",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "heart with ribbon",
- "slug": "heart_with_ribbon",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "sparkling heart",
- "slug": "sparkling_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "growing heart",
- "slug": "growing_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "beating heart",
- "slug": "beating_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "revolving hearts",
- "slug": "revolving_hearts",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "two hearts",
- "slug": "two_hearts",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "heart decoration",
- "slug": "heart_decoration",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฃ๏ธ": {
- "name": "heart exclamation",
- "slug": "heart_exclamation",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "broken heart",
- "slug": "broken_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โค๏ธโ๐ฅ": {
- "name": "heart on fire",
- "slug": "heart_on_fire",
- "group": "Smileys & Emotion",
- "emoji_version": "13.1",
- "unicode_version": "13.1",
- "skin_tone_support": false
- },
- "โค๏ธโ๐ฉน": {
- "name": "mending heart",
- "slug": "mending_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "13.1",
- "unicode_version": "13.1",
- "skin_tone_support": false
- },
- "โค๏ธ": {
- "name": "red heart",
- "slug": "red_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉท": {
- "name": "pink heart",
- "slug": "pink_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐งก": {
- "name": "orange heart",
- "slug": "orange_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "yellow heart",
- "slug": "yellow_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "green heart",
- "slug": "green_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "blue heart",
- "slug": "blue_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉต": {
- "name": "light blue heart",
- "slug": "light_blue_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "purple heart",
- "slug": "purple_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "brown heart",
- "slug": "brown_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "black heart",
- "slug": "black_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฉถ": {
- "name": "grey heart",
- "slug": "grey_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "white heart",
- "slug": "white_heart",
- "group": "Smileys & Emotion",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "kiss mark",
- "slug": "kiss_mark",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "hundred points",
- "slug": "hundred_points",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "anger symbol",
- "slug": "anger_symbol",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "collision",
- "slug": "collision",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "dizzy",
- "slug": "dizzy",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "sweat droplets",
- "slug": "sweat_droplets",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "dashing away",
- "slug": "dashing_away",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ณ๏ธ": {
- "name": "hole",
- "slug": "hole",
- "group": "Smileys & Emotion",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "speech balloon",
- "slug": "speech_balloon",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธโ๐จ๏ธ": {
- "name": "eye in speech bubble",
- "slug": "eye_in_speech_bubble",
- "group": "Smileys & Emotion",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๏ธ": {
- "name": "left speech bubble",
- "slug": "left_speech_bubble",
- "group": "Smileys & Emotion",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฏ๏ธ": {
- "name": "right anger bubble",
- "slug": "right_anger_bubble",
- "group": "Smileys & Emotion",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "thought balloon",
- "slug": "thought_balloon",
- "group": "Smileys & Emotion",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "ZZZ",
- "slug": "zzz",
- "group": "Smileys & Emotion",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "waving hand",
- "slug": "waving_hand",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ค": {
- "name": "raised back of hand",
- "slug": "raised_back_of_hand",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐๏ธ": {
- "name": "hand with fingers splayed",
- "slug": "hand_with_fingers_splayed",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "โ": {
- "name": "raised hand",
- "slug": "raised_hand",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "vulcan salute",
- "slug": "vulcan_salute",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ซฑ": {
- "name": "rightwards hand",
- "slug": "rightwards_hand",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐ซฒ": {
- "name": "leftwards hand",
- "slug": "leftwards_hand",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐ซณ": {
- "name": "palm down hand",
- "slug": "palm_down_hand",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐ซด": {
- "name": "palm up hand",
- "slug": "palm_up_hand",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐ซท": {
- "name": "leftwards pushing hand",
- "slug": "leftwards_pushing_hand",
- "group": "People & Body",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.0"
- },
- "๐ซธ": {
- "name": "rightwards pushing hand",
- "slug": "rightwards_pushing_hand",
- "group": "People & Body",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.0"
- },
- "๐": {
- "name": "OK hand",
- "slug": "ok_hand",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ค": {
- "name": "pinched fingers",
- "slug": "pinched_fingers",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐ค": {
- "name": "pinching hand",
- "slug": "pinching_hand",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "โ๏ธ": {
- "name": "victory hand",
- "slug": "victory_hand",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ค": {
- "name": "crossed fingers",
- "slug": "crossed_fingers",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐ซฐ": {
- "name": "hand with index finger and thumb crossed",
- "slug": "hand_with_index_finger_and_thumb_crossed",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐ค": {
- "name": "love-you gesture",
- "slug": "love_you_gesture",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ค": {
- "name": "sign of the horns",
- "slug": "sign_of_the_horns",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ค": {
- "name": "call me hand",
- "slug": "call_me_hand",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐": {
- "name": "backhand index pointing left",
- "slug": "backhand_index_pointing_left",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "backhand index pointing right",
- "slug": "backhand_index_pointing_right",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "backhand index pointing up",
- "slug": "backhand_index_pointing_up",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "middle finger",
- "slug": "middle_finger",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "backhand index pointing down",
- "slug": "backhand_index_pointing_down",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "โ๏ธ": {
- "name": "index pointing up",
- "slug": "index_pointing_up",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ซต": {
- "name": "index pointing at the viewer",
- "slug": "index_pointing_at_the_viewer",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐": {
- "name": "thumbs up",
- "slug": "thumbs_up",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "thumbs down",
- "slug": "thumbs_down",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "โ": {
- "name": "raised fist",
- "slug": "raised_fist",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "oncoming fist",
- "slug": "oncoming_fist",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ค": {
- "name": "left-facing fist",
- "slug": "left_facing_fist",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐ค": {
- "name": "right-facing fist",
- "slug": "right_facing_fist",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐": {
- "name": "clapping hands",
- "slug": "clapping_hands",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "raising hands",
- "slug": "raising_hands",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ซถ": {
- "name": "heart hands",
- "slug": "heart_hands",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐": {
- "name": "open hands",
- "slug": "open_hands",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐คฒ": {
- "name": "palms up together",
- "slug": "palms_up_together",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ค": {
- "name": "handshake",
- "slug": "handshake",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐": {
- "name": "folded hands",
- "slug": "folded_hands",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "โ๏ธ": {
- "name": "writing hand",
- "slug": "writing_hand",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐
": {
- "name": "nail polish",
- "slug": "nail_polish",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐คณ": {
- "name": "selfie",
- "slug": "selfie",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐ช": {
- "name": "flexed biceps",
- "slug": "flexed_biceps",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ฆพ": {
- "name": "mechanical arm",
- "slug": "mechanical_arm",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆฟ": {
- "name": "mechanical leg",
- "slug": "mechanical_leg",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆต": {
- "name": "leg",
- "slug": "leg",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐ฆถ": {
- "name": "foot",
- "slug": "foot",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐": {
- "name": "ear",
- "slug": "ear",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ฆป": {
- "name": "ear with hearing aid",
- "slug": "ear_with_hearing_aid",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐": {
- "name": "nose",
- "slug": "nose",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ง ": {
- "name": "brain",
- "slug": "brain",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "anatomical heart",
- "slug": "anatomical_heart",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "lungs",
- "slug": "lungs",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฆท": {
- "name": "tooth",
- "slug": "tooth",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฆด": {
- "name": "bone",
- "slug": "bone",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "eyes",
- "slug": "eyes",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "eye",
- "slug": "eye",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "tongue",
- "slug": "tongue",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "mouth",
- "slug": "mouth",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซฆ": {
- "name": "biting lip",
- "slug": "biting_lip",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "baby",
- "slug": "baby",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ง": {
- "name": "child",
- "slug": "child",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ฆ": {
- "name": "boy",
- "slug": "boy",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ง": {
- "name": "girl",
- "slug": "girl",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ง": {
- "name": "person",
- "slug": "person",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ฑ": {
- "name": "person blond hair",
- "slug": "person_blond_hair",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐จ": {
- "name": "man",
- "slug": "man",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ง": {
- "name": "person beard",
- "slug": "person_beard",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "man beard",
- "slug": "man_beard",
- "group": "People & Body",
- "emoji_version": "13.1",
- "unicode_version": "13.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐งโโ๏ธ": {
- "name": "woman beard",
- "slug": "woman_beard",
- "group": "People & Body",
- "emoji_version": "13.1",
- "unicode_version": "13.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐จโ๐ฆฐ": {
- "name": "man red hair",
- "slug": "man_red_hair",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐จโ๐ฆฑ": {
- "name": "man curly hair",
- "slug": "man_curly_hair",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐จโ๐ฆณ": {
- "name": "man white hair",
- "slug": "man_white_hair",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐จโ๐ฆฒ": {
- "name": "man bald",
- "slug": "man_bald",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐ฉ": {
- "name": "woman",
- "slug": "woman",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ฉโ๐ฆฐ": {
- "name": "woman red hair",
- "slug": "woman_red_hair",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐งโ๐ฆฐ": {
- "name": "person red hair",
- "slug": "person_red_hair",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐ฉโ๐ฆฑ": {
- "name": "woman curly hair",
- "slug": "woman_curly_hair",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐งโ๐ฆฑ": {
- "name": "person curly hair",
- "slug": "person_curly_hair",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐ฉโ๐ฆณ": {
- "name": "woman white hair",
- "slug": "woman_white_hair",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐งโ๐ฆณ": {
- "name": "person white hair",
- "slug": "person_white_hair",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐ฉโ๐ฆฒ": {
- "name": "woman bald",
- "slug": "woman_bald",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐งโ๐ฆฒ": {
- "name": "person bald",
- "slug": "person_bald",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐ฑโโ๏ธ": {
- "name": "woman blond hair",
- "slug": "woman_blond_hair",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฑโโ๏ธ": {
- "name": "man blond hair",
- "slug": "man_blond_hair",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ง": {
- "name": "older person",
- "slug": "older_person",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ด": {
- "name": "old man",
- "slug": "old_man",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ต": {
- "name": "old woman",
- "slug": "old_woman",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "person frowning",
- "slug": "person_frowning",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man frowning",
- "slug": "man_frowning",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman frowning",
- "slug": "woman_frowning",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐": {
- "name": "person pouting",
- "slug": "person_pouting",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man pouting",
- "slug": "man_pouting",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman pouting",
- "slug": "woman_pouting",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐
": {
- "name": "person gesturing NO",
- "slug": "person_gesturing_no",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐
โโ๏ธ": {
- "name": "man gesturing NO",
- "slug": "man_gesturing_no",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐
โโ๏ธ": {
- "name": "woman gesturing NO",
- "slug": "woman_gesturing_no",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐": {
- "name": "person gesturing OK",
- "slug": "person_gesturing_ok",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man gesturing OK",
- "slug": "man_gesturing_ok",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman gesturing OK",
- "slug": "woman_gesturing_ok",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐": {
- "name": "person tipping hand",
- "slug": "person_tipping_hand",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man tipping hand",
- "slug": "man_tipping_hand",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman tipping hand",
- "slug": "woman_tipping_hand",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐": {
- "name": "person raising hand",
- "slug": "person_raising_hand",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man raising hand",
- "slug": "man_raising_hand",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman raising hand",
- "slug": "woman_raising_hand",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ง": {
- "name": "deaf person",
- "slug": "deaf_person",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐งโโ๏ธ": {
- "name": "deaf man",
- "slug": "deaf_man",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐งโโ๏ธ": {
- "name": "deaf woman",
- "slug": "deaf_woman",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐": {
- "name": "person bowing",
- "slug": "person_bowing",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man bowing",
- "slug": "man_bowing",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman bowing",
- "slug": "woman_bowing",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คฆ": {
- "name": "person facepalming",
- "slug": "person_facepalming",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐คฆโโ๏ธ": {
- "name": "man facepalming",
- "slug": "man_facepalming",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คฆโโ๏ธ": {
- "name": "woman facepalming",
- "slug": "woman_facepalming",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คท": {
- "name": "person shrugging",
- "slug": "person_shrugging",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐คทโโ๏ธ": {
- "name": "man shrugging",
- "slug": "man_shrugging",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คทโโ๏ธ": {
- "name": "woman shrugging",
- "slug": "woman_shrugging",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโโ๏ธ": {
- "name": "health worker",
- "slug": "health_worker",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโโ๏ธ": {
- "name": "man health worker",
- "slug": "man_health_worker",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโโ๏ธ": {
- "name": "woman health worker",
- "slug": "woman_health_worker",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐": {
- "name": "student",
- "slug": "student",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐": {
- "name": "man student",
- "slug": "man_student",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐": {
- "name": "woman student",
- "slug": "woman_student",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐ซ": {
- "name": "teacher",
- "slug": "teacher",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐ซ": {
- "name": "man teacher",
- "slug": "man_teacher",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐ซ": {
- "name": "woman teacher",
- "slug": "woman_teacher",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโโ๏ธ": {
- "name": "judge",
- "slug": "judge",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโโ๏ธ": {
- "name": "man judge",
- "slug": "man_judge",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโโ๏ธ": {
- "name": "woman judge",
- "slug": "woman_judge",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐พ": {
- "name": "farmer",
- "slug": "farmer",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐พ": {
- "name": "man farmer",
- "slug": "man_farmer",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐พ": {
- "name": "woman farmer",
- "slug": "woman_farmer",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐ณ": {
- "name": "cook",
- "slug": "cook",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐ณ": {
- "name": "man cook",
- "slug": "man_cook",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐ณ": {
- "name": "woman cook",
- "slug": "woman_cook",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐ง": {
- "name": "mechanic",
- "slug": "mechanic",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐ง": {
- "name": "man mechanic",
- "slug": "man_mechanic",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐ง": {
- "name": "woman mechanic",
- "slug": "woman_mechanic",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐ญ": {
- "name": "factory worker",
- "slug": "factory_worker",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐ญ": {
- "name": "man factory worker",
- "slug": "man_factory_worker",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐ญ": {
- "name": "woman factory worker",
- "slug": "woman_factory_worker",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐ผ": {
- "name": "office worker",
- "slug": "office_worker",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐ผ": {
- "name": "man office worker",
- "slug": "man_office_worker",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐ผ": {
- "name": "woman office worker",
- "slug": "woman_office_worker",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐ฌ": {
- "name": "scientist",
- "slug": "scientist",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐ฌ": {
- "name": "man scientist",
- "slug": "man_scientist",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐ฌ": {
- "name": "woman scientist",
- "slug": "woman_scientist",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐ป": {
- "name": "technologist",
- "slug": "technologist",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐ป": {
- "name": "man technologist",
- "slug": "man_technologist",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐ป": {
- "name": "woman technologist",
- "slug": "woman_technologist",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐ค": {
- "name": "singer",
- "slug": "singer",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐ค": {
- "name": "man singer",
- "slug": "man_singer",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐ค": {
- "name": "woman singer",
- "slug": "woman_singer",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐จ": {
- "name": "artist",
- "slug": "artist",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐จ": {
- "name": "man artist",
- "slug": "man_artist",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐จ": {
- "name": "woman artist",
- "slug": "woman_artist",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโโ๏ธ": {
- "name": "pilot",
- "slug": "pilot",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโโ๏ธ": {
- "name": "man pilot",
- "slug": "man_pilot",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโโ๏ธ": {
- "name": "woman pilot",
- "slug": "woman_pilot",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐": {
- "name": "astronaut",
- "slug": "astronaut",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐": {
- "name": "man astronaut",
- "slug": "man_astronaut",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐": {
- "name": "woman astronaut",
- "slug": "woman_astronaut",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐": {
- "name": "firefighter",
- "slug": "firefighter",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐จโ๐": {
- "name": "man firefighter",
- "slug": "man_firefighter",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฉโ๐": {
- "name": "woman firefighter",
- "slug": "woman_firefighter",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฎ": {
- "name": "police officer",
- "slug": "police_officer",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ฎโโ๏ธ": {
- "name": "man police officer",
- "slug": "man_police_officer",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฎโโ๏ธ": {
- "name": "woman police officer",
- "slug": "woman_police_officer",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ต๏ธ": {
- "name": "detective",
- "slug": "detective",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "2.0"
- },
- "๐ต๏ธโโ๏ธ": {
- "name": "man detective",
- "slug": "man_detective",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ต๏ธโโ๏ธ": {
- "name": "woman detective",
- "slug": "woman_detective",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐": {
- "name": "guard",
- "slug": "guard",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man guard",
- "slug": "man_guard",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman guard",
- "slug": "woman_guard",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฅท": {
- "name": "ninja",
- "slug": "ninja",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐ท": {
- "name": "construction worker",
- "slug": "construction_worker",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ทโโ๏ธ": {
- "name": "man construction worker",
- "slug": "man_construction_worker",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ทโโ๏ธ": {
- "name": "woman construction worker",
- "slug": "woman_construction_worker",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ซ
": {
- "name": "person with crown",
- "slug": "person_with_crown",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐คด": {
- "name": "prince",
- "slug": "prince",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐ธ": {
- "name": "princess",
- "slug": "princess",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ณ": {
- "name": "person wearing turban",
- "slug": "person_wearing_turban",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ณโโ๏ธ": {
- "name": "man wearing turban",
- "slug": "man_wearing_turban",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ณโโ๏ธ": {
- "name": "woman wearing turban",
- "slug": "woman_wearing_turban",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฒ": {
- "name": "person with skullcap",
- "slug": "person_with_skullcap",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ง": {
- "name": "woman with headscarf",
- "slug": "woman_with_headscarf",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐คต": {
- "name": "person in tuxedo",
- "slug": "person_in_tuxedo",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐คตโโ๏ธ": {
- "name": "man in tuxedo",
- "slug": "man_in_tuxedo",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐คตโโ๏ธ": {
- "name": "woman in tuxedo",
- "slug": "woman_in_tuxedo",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐ฐ": {
- "name": "person with veil",
- "slug": "person_with_veil",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ฐโโ๏ธ": {
- "name": "man with veil",
- "slug": "man_with_veil",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐ฐโโ๏ธ": {
- "name": "woman with veil",
- "slug": "woman_with_veil",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐คฐ": {
- "name": "pregnant woman",
- "slug": "pregnant_woman",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐ซ": {
- "name": "pregnant man",
- "slug": "pregnant_man",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐ซ": {
- "name": "pregnant person",
- "slug": "pregnant_person",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "14.0"
- },
- "๐คฑ": {
- "name": "breast-feeding",
- "slug": "breast_feeding",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ฉโ๐ผ": {
- "name": "woman feeding baby",
- "slug": "woman_feeding_baby",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐จโ๐ผ": {
- "name": "man feeding baby",
- "slug": "man_feeding_baby",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐งโ๐ผ": {
- "name": "person feeding baby",
- "slug": "person_feeding_baby",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐ผ": {
- "name": "baby angel",
- "slug": "baby_angel",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐
": {
- "name": "Santa Claus",
- "slug": "santa_claus",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐คถ": {
- "name": "Mrs. Claus",
- "slug": "mrs_claus",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐งโ๐": {
- "name": "mx claus",
- "slug": "mx_claus",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.0"
- },
- "๐ฆธ": {
- "name": "superhero",
- "slug": "superhero",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐ฆธโโ๏ธ": {
- "name": "man superhero",
- "slug": "man_superhero",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐ฆธโโ๏ธ": {
- "name": "woman superhero",
- "slug": "woman_superhero",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐ฆน": {
- "name": "supervillain",
- "slug": "supervillain",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐ฆนโโ๏ธ": {
- "name": "man supervillain",
- "slug": "man_supervillain",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐ฆนโโ๏ธ": {
- "name": "woman supervillain",
- "slug": "woman_supervillain",
- "group": "People & Body",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "11.0"
- },
- "๐ง": {
- "name": "mage",
- "slug": "mage",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "man mage",
- "slug": "man_mage",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman mage",
- "slug": "woman_mage",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ง": {
- "name": "fairy",
- "slug": "fairy",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "man fairy",
- "slug": "man_fairy",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman fairy",
- "slug": "woman_fairy",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ง": {
- "name": "vampire",
- "slug": "vampire",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "man vampire",
- "slug": "man_vampire",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman vampire",
- "slug": "woman_vampire",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ง": {
- "name": "merperson",
- "slug": "merperson",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "merman",
- "slug": "merman",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "mermaid",
- "slug": "mermaid",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ง": {
- "name": "elf",
- "slug": "elf",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "man elf",
- "slug": "man_elf",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman elf",
- "slug": "woman_elf",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ง": {
- "name": "genie",
- "slug": "genie",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐งโโ๏ธ": {
- "name": "man genie",
- "slug": "man_genie",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐งโโ๏ธ": {
- "name": "woman genie",
- "slug": "woman_genie",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "zombie",
- "slug": "zombie",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐งโโ๏ธ": {
- "name": "man zombie",
- "slug": "man_zombie",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐งโโ๏ธ": {
- "name": "woman zombie",
- "slug": "woman_zombie",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "troll",
- "slug": "troll",
- "group": "People & Body",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "person getting massage",
- "slug": "person_getting_massage",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man getting massage",
- "slug": "man_getting_massage",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman getting massage",
- "slug": "woman_getting_massage",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐": {
- "name": "person getting haircut",
- "slug": "person_getting_haircut",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man getting haircut",
- "slug": "man_getting_haircut",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman getting haircut",
- "slug": "woman_getting_haircut",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ถ": {
- "name": "person walking",
- "slug": "person_walking",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ถโโ๏ธ": {
- "name": "man walking",
- "slug": "man_walking",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ถโโ๏ธ": {
- "name": "woman walking",
- "slug": "woman_walking",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ถโโก๏ธ": {
- "name": "person walking facing right",
- "slug": "person_walking_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐ถโโ๏ธโโก๏ธ": {
- "name": "woman walking facing right",
- "slug": "woman_walking_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐ถโโ๏ธโโก๏ธ": {
- "name": "man walking facing right",
- "slug": "man_walking_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐ง": {
- "name": "person standing",
- "slug": "person_standing",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐งโโ๏ธ": {
- "name": "man standing",
- "slug": "man_standing",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman standing",
- "slug": "woman_standing",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐ง": {
- "name": "person kneeling",
- "slug": "person_kneeling",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐งโโ๏ธ": {
- "name": "man kneeling",
- "slug": "man_kneeling",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman kneeling",
- "slug": "woman_kneeling",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐งโโก๏ธ": {
- "name": "person kneeling facing right",
- "slug": "person_kneeling_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐งโโ๏ธโโก๏ธ": {
- "name": "woman kneeling facing right",
- "slug": "woman_kneeling_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐งโโ๏ธโโก๏ธ": {
- "name": "man kneeling facing right",
- "slug": "man_kneeling_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐งโ๐ฆฏ": {
- "name": "person with white cane",
- "slug": "person_with_white_cane",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐งโ๐ฆฏโโก๏ธ": {
- "name": "person with white cane facing right",
- "slug": "person_with_white_cane_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐จโ๐ฆฏ": {
- "name": "man with white cane",
- "slug": "man_with_white_cane",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐จโ๐ฆฏโโก๏ธ": {
- "name": "man with white cane facing right",
- "slug": "man_with_white_cane_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐ฉโ๐ฆฏ": {
- "name": "woman with white cane",
- "slug": "woman_with_white_cane",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐ฉโ๐ฆฏโโก๏ธ": {
- "name": "woman with white cane facing right",
- "slug": "woman_with_white_cane_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐งโ๐ฆผ": {
- "name": "person in motorized wheelchair",
- "slug": "person_in_motorized_wheelchair",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐งโ๐ฆผโโก๏ธ": {
- "name": "person in motorized wheelchair facing right",
- "slug": "person_in_motorized_wheelchair_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐จโ๐ฆผ": {
- "name": "man in motorized wheelchair",
- "slug": "man_in_motorized_wheelchair",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐จโ๐ฆผโโก๏ธ": {
- "name": "man in motorized wheelchair facing right",
- "slug": "man_in_motorized_wheelchair_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐ฉโ๐ฆผ": {
- "name": "woman in motorized wheelchair",
- "slug": "woman_in_motorized_wheelchair",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐ฉโ๐ฆผโโก๏ธ": {
- "name": "woman in motorized wheelchair facing right",
- "slug": "woman_in_motorized_wheelchair_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐งโ๐ฆฝ": {
- "name": "person in manual wheelchair",
- "slug": "person_in_manual_wheelchair",
- "group": "People & Body",
- "emoji_version": "12.1",
- "unicode_version": "12.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.1"
- },
- "๐งโ๐ฆฝโโก๏ธ": {
- "name": "person in manual wheelchair facing right",
- "slug": "person_in_manual_wheelchair_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐จโ๐ฆฝ": {
- "name": "man in manual wheelchair",
- "slug": "man_in_manual_wheelchair",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐จโ๐ฆฝโโก๏ธ": {
- "name": "man in manual wheelchair facing right",
- "slug": "man_in_manual_wheelchair_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐ฉโ๐ฆฝ": {
- "name": "woman in manual wheelchair",
- "slug": "woman_in_manual_wheelchair",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐ฉโ๐ฆฝโโก๏ธ": {
- "name": "woman in manual wheelchair facing right",
- "slug": "woman_in_manual_wheelchair_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐": {
- "name": "person running",
- "slug": "person_running",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man running",
- "slug": "man_running",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman running",
- "slug": "woman_running",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโก๏ธ": {
- "name": "person running facing right",
- "slug": "person_running_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐โโ๏ธโโก๏ธ": {
- "name": "woman running facing right",
- "slug": "woman_running_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐โโ๏ธโโก๏ธ": {
- "name": "man running facing right",
- "slug": "man_running_facing_right",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "15.1"
- },
- "๐": {
- "name": "woman dancing",
- "slug": "woman_dancing",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐บ": {
- "name": "man dancing",
- "slug": "man_dancing",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐ด๏ธ": {
- "name": "person in suit levitating",
- "slug": "person_in_suit_levitating",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฏ": {
- "name": "people with bunny ears",
- "slug": "people_with_bunny_ears",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏโโ๏ธ": {
- "name": "men with bunny ears",
- "slug": "men_with_bunny_ears",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ฏโโ๏ธ": {
- "name": "women with bunny ears",
- "slug": "women_with_bunny_ears",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "person in steamy room",
- "slug": "person_in_steamy_room",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "man in steamy room",
- "slug": "man_in_steamy_room",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman in steamy room",
- "slug": "woman_in_steamy_room",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐ง": {
- "name": "person climbing",
- "slug": "person_climbing",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "man climbing",
- "slug": "man_climbing",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman climbing",
- "slug": "woman_climbing",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐คบ": {
- "name": "person fencing",
- "slug": "person_fencing",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "horse racing",
- "slug": "horse_racing",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "โท๏ธ": {
- "name": "skier",
- "slug": "skier",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "snowboarder",
- "slug": "snowboarder",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐๏ธ": {
- "name": "person golfing",
- "slug": "person_golfing",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐๏ธโโ๏ธ": {
- "name": "man golfing",
- "slug": "man_golfing",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐๏ธโโ๏ธ": {
- "name": "woman golfing",
- "slug": "woman_golfing",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐": {
- "name": "person surfing",
- "slug": "person_surfing",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man surfing",
- "slug": "man_surfing",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman surfing",
- "slug": "woman_surfing",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฃ": {
- "name": "person rowing boat",
- "slug": "person_rowing_boat",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ฃโโ๏ธ": {
- "name": "man rowing boat",
- "slug": "man_rowing_boat",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ฃโโ๏ธ": {
- "name": "woman rowing boat",
- "slug": "woman_rowing_boat",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐": {
- "name": "person swimming",
- "slug": "person_swimming",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐โโ๏ธ": {
- "name": "man swimming",
- "slug": "man_swimming",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐โโ๏ธ": {
- "name": "woman swimming",
- "slug": "woman_swimming",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "โน๏ธ": {
- "name": "person bouncing ball",
- "slug": "person_bouncing_ball",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "2.0"
- },
- "โน๏ธโโ๏ธ": {
- "name": "man bouncing ball",
- "slug": "man_bouncing_ball",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "โน๏ธโโ๏ธ": {
- "name": "woman bouncing ball",
- "slug": "woman_bouncing_ball",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐๏ธ": {
- "name": "person lifting weights",
- "slug": "person_lifting_weights",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "2.0"
- },
- "๐๏ธโโ๏ธ": {
- "name": "man lifting weights",
- "slug": "man_lifting_weights",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐๏ธโโ๏ธ": {
- "name": "woman lifting weights",
- "slug": "woman_lifting_weights",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ด": {
- "name": "person biking",
- "slug": "person_biking",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ดโโ๏ธ": {
- "name": "man biking",
- "slug": "man_biking",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ดโโ๏ธ": {
- "name": "woman biking",
- "slug": "woman_biking",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ต": {
- "name": "person mountain biking",
- "slug": "person_mountain_biking",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐ตโโ๏ธ": {
- "name": "man mountain biking",
- "slug": "man_mountain_biking",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ตโโ๏ธ": {
- "name": "woman mountain biking",
- "slug": "woman_mountain_biking",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คธ": {
- "name": "person cartwheeling",
- "slug": "person_cartwheeling",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐คธโโ๏ธ": {
- "name": "man cartwheeling",
- "slug": "man_cartwheeling",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คธโโ๏ธ": {
- "name": "woman cartwheeling",
- "slug": "woman_cartwheeling",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คผ": {
- "name": "people wrestling",
- "slug": "people_wrestling",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐คผโโ๏ธ": {
- "name": "men wrestling",
- "slug": "men_wrestling",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐คผโโ๏ธ": {
- "name": "women wrestling",
- "slug": "women_wrestling",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐คฝ": {
- "name": "person playing water polo",
- "slug": "person_playing_water_polo",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐คฝโโ๏ธ": {
- "name": "man playing water polo",
- "slug": "man_playing_water_polo",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คฝโโ๏ธ": {
- "name": "woman playing water polo",
- "slug": "woman_playing_water_polo",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คพ": {
- "name": "person playing handball",
- "slug": "person_playing_handball",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐คพโโ๏ธ": {
- "name": "man playing handball",
- "slug": "man_playing_handball",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คพโโ๏ธ": {
- "name": "woman playing handball",
- "slug": "woman_playing_handball",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คน": {
- "name": "person juggling",
- "slug": "person_juggling",
- "group": "People & Body",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "3.0"
- },
- "๐คนโโ๏ธ": {
- "name": "man juggling",
- "slug": "man_juggling",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐คนโโ๏ธ": {
- "name": "woman juggling",
- "slug": "woman_juggling",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐ง": {
- "name": "person in lotus position",
- "slug": "person_in_lotus_position",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "man in lotus position",
- "slug": "man_in_lotus_position",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐งโโ๏ธ": {
- "name": "woman in lotus position",
- "slug": "woman_in_lotus_position",
- "group": "People & Body",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "5.0"
- },
- "๐": {
- "name": "person taking bath",
- "slug": "person_taking_bath",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "1.0"
- },
- "๐": {
- "name": "person in bed",
- "slug": "person_in_bed",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "4.0"
- },
- "๐งโ๐คโ๐ง": {
- "name": "people holding hands",
- "slug": "people_holding_hands",
- "group": "People & Body",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐ญ": {
- "name": "women holding hands",
- "slug": "women_holding_hands",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐ซ": {
- "name": "woman and man holding hands",
- "slug": "woman_and_man_holding_hands",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐ฌ": {
- "name": "men holding hands",
- "slug": "men_holding_hands",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "12.0"
- },
- "๐": {
- "name": "kiss",
- "slug": "kiss",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐ฉโโค๏ธโ๐โ๐จ": {
- "name": "kiss woman, man",
- "slug": "kiss_woman_man",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐จโโค๏ธโ๐โ๐จ": {
- "name": "kiss man, man",
- "slug": "kiss_man_man",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐ฉโโค๏ธโ๐โ๐ฉ": {
- "name": "kiss woman, woman",
- "slug": "kiss_woman_woman",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐": {
- "name": "couple with heart",
- "slug": "couple_with_heart",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐ฉโโค๏ธโ๐จ": {
- "name": "couple with heart woman, man",
- "slug": "couple_with_heart_woman_man",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐จโโค๏ธโ๐จ": {
- "name": "couple with heart man, man",
- "slug": "couple_with_heart_man_man",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐ฉโโค๏ธโ๐ฉ": {
- "name": "couple with heart woman, woman",
- "slug": "couple_with_heart_woman_woman",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": true,
- "skin_tone_support_unicode_version": "13.1"
- },
- "๐จโ๐ฉโ๐ฆ": {
- "name": "family man, woman, boy",
- "slug": "family_man_woman_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐ฉโ๐ง": {
- "name": "family man, woman, girl",
- "slug": "family_man_woman_girl",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐ฉโ๐งโ๐ฆ": {
- "name": "family man, woman, girl, boy",
- "slug": "family_man_woman_girl_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐ฉโ๐ฆโ๐ฆ": {
- "name": "family man, woman, boy, boy",
- "slug": "family_man_woman_boy_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐ฉโ๐งโ๐ง": {
- "name": "family man, woman, girl, girl",
- "slug": "family_man_woman_girl_girl",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐จโ๐ฆ": {
- "name": "family man, man, boy",
- "slug": "family_man_man_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐จโ๐ง": {
- "name": "family man, man, girl",
- "slug": "family_man_man_girl",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐จโ๐งโ๐ฆ": {
- "name": "family man, man, girl, boy",
- "slug": "family_man_man_girl_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐จโ๐ฆโ๐ฆ": {
- "name": "family man, man, boy, boy",
- "slug": "family_man_man_boy_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐จโ๐งโ๐ง": {
- "name": "family man, man, girl, girl",
- "slug": "family_man_man_girl_girl",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐ฉโ๐ฆ": {
- "name": "family woman, woman, boy",
- "slug": "family_woman_woman_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐ฉโ๐ง": {
- "name": "family woman, woman, girl",
- "slug": "family_woman_woman_girl",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐ฉโ๐งโ๐ฆ": {
- "name": "family woman, woman, girl, boy",
- "slug": "family_woman_woman_girl_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐ฉโ๐ฆโ๐ฆ": {
- "name": "family woman, woman, boy, boy",
- "slug": "family_woman_woman_boy_boy",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐ฉโ๐งโ๐ง": {
- "name": "family woman, woman, girl, girl",
- "slug": "family_woman_woman_girl_girl",
- "group": "People & Body",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จโ๐ฆ": {
- "name": "family man, boy",
- "slug": "family_man_boy",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐จโ๐ฆโ๐ฆ": {
- "name": "family man, boy, boy",
- "slug": "family_man_boy_boy",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐จโ๐ง": {
- "name": "family man, girl",
- "slug": "family_man_girl",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐จโ๐งโ๐ฆ": {
- "name": "family man, girl, boy",
- "slug": "family_man_girl_boy",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐จโ๐งโ๐ง": {
- "name": "family man, girl, girl",
- "slug": "family_man_girl_girl",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐ฆ": {
- "name": "family woman, boy",
- "slug": "family_woman_boy",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐ฆโ๐ฆ": {
- "name": "family woman, boy, boy",
- "slug": "family_woman_boy_boy",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐ง": {
- "name": "family woman, girl",
- "slug": "family_woman_girl",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐งโ๐ฆ": {
- "name": "family woman, girl, boy",
- "slug": "family_woman_girl_boy",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ฉโ๐งโ๐ง": {
- "name": "family woman, girl, girl",
- "slug": "family_woman_girl_girl",
- "group": "People & Body",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ฃ๏ธ": {
- "name": "speaking head",
- "slug": "speaking_head",
- "group": "People & Body",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "bust in silhouette",
- "slug": "bust_in_silhouette",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "busts in silhouette",
- "slug": "busts_in_silhouette",
- "group": "People & Body",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "people hugging",
- "slug": "people_hugging",
- "group": "People & Body",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "family",
- "slug": "family",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งโ๐งโ๐ง": {
- "name": "family adult, adult, child",
- "slug": "family_adult_adult_child",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐งโ๐งโ๐งโ๐ง": {
- "name": "family adult, adult, child, child",
- "slug": "family_adult_adult_child_child",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐งโ๐ง": {
- "name": "family adult, child",
- "slug": "family_adult_child",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐งโ๐งโ๐ง": {
- "name": "family adult, child, child",
- "slug": "family_adult_child_child",
- "group": "People & Body",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "footprints",
- "slug": "footprints",
- "group": "People & Body",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "monkey face",
- "slug": "monkey_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "monkey",
- "slug": "monkey",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "gorilla",
- "slug": "gorilla",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆง": {
- "name": "orangutan",
- "slug": "orangutan",
- "group": "Animals & Nature",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "dog face",
- "slug": "dog_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "dog",
- "slug": "dog",
- "group": "Animals & Nature",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฆฎ": {
- "name": "guide dog",
- "slug": "guide_dog",
- "group": "Animals & Nature",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐โ๐ฆบ": {
- "name": "service dog",
- "slug": "service_dog",
- "group": "Animals & Nature",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "poodle",
- "slug": "poodle",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "wolf",
- "slug": "wolf",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "fox",
- "slug": "fox",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "raccoon",
- "slug": "raccoon",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "cat face",
- "slug": "cat_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "cat",
- "slug": "cat",
- "group": "Animals & Nature",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐โโฌ": {
- "name": "black cat",
- "slug": "black_cat",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "lion",
- "slug": "lion",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "tiger face",
- "slug": "tiger_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "tiger",
- "slug": "tiger",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "leopard",
- "slug": "leopard",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "horse face",
- "slug": "horse_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "moose",
- "slug": "moose",
- "group": "Animals & Nature",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "donkey",
- "slug": "donkey",
- "group": "Animals & Nature",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "horse",
- "slug": "horse",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "unicorn",
- "slug": "unicorn",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "zebra",
- "slug": "zebra",
- "group": "Animals & Nature",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "deer",
- "slug": "deer",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆฌ": {
- "name": "bison",
- "slug": "bison",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "cow face",
- "slug": "cow_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ox",
- "slug": "ox",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "water buffalo",
- "slug": "water_buffalo",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "cow",
- "slug": "cow",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "pig face",
- "slug": "pig_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pig",
- "slug": "pig",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "boar",
- "slug": "boar",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "pig nose",
- "slug": "pig_nose",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ram",
- "slug": "ram",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ewe",
- "slug": "ewe",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "goat",
- "slug": "goat",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "camel",
- "slug": "camel",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "two-hump camel",
- "slug": "two_hump_camel",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "llama",
- "slug": "llama",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "giraffe",
- "slug": "giraffe",
- "group": "Animals & Nature",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "elephant",
- "slug": "elephant",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆฃ": {
- "name": "mammoth",
- "slug": "mammoth",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "rhinoceros",
- "slug": "rhinoceros",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "hippopotamus",
- "slug": "hippopotamus",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "mouse face",
- "slug": "mouse_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "mouse",
- "slug": "mouse",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "rat",
- "slug": "rat",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "hamster",
- "slug": "hamster",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "rabbit face",
- "slug": "rabbit_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "rabbit",
- "slug": "rabbit",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฟ๏ธ": {
- "name": "chipmunk",
- "slug": "chipmunk",
- "group": "Animals & Nature",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฆซ": {
- "name": "beaver",
- "slug": "beaver",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "hedgehog",
- "slug": "hedgehog",
- "group": "Animals & Nature",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "bat",
- "slug": "bat",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "bear",
- "slug": "bear",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ปโโ๏ธ": {
- "name": "polar bear",
- "slug": "polar_bear",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "koala",
- "slug": "koala",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "panda",
- "slug": "panda",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆฅ": {
- "name": "sloth",
- "slug": "sloth",
- "group": "Animals & Nature",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆฆ": {
- "name": "otter",
- "slug": "otter",
- "group": "Animals & Nature",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆจ": {
- "name": "skunk",
- "slug": "skunk",
- "group": "Animals & Nature",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "kangaroo",
- "slug": "kangaroo",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฆก": {
- "name": "badger",
- "slug": "badger",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "paw prints",
- "slug": "paw_prints",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "turkey",
- "slug": "turkey",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "chicken",
- "slug": "chicken",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "rooster",
- "slug": "rooster",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "hatching chick",
- "slug": "hatching_chick",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "baby chick",
- "slug": "baby_chick",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "front-facing baby chick",
- "slug": "front_facing_baby_chick",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "bird",
- "slug": "bird",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "penguin",
- "slug": "penguin",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "dove",
- "slug": "dove",
- "group": "Animals & Nature",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฆ
": {
- "name": "eagle",
- "slug": "eagle",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "duck",
- "slug": "duck",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆข": {
- "name": "swan",
- "slug": "swan",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "owl",
- "slug": "owl",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆค": {
- "name": "dodo",
- "slug": "dodo",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ชถ": {
- "name": "feather",
- "slug": "feather",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฆฉ": {
- "name": "flamingo",
- "slug": "flamingo",
- "group": "Animals & Nature",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "peacock",
- "slug": "peacock",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "parrot",
- "slug": "parrot",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ชฝ": {
- "name": "wing",
- "slug": "wing",
- "group": "Animals & Nature",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ฆโโฌ": {
- "name": "black bird",
- "slug": "black_bird",
- "group": "Animals & Nature",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ชฟ": {
- "name": "goose",
- "slug": "goose",
- "group": "Animals & Nature",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ฆโ๐ฅ": {
- "name": "phoenix",
- "slug": "phoenix",
- "group": "Animals & Nature",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "frog",
- "slug": "frog",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "crocodile",
- "slug": "crocodile",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "turtle",
- "slug": "turtle",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "lizard",
- "slug": "lizard",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "snake",
- "slug": "snake",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "dragon face",
- "slug": "dragon_face",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "dragon",
- "slug": "dragon",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "sauropod",
- "slug": "sauropod",
- "group": "Animals & Nature",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "T-Rex",
- "slug": "t_rex",
- "group": "Animals & Nature",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "spouting whale",
- "slug": "spouting_whale",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "whale",
- "slug": "whale",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "dolphin",
- "slug": "dolphin",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆญ": {
- "name": "seal",
- "slug": "seal",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "fish",
- "slug": "fish",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "tropical fish",
- "slug": "tropical_fish",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "blowfish",
- "slug": "blowfish",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "shark",
- "slug": "shark",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "octopus",
- "slug": "octopus",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "spiral shell",
- "slug": "spiral_shell",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชธ": {
- "name": "coral",
- "slug": "coral",
- "group": "Animals & Nature",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ชผ": {
- "name": "jellyfish",
- "slug": "jellyfish",
- "group": "Animals & Nature",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "snail",
- "slug": "snail",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "butterfly",
- "slug": "butterfly",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bug",
- "slug": "bug",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ant",
- "slug": "ant",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "honeybee",
- "slug": "honeybee",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชฒ": {
- "name": "beetle",
- "slug": "beetle",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "lady beetle",
- "slug": "lady_beetle",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "cricket",
- "slug": "cricket",
- "group": "Animals & Nature",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ชณ": {
- "name": "cockroach",
- "slug": "cockroach",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ท๏ธ": {
- "name": "spider",
- "slug": "spider",
- "group": "Animals & Nature",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ธ๏ธ": {
- "name": "spider web",
- "slug": "spider_web",
- "group": "Animals & Nature",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "scorpion",
- "slug": "scorpion",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "mosquito",
- "slug": "mosquito",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ชฐ": {
- "name": "fly",
- "slug": "fly",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ชฑ": {
- "name": "worm",
- "slug": "worm",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฆ ": {
- "name": "microbe",
- "slug": "microbe",
- "group": "Animals & Nature",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bouquet",
- "slug": "bouquet",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "cherry blossom",
- "slug": "cherry_blossom",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "white flower",
- "slug": "white_flower",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชท": {
- "name": "lotus",
- "slug": "lotus",
- "group": "Animals & Nature",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ต๏ธ": {
- "name": "rosette",
- "slug": "rosette",
- "group": "Animals & Nature",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "rose",
- "slug": "rose",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "wilted flower",
- "slug": "wilted_flower",
- "group": "Animals & Nature",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "hibiscus",
- "slug": "hibiscus",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "sunflower",
- "slug": "sunflower",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "blossom",
- "slug": "blossom",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "tulip",
- "slug": "tulip",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชป": {
- "name": "hyacinth",
- "slug": "hyacinth",
- "group": "Animals & Nature",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "seedling",
- "slug": "seedling",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชด": {
- "name": "potted plant",
- "slug": "potted_plant",
- "group": "Animals & Nature",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "evergreen tree",
- "slug": "evergreen_tree",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "deciduous tree",
- "slug": "deciduous_tree",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "palm tree",
- "slug": "palm_tree",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "cactus",
- "slug": "cactus",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "sheaf of rice",
- "slug": "sheaf_of_rice",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "herb",
- "slug": "herb",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "shamrock",
- "slug": "shamrock",
- "group": "Animals & Nature",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "four leaf clover",
- "slug": "four_leaf_clover",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "maple leaf",
- "slug": "maple_leaf",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "fallen leaf",
- "slug": "fallen_leaf",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "leaf fluttering in wind",
- "slug": "leaf_fluttering_in_wind",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชน": {
- "name": "empty nest",
- "slug": "empty_nest",
- "group": "Animals & Nature",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ชบ": {
- "name": "nest with eggs",
- "slug": "nest_with_eggs",
- "group": "Animals & Nature",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "mushroom",
- "slug": "mushroom",
- "group": "Animals & Nature",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "grapes",
- "slug": "grapes",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "melon",
- "slug": "melon",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "watermelon",
- "slug": "watermelon",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "tangerine",
- "slug": "tangerine",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "lemon",
- "slug": "lemon",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐โ๐ฉ": {
- "name": "lime",
- "slug": "lime",
- "group": "Food & Drink",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐": {
- "name": "banana",
- "slug": "banana",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pineapple",
- "slug": "pineapple",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅญ": {
- "name": "mango",
- "slug": "mango",
- "group": "Food & Drink",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "red apple",
- "slug": "red_apple",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "green apple",
- "slug": "green_apple",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pear",
- "slug": "pear",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "peach",
- "slug": "peach",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "cherries",
- "slug": "cherries",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "strawberry",
- "slug": "strawberry",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "blueberries",
- "slug": "blueberries",
- "group": "Food & Drink",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "kiwi fruit",
- "slug": "kiwi_fruit",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "tomato",
- "slug": "tomato",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "olive",
- "slug": "olive",
- "group": "Food & Drink",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฅฅ": {
- "name": "coconut",
- "slug": "coconut",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "avocado",
- "slug": "avocado",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "eggplant",
- "slug": "eggplant",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "potato",
- "slug": "potato",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "carrot",
- "slug": "carrot",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "ear of corn",
- "slug": "ear_of_corn",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ถ๏ธ": {
- "name": "hot pepper",
- "slug": "hot_pepper",
- "group": "Food & Drink",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "bell pepper",
- "slug": "bell_pepper",
- "group": "Food & Drink",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "cucumber",
- "slug": "cucumber",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅฌ": {
- "name": "leafy green",
- "slug": "leafy_green",
- "group": "Food & Drink",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅฆ": {
- "name": "broccoli",
- "slug": "broccoli",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "garlic",
- "slug": "garlic",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ง
": {
- "name": "onion",
- "slug": "onion",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "peanuts",
- "slug": "peanuts",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "beans",
- "slug": "beans",
- "group": "Food & Drink",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "chestnut",
- "slug": "chestnut",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "ginger root",
- "slug": "ginger_root",
- "group": "Food & Drink",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "pea pod",
- "slug": "pea_pod",
- "group": "Food & Drink",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐โ๐ซ": {
- "name": "brown mushroom",
- "slug": "brown_mushroom",
- "group": "Food & Drink",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bread",
- "slug": "bread",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "croissant",
- "slug": "croissant",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "baguette bread",
- "slug": "baguette_bread",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "flatbread",
- "slug": "flatbread",
- "group": "Food & Drink",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฅจ": {
- "name": "pretzel",
- "slug": "pretzel",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฅฏ": {
- "name": "bagel",
- "slug": "bagel",
- "group": "Food & Drink",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "pancakes",
- "slug": "pancakes",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "waffle",
- "slug": "waffle",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "cheese wedge",
- "slug": "cheese_wedge",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "meat on bone",
- "slug": "meat_on_bone",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "poultry leg",
- "slug": "poultry_leg",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅฉ": {
- "name": "cut of meat",
- "slug": "cut_of_meat",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "bacon",
- "slug": "bacon",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "hamburger",
- "slug": "hamburger",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "french fries",
- "slug": "french_fries",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pizza",
- "slug": "pizza",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "hot dog",
- "slug": "hot_dog",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅช": {
- "name": "sandwich",
- "slug": "sandwich",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "taco",
- "slug": "taco",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "burrito",
- "slug": "burrito",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "tamale",
- "slug": "tamale",
- "group": "Food & Drink",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "stuffed flatbread",
- "slug": "stuffed_flatbread",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "falafel",
- "slug": "falafel",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "egg",
- "slug": "egg",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "cooking",
- "slug": "cooking",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "shallow pan of food",
- "slug": "shallow_pan_of_food",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "pot of food",
- "slug": "pot_of_food",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "fondue",
- "slug": "fondue",
- "group": "Food & Drink",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฅฃ": {
- "name": "bowl with spoon",
- "slug": "bowl_with_spoon",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "green salad",
- "slug": "green_salad",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "popcorn",
- "slug": "popcorn",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "butter",
- "slug": "butter",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "salt",
- "slug": "salt",
- "group": "Food & Drink",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅซ": {
- "name": "canned food",
- "slug": "canned_food",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "bento box",
- "slug": "bento_box",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "rice cracker",
- "slug": "rice_cracker",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "rice ball",
- "slug": "rice_ball",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "cooked rice",
- "slug": "cooked_rice",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "curry rice",
- "slug": "curry_rice",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "steaming bowl",
- "slug": "steaming_bowl",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "spaghetti",
- "slug": "spaghetti",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "roasted sweet potato",
- "slug": "roasted_sweet_potato",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "oden",
- "slug": "oden",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "sushi",
- "slug": "sushi",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "fried shrimp",
- "slug": "fried_shrimp",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "fish cake with swirl",
- "slug": "fish_cake_with_swirl",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅฎ": {
- "name": "moon cake",
- "slug": "moon_cake",
- "group": "Food & Drink",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "dango",
- "slug": "dango",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "dumpling",
- "slug": "dumpling",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฅ ": {
- "name": "fortune cookie",
- "slug": "fortune_cookie",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฅก": {
- "name": "takeout box",
- "slug": "takeout_box",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "crab",
- "slug": "crab",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "lobster",
- "slug": "lobster",
- "group": "Food & Drink",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "shrimp",
- "slug": "shrimp",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "squid",
- "slug": "squid",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆช": {
- "name": "oyster",
- "slug": "oyster",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "soft ice cream",
- "slug": "soft_ice_cream",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "shaved ice",
- "slug": "shaved_ice",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "ice cream",
- "slug": "ice_cream",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "doughnut",
- "slug": "doughnut",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "cookie",
- "slug": "cookie",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "birthday cake",
- "slug": "birthday_cake",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "shortcake",
- "slug": "shortcake",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "cupcake",
- "slug": "cupcake",
- "group": "Food & Drink",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅง": {
- "name": "pie",
- "slug": "pie",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "chocolate bar",
- "slug": "chocolate_bar",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "candy",
- "slug": "candy",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "lollipop",
- "slug": "lollipop",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "custard",
- "slug": "custard",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "honey pot",
- "slug": "honey_pot",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "baby bottle",
- "slug": "baby_bottle",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "glass of milk",
- "slug": "glass_of_milk",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "โ": {
- "name": "hot beverage",
- "slug": "hot_beverage",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "teapot",
- "slug": "teapot",
- "group": "Food & Drink",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "teacup without handle",
- "slug": "teacup_without_handle",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "sake",
- "slug": "sake",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "bottle with popping cork",
- "slug": "bottle_with_popping_cork",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "wine glass",
- "slug": "wine_glass",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "cocktail glass",
- "slug": "cocktail_glass",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "tropical drink",
- "slug": "tropical_drink",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "beer mug",
- "slug": "beer_mug",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "clinking beer mugs",
- "slug": "clinking_beer_mugs",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "clinking glasses",
- "slug": "clinking_glasses",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "tumbler glass",
- "slug": "tumbler_glass",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "pouring liquid",
- "slug": "pouring_liquid",
- "group": "Food & Drink",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ฅค": {
- "name": "cup with straw",
- "slug": "cup_with_straw",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "bubble tea",
- "slug": "bubble_tea",
- "group": "Food & Drink",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "beverage box",
- "slug": "beverage_box",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "mate",
- "slug": "mate",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "ice",
- "slug": "ice",
- "group": "Food & Drink",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฅข": {
- "name": "chopsticks",
- "slug": "chopsticks",
- "group": "Food & Drink",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฝ๏ธ": {
- "name": "fork and knife with plate",
- "slug": "fork_and_knife_with_plate",
- "group": "Food & Drink",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "fork and knife",
- "slug": "fork_and_knife",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "spoon",
- "slug": "spoon",
- "group": "Food & Drink",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "kitchen knife",
- "slug": "kitchen_knife",
- "group": "Food & Drink",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "jar",
- "slug": "jar",
- "group": "Food & Drink",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "amphora",
- "slug": "amphora",
- "group": "Food & Drink",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "globe showing Europe-Africa",
- "slug": "globe_showing_europe_africa",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "globe showing Americas",
- "slug": "globe_showing_americas",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "globe showing Asia-Australia",
- "slug": "globe_showing_asia_australia",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "globe with meridians",
- "slug": "globe_with_meridians",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐บ๏ธ": {
- "name": "world map",
- "slug": "world_map",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "map of Japan",
- "slug": "map_of_japan",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งญ": {
- "name": "compass",
- "slug": "compass",
- "group": "Travel & Places",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "snow-capped mountain",
- "slug": "snow_capped_mountain",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โฐ๏ธ": {
- "name": "mountain",
- "slug": "mountain",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "volcano",
- "slug": "volcano",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "mount fuji",
- "slug": "mount_fuji",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "camping",
- "slug": "camping",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "beach with umbrella",
- "slug": "beach_with_umbrella",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "desert",
- "slug": "desert",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "desert island",
- "slug": "desert_island",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "national park",
- "slug": "national_park",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "stadium",
- "slug": "stadium",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "classical building",
- "slug": "classical_building",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "building construction",
- "slug": "building_construction",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐งฑ": {
- "name": "brick",
- "slug": "brick",
- "group": "Travel & Places",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ชจ": {
- "name": "rock",
- "slug": "rock",
- "group": "Travel & Places",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ชต": {
- "name": "wood",
- "slug": "wood",
- "group": "Travel & Places",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "hut",
- "slug": "hut",
- "group": "Travel & Places",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "houses",
- "slug": "houses",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "derelict house",
- "slug": "derelict_house",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "house",
- "slug": "house",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "house with garden",
- "slug": "house_with_garden",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "office building",
- "slug": "office_building",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "Japanese post office",
- "slug": "japanese_post_office",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "post office",
- "slug": "post_office",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "hospital",
- "slug": "hospital",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "bank",
- "slug": "bank",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "hotel",
- "slug": "hotel",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "love hotel",
- "slug": "love_hotel",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "convenience store",
- "slug": "convenience_store",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "school",
- "slug": "school",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "department store",
- "slug": "department_store",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "factory",
- "slug": "factory",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "Japanese castle",
- "slug": "japanese_castle",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "castle",
- "slug": "castle",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "wedding",
- "slug": "wedding",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "Tokyo tower",
- "slug": "tokyo_tower",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "Statue of Liberty",
- "slug": "statue_of_liberty",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โช": {
- "name": "church",
- "slug": "church",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "mosque",
- "slug": "mosque",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "hindu temple",
- "slug": "hindu_temple",
- "group": "Travel & Places",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "synagogue",
- "slug": "synagogue",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โฉ๏ธ": {
- "name": "shinto shrine",
- "slug": "shinto_shrine",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "kaaba",
- "slug": "kaaba",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โฒ": {
- "name": "fountain",
- "slug": "fountain",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โบ": {
- "name": "tent",
- "slug": "tent",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "foggy",
- "slug": "foggy",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "night with stars",
- "slug": "night_with_stars",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "cityscape",
- "slug": "cityscape",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "sunrise over mountains",
- "slug": "sunrise_over_mountains",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "sunrise",
- "slug": "sunrise",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "cityscape at dusk",
- "slug": "cityscape_at_dusk",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "sunset",
- "slug": "sunset",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bridge at night",
- "slug": "bridge_at_night",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โจ๏ธ": {
- "name": "hot springs",
- "slug": "hot_springs",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "carousel horse",
- "slug": "carousel_horse",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "playground slide",
- "slug": "playground_slide",
- "group": "Travel & Places",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "ferris wheel",
- "slug": "ferris_wheel",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "roller coaster",
- "slug": "roller_coaster",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "barber pole",
- "slug": "barber_pole",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "circus tent",
- "slug": "circus_tent",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "locomotive",
- "slug": "locomotive",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "railway car",
- "slug": "railway_car",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "high-speed train",
- "slug": "high_speed_train",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "bullet train",
- "slug": "bullet_train",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "train",
- "slug": "train",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "metro",
- "slug": "metro",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "light rail",
- "slug": "light_rail",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "station",
- "slug": "station",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "tram",
- "slug": "tram",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "monorail",
- "slug": "monorail",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "mountain railway",
- "slug": "mountain_railway",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "tram car",
- "slug": "tram_car",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bus",
- "slug": "bus",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "oncoming bus",
- "slug": "oncoming_bus",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "trolleybus",
- "slug": "trolleybus",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "minibus",
- "slug": "minibus",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ambulance",
- "slug": "ambulance",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "fire engine",
- "slug": "fire_engine",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "police car",
- "slug": "police_car",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "oncoming police car",
- "slug": "oncoming_police_car",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "taxi",
- "slug": "taxi",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "oncoming taxi",
- "slug": "oncoming_taxi",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "automobile",
- "slug": "automobile",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "oncoming automobile",
- "slug": "oncoming_automobile",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "sport utility vehicle",
- "slug": "sport_utility_vehicle",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "pickup truck",
- "slug": "pickup_truck",
- "group": "Travel & Places",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "delivery truck",
- "slug": "delivery_truck",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "articulated lorry",
- "slug": "articulated_lorry",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "tractor",
- "slug": "tractor",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "racing car",
- "slug": "racing_car",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "motorcycle",
- "slug": "motorcycle",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "motor scooter",
- "slug": "motor_scooter",
- "group": "Travel & Places",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฆฝ": {
- "name": "manual wheelchair",
- "slug": "manual_wheelchair",
- "group": "Travel & Places",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆผ": {
- "name": "motorized wheelchair",
- "slug": "motorized_wheelchair",
- "group": "Travel & Places",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "auto rickshaw",
- "slug": "auto_rickshaw",
- "group": "Travel & Places",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "bicycle",
- "slug": "bicycle",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "kick scooter",
- "slug": "kick_scooter",
- "group": "Travel & Places",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "skateboard",
- "slug": "skateboard",
- "group": "Travel & Places",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "roller skate",
- "slug": "roller_skate",
- "group": "Travel & Places",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bus stop",
- "slug": "bus_stop",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฃ๏ธ": {
- "name": "motorway",
- "slug": "motorway",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ค๏ธ": {
- "name": "railway track",
- "slug": "railway_track",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ข๏ธ": {
- "name": "oil drum",
- "slug": "oil_drum",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โฝ": {
- "name": "fuel pump",
- "slug": "fuel_pump",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "wheel",
- "slug": "wheel",
- "group": "Travel & Places",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "police car light",
- "slug": "police_car_light",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "horizontal traffic light",
- "slug": "horizontal_traffic_light",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "vertical traffic light",
- "slug": "vertical_traffic_light",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "stop sign",
- "slug": "stop_sign",
- "group": "Travel & Places",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "construction",
- "slug": "construction",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "anchor",
- "slug": "anchor",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ring buoy",
- "slug": "ring_buoy",
- "group": "Travel & Places",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "โต": {
- "name": "sailboat",
- "slug": "sailboat",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "canoe",
- "slug": "canoe",
- "group": "Travel & Places",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "speedboat",
- "slug": "speedboat",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ณ๏ธ": {
- "name": "passenger ship",
- "slug": "passenger_ship",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โด๏ธ": {
- "name": "ferry",
- "slug": "ferry",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฅ๏ธ": {
- "name": "motor boat",
- "slug": "motor_boat",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "ship",
- "slug": "ship",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "airplane",
- "slug": "airplane",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉ๏ธ": {
- "name": "small airplane",
- "slug": "small_airplane",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "airplane departure",
- "slug": "airplane_departure",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "airplane arrival",
- "slug": "airplane_arrival",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "parachute",
- "slug": "parachute",
- "group": "Travel & Places",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "seat",
- "slug": "seat",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "helicopter",
- "slug": "helicopter",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "suspension railway",
- "slug": "suspension_railway",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "mountain cableway",
- "slug": "mountain_cableway",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "aerial tramway",
- "slug": "aerial_tramway",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฐ๏ธ": {
- "name": "satellite",
- "slug": "satellite",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "rocket",
- "slug": "rocket",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "flying saucer",
- "slug": "flying_saucer",
- "group": "Travel & Places",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "bellhop bell",
- "slug": "bellhop_bell",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐งณ": {
- "name": "luggage",
- "slug": "luggage",
- "group": "Travel & Places",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "โ": {
- "name": "hourglass done",
- "slug": "hourglass_done",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โณ": {
- "name": "hourglass not done",
- "slug": "hourglass_not_done",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "watch",
- "slug": "watch",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฐ": {
- "name": "alarm clock",
- "slug": "alarm_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฑ๏ธ": {
- "name": "stopwatch",
- "slug": "stopwatch",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โฒ๏ธ": {
- "name": "timer clock",
- "slug": "timer_clock",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฐ๏ธ": {
- "name": "mantelpiece clock",
- "slug": "mantelpiece_clock",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "twelve oโclock",
- "slug": "twelve_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "twelve-thirty",
- "slug": "twelve_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "one oโclock",
- "slug": "one_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "one-thirty",
- "slug": "one_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "two oโclock",
- "slug": "two_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "two-thirty",
- "slug": "two_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "three oโclock",
- "slug": "three_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "three-thirty",
- "slug": "three_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "four oโclock",
- "slug": "four_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "four-thirty",
- "slug": "four_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "five oโclock",
- "slug": "five_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "five-thirty",
- "slug": "five_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "six oโclock",
- "slug": "six_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "six-thirty",
- "slug": "six_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "seven oโclock",
- "slug": "seven_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "seven-thirty",
- "slug": "seven_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "eight oโclock",
- "slug": "eight_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "eight-thirty",
- "slug": "eight_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "nine oโclock",
- "slug": "nine_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "nine-thirty",
- "slug": "nine_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ten oโclock",
- "slug": "ten_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "ten-thirty",
- "slug": "ten_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "eleven oโclock",
- "slug": "eleven_o_clock",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "eleven-thirty",
- "slug": "eleven_thirty",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "new moon",
- "slug": "new_moon",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "waxing crescent moon",
- "slug": "waxing_crescent_moon",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "first quarter moon",
- "slug": "first_quarter_moon",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "waxing gibbous moon",
- "slug": "waxing_gibbous_moon",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "full moon",
- "slug": "full_moon",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "waning gibbous moon",
- "slug": "waning_gibbous_moon",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "last quarter moon",
- "slug": "last_quarter_moon",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "waning crescent moon",
- "slug": "waning_crescent_moon",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "crescent moon",
- "slug": "crescent_moon",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "new moon face",
- "slug": "new_moon_face",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "first quarter moon face",
- "slug": "first_quarter_moon_face",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "last quarter moon face",
- "slug": "last_quarter_moon_face",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ก๏ธ": {
- "name": "thermometer",
- "slug": "thermometer",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "sun",
- "slug": "sun",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "full moon face",
- "slug": "full_moon_face",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "sun with face",
- "slug": "sun_with_face",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "ringed planet",
- "slug": "ringed_planet",
- "group": "Travel & Places",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "โญ": {
- "name": "star",
- "slug": "star",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "glowing star",
- "slug": "glowing_star",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "shooting star",
- "slug": "shooting_star",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "milky way",
- "slug": "milky_way",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "cloud",
- "slug": "cloud",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ
": {
- "name": "sun behind cloud",
- "slug": "sun_behind_cloud",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "cloud with lightning and rain",
- "slug": "cloud_with_lightning_and_rain",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ค๏ธ": {
- "name": "sun behind small cloud",
- "slug": "sun_behind_small_cloud",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฅ๏ธ": {
- "name": "sun behind large cloud",
- "slug": "sun_behind_large_cloud",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฆ๏ธ": {
- "name": "sun behind rain cloud",
- "slug": "sun_behind_rain_cloud",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ง๏ธ": {
- "name": "cloud with rain",
- "slug": "cloud_with_rain",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐จ๏ธ": {
- "name": "cloud with snow",
- "slug": "cloud_with_snow",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฉ๏ธ": {
- "name": "cloud with lightning",
- "slug": "cloud_with_lightning",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ช๏ธ": {
- "name": "tornado",
- "slug": "tornado",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ซ๏ธ": {
- "name": "fog",
- "slug": "fog",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฌ๏ธ": {
- "name": "wind face",
- "slug": "wind_face",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "cyclone",
- "slug": "cyclone",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "rainbow",
- "slug": "rainbow",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "closed umbrella",
- "slug": "closed_umbrella",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "umbrella",
- "slug": "umbrella",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ": {
- "name": "umbrella with rain drops",
- "slug": "umbrella_with_rain_drops",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฑ๏ธ": {
- "name": "umbrella on ground",
- "slug": "umbrella_on_ground",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โก": {
- "name": "high voltage",
- "slug": "high_voltage",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "snowflake",
- "slug": "snowflake",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "snowman",
- "slug": "snowman",
- "group": "Travel & Places",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ": {
- "name": "snowman without snow",
- "slug": "snowman_without_snow",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "comet",
- "slug": "comet",
- "group": "Travel & Places",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "fire",
- "slug": "fire",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "droplet",
- "slug": "droplet",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "water wave",
- "slug": "water_wave",
- "group": "Travel & Places",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "jack-o-lantern",
- "slug": "jack_o_lantern",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "Christmas tree",
- "slug": "christmas_tree",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "fireworks",
- "slug": "fireworks",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "sparkler",
- "slug": "sparkler",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งจ": {
- "name": "firecracker",
- "slug": "firecracker",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "โจ": {
- "name": "sparkles",
- "slug": "sparkles",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "balloon",
- "slug": "balloon",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "party popper",
- "slug": "party_popper",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "confetti ball",
- "slug": "confetti_ball",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "tanabata tree",
- "slug": "tanabata_tree",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pine decoration",
- "slug": "pine_decoration",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "Japanese dolls",
- "slug": "japanese_dolls",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "carp streamer",
- "slug": "carp_streamer",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "wind chime",
- "slug": "wind_chime",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "moon viewing ceremony",
- "slug": "moon_viewing_ceremony",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งง": {
- "name": "red envelope",
- "slug": "red_envelope",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ribbon",
- "slug": "ribbon",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "wrapped gift",
- "slug": "wrapped_gift",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "reminder ribbon",
- "slug": "reminder_ribbon",
- "group": "Activities",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "admission tickets",
- "slug": "admission_tickets",
- "group": "Activities",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "ticket",
- "slug": "ticket",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "military medal",
- "slug": "military_medal",
- "group": "Activities",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "trophy",
- "slug": "trophy",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "sports medal",
- "slug": "sports_medal",
- "group": "Activities",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "1st place medal",
- "slug": "1st_place_medal",
- "group": "Activities",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "2nd place medal",
- "slug": "2nd_place_medal",
- "group": "Activities",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "3rd place medal",
- "slug": "3rd_place_medal",
- "group": "Activities",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "โฝ": {
- "name": "soccer ball",
- "slug": "soccer_ball",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โพ": {
- "name": "baseball",
- "slug": "baseball",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "softball",
- "slug": "softball",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "basketball",
- "slug": "basketball",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "volleyball",
- "slug": "volleyball",
- "group": "Activities",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "american football",
- "slug": "american_football",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "rugby football",
- "slug": "rugby_football",
- "group": "Activities",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "tennis",
- "slug": "tennis",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "flying disc",
- "slug": "flying_disc",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "bowling",
- "slug": "bowling",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "cricket game",
- "slug": "cricket_game",
- "group": "Activities",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "field hockey",
- "slug": "field_hockey",
- "group": "Activities",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ice hockey",
- "slug": "ice_hockey",
- "group": "Activities",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "lacrosse",
- "slug": "lacrosse",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ping pong",
- "slug": "ping_pong",
- "group": "Activities",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "badminton",
- "slug": "badminton",
- "group": "Activities",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "boxing glove",
- "slug": "boxing_glove",
- "group": "Activities",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "martial arts uniform",
- "slug": "martial_arts_uniform",
- "group": "Activities",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฅ
": {
- "name": "goal net",
- "slug": "goal_net",
- "group": "Activities",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "โณ": {
- "name": "flag in hole",
- "slug": "flag_in_hole",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โธ๏ธ": {
- "name": "ice skate",
- "slug": "ice_skate",
- "group": "Activities",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "fishing pole",
- "slug": "fishing_pole",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐คฟ": {
- "name": "diving mask",
- "slug": "diving_mask",
- "group": "Activities",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "running shirt",
- "slug": "running_shirt",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "skis",
- "slug": "skis",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "sled",
- "slug": "sled",
- "group": "Activities",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "curling stone",
- "slug": "curling_stone",
- "group": "Activities",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "bullseye",
- "slug": "bullseye",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "yo-yo",
- "slug": "yo_yo",
- "group": "Activities",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "kite",
- "slug": "kite",
- "group": "Activities",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "water pistol",
- "slug": "water_pistol",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "pool 8 ball",
- "slug": "pool_8_ball",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "crystal ball",
- "slug": "crystal_ball",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "magic wand",
- "slug": "magic_wand",
- "group": "Activities",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "video game",
- "slug": "video_game",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐น๏ธ": {
- "name": "joystick",
- "slug": "joystick",
- "group": "Activities",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "slot machine",
- "slug": "slot_machine",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "game die",
- "slug": "game_die",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งฉ": {
- "name": "puzzle piece",
- "slug": "puzzle_piece",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งธ": {
- "name": "teddy bear",
- "slug": "teddy_bear",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ช
": {
- "name": "piรฑata",
- "slug": "pinata",
- "group": "Activities",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ชฉ": {
- "name": "mirror ball",
- "slug": "mirror_ball",
- "group": "Activities",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "nesting dolls",
- "slug": "nesting_dolls",
- "group": "Activities",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "โ ๏ธ": {
- "name": "spade suit",
- "slug": "spade_suit",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฅ๏ธ": {
- "name": "heart suit",
- "slug": "heart_suit",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฆ๏ธ": {
- "name": "diamond suit",
- "slug": "diamond_suit",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฃ๏ธ": {
- "name": "club suit",
- "slug": "club_suit",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "chess pawn",
- "slug": "chess_pawn",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "joker",
- "slug": "joker",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "mahjong red dragon",
- "slug": "mahjong_red_dragon",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "flower playing cards",
- "slug": "flower_playing_cards",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "performing arts",
- "slug": "performing_arts",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ๏ธ": {
- "name": "framed picture",
- "slug": "framed_picture",
- "group": "Activities",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "artist palette",
- "slug": "artist_palette",
- "group": "Activities",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งต": {
- "name": "thread",
- "slug": "thread",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ชก": {
- "name": "sewing needle",
- "slug": "sewing_needle",
- "group": "Activities",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐งถ": {
- "name": "yarn",
- "slug": "yarn",
- "group": "Activities",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ชข": {
- "name": "knot",
- "slug": "knot",
- "group": "Activities",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "glasses",
- "slug": "glasses",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ถ๏ธ": {
- "name": "sunglasses",
- "slug": "sunglasses",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฅฝ": {
- "name": "goggles",
- "slug": "goggles",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅผ": {
- "name": "lab coat",
- "slug": "lab_coat",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฆบ": {
- "name": "safety vest",
- "slug": "safety_vest",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "necktie",
- "slug": "necktie",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "t-shirt",
- "slug": "t_shirt",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "jeans",
- "slug": "jeans",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งฃ": {
- "name": "scarf",
- "slug": "scarf",
- "group": "Objects",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐งค": {
- "name": "gloves",
- "slug": "gloves",
- "group": "Objects",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐งฅ": {
- "name": "coat",
- "slug": "coat",
- "group": "Objects",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐งฆ": {
- "name": "socks",
- "slug": "socks",
- "group": "Objects",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "dress",
- "slug": "dress",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "kimono",
- "slug": "kimono",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅป": {
- "name": "sari",
- "slug": "sari",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฉฑ": {
- "name": "one-piece swimsuit",
- "slug": "one_piece_swimsuit",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฉฒ": {
- "name": "briefs",
- "slug": "briefs",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฉณ": {
- "name": "shorts",
- "slug": "shorts",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bikini",
- "slug": "bikini",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "womanโs clothes",
- "slug": "woman_s_clothes",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชญ": {
- "name": "folding hand fan",
- "slug": "folding_hand_fan",
- "group": "Objects",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "purse",
- "slug": "purse",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "handbag",
- "slug": "handbag",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "clutch bag",
- "slug": "clutch_bag",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "shopping bags",
- "slug": "shopping_bags",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "backpack",
- "slug": "backpack",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉด": {
- "name": "thong sandal",
- "slug": "thong_sandal",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "manโs shoe",
- "slug": "man_s_shoe",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "running shoe",
- "slug": "running_shoe",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅพ": {
- "name": "hiking boot",
- "slug": "hiking_boot",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅฟ": {
- "name": "flat shoe",
- "slug": "flat_shoe",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "high-heeled shoe",
- "slug": "high_heeled_shoe",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "womanโs sandal",
- "slug": "woman_s_sandal",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉฐ": {
- "name": "ballet shoes",
- "slug": "ballet_shoes",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "womanโs boot",
- "slug": "woman_s_boot",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชฎ": {
- "name": "hair pick",
- "slug": "hair_pick",
- "group": "Objects",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "crown",
- "slug": "crown",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "womanโs hat",
- "slug": "woman_s_hat",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "top hat",
- "slug": "top_hat",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "graduation cap",
- "slug": "graduation_cap",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งข": {
- "name": "billed cap",
- "slug": "billed_cap",
- "group": "Objects",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "military helmet",
- "slug": "military_helmet",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "rescue workerโs helmet",
- "slug": "rescue_worker_s_helmet",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "prayer beads",
- "slug": "prayer_beads",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "lipstick",
- "slug": "lipstick",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ring",
- "slug": "ring",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "gem stone",
- "slug": "gem_stone",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "muted speaker",
- "slug": "muted_speaker",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "speaker low volume",
- "slug": "speaker_low_volume",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "speaker medium volume",
- "slug": "speaker_medium_volume",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "speaker high volume",
- "slug": "speaker_high_volume",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "loudspeaker",
- "slug": "loudspeaker",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "megaphone",
- "slug": "megaphone",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "postal horn",
- "slug": "postal_horn",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bell",
- "slug": "bell",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bell with slash",
- "slug": "bell_with_slash",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "musical score",
- "slug": "musical_score",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "musical note",
- "slug": "musical_note",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "musical notes",
- "slug": "musical_notes",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "studio microphone",
- "slug": "studio_microphone",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "level slider",
- "slug": "level_slider",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "control knobs",
- "slug": "control_knobs",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "microphone",
- "slug": "microphone",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "headphone",
- "slug": "headphone",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "radio",
- "slug": "radio",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "saxophone",
- "slug": "saxophone",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "accordion",
- "slug": "accordion",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "guitar",
- "slug": "guitar",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "musical keyboard",
- "slug": "musical_keyboard",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "trumpet",
- "slug": "trumpet",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "violin",
- "slug": "violin",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "banjo",
- "slug": "banjo",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "drum",
- "slug": "drum",
- "group": "Objects",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "long drum",
- "slug": "long_drum",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "maracas",
- "slug": "maracas",
- "group": "Objects",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "flute",
- "slug": "flute",
- "group": "Objects",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "mobile phone",
- "slug": "mobile_phone",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "mobile phone with arrow",
- "slug": "mobile_phone_with_arrow",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "telephone",
- "slug": "telephone",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "telephone receiver",
- "slug": "telephone_receiver",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pager",
- "slug": "pager",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "fax machine",
- "slug": "fax_machine",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "battery",
- "slug": "battery",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชซ": {
- "name": "low battery",
- "slug": "low_battery",
- "group": "Objects",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "electric plug",
- "slug": "electric_plug",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "laptop",
- "slug": "laptop",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ๏ธ": {
- "name": "desktop computer",
- "slug": "desktop_computer",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐จ๏ธ": {
- "name": "printer",
- "slug": "printer",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โจ๏ธ": {
- "name": "keyboard",
- "slug": "keyboard",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฑ๏ธ": {
- "name": "computer mouse",
- "slug": "computer_mouse",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฒ๏ธ": {
- "name": "trackball",
- "slug": "trackball",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "computer disk",
- "slug": "computer_disk",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "floppy disk",
- "slug": "floppy_disk",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "optical disk",
- "slug": "optical_disk",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "dvd",
- "slug": "dvd",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งฎ": {
- "name": "abacus",
- "slug": "abacus",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "movie camera",
- "slug": "movie_camera",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "film frames",
- "slug": "film_frames",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฝ๏ธ": {
- "name": "film projector",
- "slug": "film_projector",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "clapper board",
- "slug": "clapper_board",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "television",
- "slug": "television",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "camera",
- "slug": "camera",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "camera with flash",
- "slug": "camera_with_flash",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "video camera",
- "slug": "video_camera",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "videocassette",
- "slug": "videocassette",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "magnifying glass tilted left",
- "slug": "magnifying_glass_tilted_left",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "magnifying glass tilted right",
- "slug": "magnifying_glass_tilted_right",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏ๏ธ": {
- "name": "candle",
- "slug": "candle",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "light bulb",
- "slug": "light_bulb",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "flashlight",
- "slug": "flashlight",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "red paper lantern",
- "slug": "red_paper_lantern",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "diya lamp",
- "slug": "diya_lamp",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "notebook with decorative cover",
- "slug": "notebook_with_decorative_cover",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "closed book",
- "slug": "closed_book",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "open book",
- "slug": "open_book",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "green book",
- "slug": "green_book",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "blue book",
- "slug": "blue_book",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "orange book",
- "slug": "orange_book",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "books",
- "slug": "books",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "notebook",
- "slug": "notebook",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ledger",
- "slug": "ledger",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "page with curl",
- "slug": "page_with_curl",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "scroll",
- "slug": "scroll",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "page facing up",
- "slug": "page_facing_up",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "newspaper",
- "slug": "newspaper",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "rolled-up newspaper",
- "slug": "rolled_up_newspaper",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bookmark tabs",
- "slug": "bookmark_tabs",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bookmark",
- "slug": "bookmark",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ท๏ธ": {
- "name": "label",
- "slug": "label",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "money bag",
- "slug": "money_bag",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "coin",
- "slug": "coin",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "yen banknote",
- "slug": "yen_banknote",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "dollar banknote",
- "slug": "dollar_banknote",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "euro banknote",
- "slug": "euro_banknote",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "pound banknote",
- "slug": "pound_banknote",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "money with wings",
- "slug": "money_with_wings",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "credit card",
- "slug": "credit_card",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐งพ": {
- "name": "receipt",
- "slug": "receipt",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "chart increasing with yen",
- "slug": "chart_increasing_with_yen",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "envelope",
- "slug": "envelope",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "e-mail",
- "slug": "e_mail",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "incoming envelope",
- "slug": "incoming_envelope",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "envelope with arrow",
- "slug": "envelope_with_arrow",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "outbox tray",
- "slug": "outbox_tray",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "inbox tray",
- "slug": "inbox_tray",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "package",
- "slug": "package",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "closed mailbox with raised flag",
- "slug": "closed_mailbox_with_raised_flag",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "closed mailbox with lowered flag",
- "slug": "closed_mailbox_with_lowered_flag",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "open mailbox with raised flag",
- "slug": "open_mailbox_with_raised_flag",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "open mailbox with lowered flag",
- "slug": "open_mailbox_with_lowered_flag",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "postbox",
- "slug": "postbox",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ณ๏ธ": {
- "name": "ballot box with ballot",
- "slug": "ballot_box_with_ballot",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "pencil",
- "slug": "pencil",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "black nib",
- "slug": "black_nib",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "fountain pen",
- "slug": "fountain_pen",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "pen",
- "slug": "pen",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "paintbrush",
- "slug": "paintbrush",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "crayon",
- "slug": "crayon",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "memo",
- "slug": "memo",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "briefcase",
- "slug": "briefcase",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "file folder",
- "slug": "file_folder",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "open file folder",
- "slug": "open_file_folder",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "card index dividers",
- "slug": "card_index_dividers",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "calendar",
- "slug": "calendar",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "tear-off calendar",
- "slug": "tear_off_calendar",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "spiral notepad",
- "slug": "spiral_notepad",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "spiral calendar",
- "slug": "spiral_calendar",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "card index",
- "slug": "card_index",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "chart increasing",
- "slug": "chart_increasing",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "chart decreasing",
- "slug": "chart_decreasing",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bar chart",
- "slug": "bar_chart",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "clipboard",
- "slug": "clipboard",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pushpin",
- "slug": "pushpin",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "round pushpin",
- "slug": "round_pushpin",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "paperclip",
- "slug": "paperclip",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "linked paperclips",
- "slug": "linked_paperclips",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "straight ruler",
- "slug": "straight_ruler",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "triangular ruler",
- "slug": "triangular_ruler",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "scissors",
- "slug": "scissors",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "card file box",
- "slug": "card_file_box",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "file cabinet",
- "slug": "file_cabinet",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "wastebasket",
- "slug": "wastebasket",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐": {
- "name": "locked",
- "slug": "locked",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "unlocked",
- "slug": "unlocked",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "locked with pen",
- "slug": "locked_with_pen",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "locked with key",
- "slug": "locked_with_key",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "key",
- "slug": "key",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "old key",
- "slug": "old_key",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "hammer",
- "slug": "hammer",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "axe",
- "slug": "axe",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "pick",
- "slug": "pick",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "hammer and pick",
- "slug": "hammer_and_pick",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ ๏ธ": {
- "name": "hammer and wrench",
- "slug": "hammer_and_wrench",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ก๏ธ": {
- "name": "dagger",
- "slug": "dagger",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "crossed swords",
- "slug": "crossed_swords",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "bomb",
- "slug": "bomb",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "boomerang",
- "slug": "boomerang",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "bow and arrow",
- "slug": "bow_and_arrow",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ก๏ธ": {
- "name": "shield",
- "slug": "shield",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "carpentry saw",
- "slug": "carpentry_saw",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "wrench",
- "slug": "wrench",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "screwdriver",
- "slug": "screwdriver",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "nut and bolt",
- "slug": "nut_and_bolt",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "gear",
- "slug": "gear",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "clamp",
- "slug": "clamp",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "balance scale",
- "slug": "balance_scale",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฆฏ": {
- "name": "white cane",
- "slug": "white_cane",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "link",
- "slug": "link",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธโ๐ฅ": {
- "name": "broken chain",
- "slug": "broken_chain",
- "group": "Objects",
- "emoji_version": "15.1",
- "unicode_version": "15.1",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "chains",
- "slug": "chains",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "hook",
- "slug": "hook",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐งฐ": {
- "name": "toolbox",
- "slug": "toolbox",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งฒ": {
- "name": "magnet",
- "slug": "magnet",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "ladder",
- "slug": "ladder",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "alembic",
- "slug": "alembic",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐งช": {
- "name": "test tube",
- "slug": "test_tube",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งซ": {
- "name": "petri dish",
- "slug": "petri_dish",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งฌ": {
- "name": "dna",
- "slug": "dna",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "microscope",
- "slug": "microscope",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "telescope",
- "slug": "telescope",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "satellite antenna",
- "slug": "satellite_antenna",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "syringe",
- "slug": "syringe",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉธ": {
- "name": "drop of blood",
- "slug": "drop_of_blood",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "pill",
- "slug": "pill",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉน": {
- "name": "adhesive bandage",
- "slug": "adhesive_bandage",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฉผ": {
- "name": "crutch",
- "slug": "crutch",
- "group": "Objects",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ฉบ": {
- "name": "stethoscope",
- "slug": "stethoscope",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฉป": {
- "name": "x-ray",
- "slug": "x_ray",
- "group": "Objects",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "door",
- "slug": "door",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "elevator",
- "slug": "elevator",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "mirror",
- "slug": "mirror",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "window",
- "slug": "window",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "bed",
- "slug": "bed",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "couch and lamp",
- "slug": "couch_and_lamp",
- "group": "Objects",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "chair",
- "slug": "chair",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "toilet",
- "slug": "toilet",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช ": {
- "name": "plunger",
- "slug": "plunger",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "shower",
- "slug": "shower",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bathtub",
- "slug": "bathtub",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ชค": {
- "name": "mouse trap",
- "slug": "mouse_trap",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "razor",
- "slug": "razor",
- "group": "Objects",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐งด": {
- "name": "lotion bottle",
- "slug": "lotion_bottle",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งท": {
- "name": "safety pin",
- "slug": "safety_pin",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งน": {
- "name": "broom",
- "slug": "broom",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งบ": {
- "name": "basket",
- "slug": "basket",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งป": {
- "name": "roll of paper",
- "slug": "roll_of_paper",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ชฃ": {
- "name": "bucket",
- "slug": "bucket",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐งผ": {
- "name": "soap",
- "slug": "soap",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ซง": {
- "name": "bubbles",
- "slug": "bubbles",
- "group": "Objects",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ชฅ": {
- "name": "toothbrush",
- "slug": "toothbrush",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐งฝ": {
- "name": "sponge",
- "slug": "sponge",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐งฏ": {
- "name": "fire extinguisher",
- "slug": "fire_extinguisher",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "shopping cart",
- "slug": "shopping_cart",
- "group": "Objects",
- "emoji_version": "3.0",
- "unicode_version": "3.0",
- "skin_tone_support": false
- },
- "๐ฌ": {
- "name": "cigarette",
- "slug": "cigarette",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฐ๏ธ": {
- "name": "coffin",
- "slug": "coffin",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ชฆ": {
- "name": "headstone",
- "slug": "headstone",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "โฑ๏ธ": {
- "name": "funeral urn",
- "slug": "funeral_urn",
- "group": "Objects",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐งฟ": {
- "name": "nazar amulet",
- "slug": "nazar_amulet",
- "group": "Objects",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ชฌ": {
- "name": "hamsa",
- "slug": "hamsa",
- "group": "Objects",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ฟ": {
- "name": "moai",
- "slug": "moai",
- "group": "Objects",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชง": {
- "name": "placard",
- "slug": "placard",
- "group": "Objects",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ชช": {
- "name": "identification card",
- "slug": "identification_card",
- "group": "Objects",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "ATM sign",
- "slug": "atm_sign",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฎ": {
- "name": "litter in bin sign",
- "slug": "litter_in_bin_sign",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "potable water",
- "slug": "potable_water",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โฟ": {
- "name": "wheelchair symbol",
- "slug": "wheelchair_symbol",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "menโs room",
- "slug": "men_s_room",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "womenโs room",
- "slug": "women_s_room",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "restroom",
- "slug": "restroom",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "baby symbol",
- "slug": "baby_symbol",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐พ": {
- "name": "water closet",
- "slug": "water_closet",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "passport control",
- "slug": "passport_control",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "customs",
- "slug": "customs",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "baggage claim",
- "slug": "baggage_claim",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "left luggage",
- "slug": "left_luggage",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โ ๏ธ": {
- "name": "warning",
- "slug": "warning",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "children crossing",
- "slug": "children_crossing",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โ": {
- "name": "no entry",
- "slug": "no_entry",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "prohibited",
- "slug": "prohibited",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "no bicycles",
- "slug": "no_bicycles",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ญ": {
- "name": "no smoking",
- "slug": "no_smoking",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "no littering",
- "slug": "no_littering",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "non-potable water",
- "slug": "non_potable_water",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "no pedestrians",
- "slug": "no_pedestrians",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "no mobile phones",
- "slug": "no_mobile_phones",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "no one under eighteen",
- "slug": "no_one_under_eighteen",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โข๏ธ": {
- "name": "radioactive",
- "slug": "radioactive",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โฃ๏ธ": {
- "name": "biohazard",
- "slug": "biohazard",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โฌ๏ธ": {
- "name": "up arrow",
- "slug": "up_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "up-right arrow",
- "slug": "up_right_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โก๏ธ": {
- "name": "right arrow",
- "slug": "right_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "down-right arrow",
- "slug": "down_right_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฌ๏ธ": {
- "name": "down arrow",
- "slug": "down_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "down-left arrow",
- "slug": "down_left_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฌ
๏ธ": {
- "name": "left arrow",
- "slug": "left_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "up-left arrow",
- "slug": "up_left_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "up-down arrow",
- "slug": "up_down_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "left-right arrow",
- "slug": "left_right_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฉ๏ธ": {
- "name": "right arrow curving left",
- "slug": "right_arrow_curving_left",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โช๏ธ": {
- "name": "left arrow curving right",
- "slug": "left_arrow_curving_right",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โคด๏ธ": {
- "name": "right arrow curving up",
- "slug": "right_arrow_curving_up",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โคต๏ธ": {
- "name": "right arrow curving down",
- "slug": "right_arrow_curving_down",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "clockwise vertical arrows",
- "slug": "clockwise_vertical_arrows",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "counterclockwise arrows button",
- "slug": "counterclockwise_arrows_button",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "BACK arrow",
- "slug": "back_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "END arrow",
- "slug": "end_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ON! arrow",
- "slug": "on_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "SOON arrow",
- "slug": "soon_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "TOP arrow",
- "slug": "top_arrow",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "place of worship",
- "slug": "place_of_worship",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "atom symbol",
- "slug": "atom_symbol",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "om",
- "slug": "om",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โก๏ธ": {
- "name": "star of David",
- "slug": "star_of_david",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โธ๏ธ": {
- "name": "wheel of dharma",
- "slug": "wheel_of_dharma",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โฏ๏ธ": {
- "name": "yin yang",
- "slug": "yin_yang",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "latin cross",
- "slug": "latin_cross",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โฆ๏ธ": {
- "name": "orthodox cross",
- "slug": "orthodox_cross",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โช๏ธ": {
- "name": "star and crescent",
- "slug": "star_and_crescent",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โฎ๏ธ": {
- "name": "peace symbol",
- "slug": "peace_symbol",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "menorah",
- "slug": "menorah",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "dotted six-pointed star",
- "slug": "dotted_six_pointed_star",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ชฏ": {
- "name": "khanda",
- "slug": "khanda",
- "group": "Symbols",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Aries",
- "slug": "aries",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Taurus",
- "slug": "taurus",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Gemini",
- "slug": "gemini",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Cancer",
- "slug": "cancer",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Leo",
- "slug": "leo",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Virgo",
- "slug": "virgo",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Libra",
- "slug": "libra",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Scorpio",
- "slug": "scorpio",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Sagittarius",
- "slug": "sagittarius",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Capricorn",
- "slug": "capricorn",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Aquarius",
- "slug": "aquarius",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Pisces",
- "slug": "pisces",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "Ophiuchus",
- "slug": "ophiuchus",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "shuffle tracks button",
- "slug": "shuffle_tracks_button",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "repeat button",
- "slug": "repeat_button",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "repeat single button",
- "slug": "repeat_single_button",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โถ๏ธ": {
- "name": "play button",
- "slug": "play_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฉ": {
- "name": "fast-forward button",
- "slug": "fast_forward_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โญ๏ธ": {
- "name": "next track button",
- "slug": "next_track_button",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โฏ๏ธ": {
- "name": "play or pause button",
- "slug": "play_or_pause_button",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "reverse button",
- "slug": "reverse_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โช": {
- "name": "fast reverse button",
- "slug": "fast_reverse_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฎ๏ธ": {
- "name": "last track button",
- "slug": "last_track_button",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ผ": {
- "name": "upwards button",
- "slug": "upwards_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โซ": {
- "name": "fast up button",
- "slug": "fast_up_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฝ": {
- "name": "downwards button",
- "slug": "downwards_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฌ": {
- "name": "fast down button",
- "slug": "fast_down_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โธ๏ธ": {
- "name": "pause button",
- "slug": "pause_button",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โน๏ธ": {
- "name": "stop button",
- "slug": "stop_button",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โบ๏ธ": {
- "name": "record button",
- "slug": "record_button",
- "group": "Symbols",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "eject button",
- "slug": "eject_button",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "cinema",
- "slug": "cinema",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
": {
- "name": "dim button",
- "slug": "dim_button",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐": {
- "name": "bright button",
- "slug": "bright_button",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "antenna bars",
- "slug": "antenna_bars",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "wireless",
- "slug": "wireless",
- "group": "Symbols",
- "emoji_version": "15.0",
- "unicode_version": "15.0",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "vibration mode",
- "slug": "vibration_mode",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "mobile phone off",
- "slug": "mobile_phone_off",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "female sign",
- "slug": "female_sign",
- "group": "Symbols",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "male sign",
- "slug": "male_sign",
- "group": "Symbols",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "โง๏ธ": {
- "name": "transgender symbol",
- "slug": "transgender_symbol",
- "group": "Symbols",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "multiply",
- "slug": "multiply",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "plus",
- "slug": "plus",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "minus",
- "slug": "minus",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "divide",
- "slug": "divide",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "heavy equals sign",
- "slug": "heavy_equals_sign",
- "group": "Symbols",
- "emoji_version": "14.0",
- "unicode_version": "14.0",
- "skin_tone_support": false
- },
- "โพ๏ธ": {
- "name": "infinity",
- "slug": "infinity",
- "group": "Symbols",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "โผ๏ธ": {
- "name": "double exclamation mark",
- "slug": "double_exclamation_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "exclamation question mark",
- "slug": "exclamation_question_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "red question mark",
- "slug": "red_question_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "white question mark",
- "slug": "white_question_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "white exclamation mark",
- "slug": "white_exclamation_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "red exclamation mark",
- "slug": "red_exclamation_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "ใฐ๏ธ": {
- "name": "wavy dash",
- "slug": "wavy_dash",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "currency exchange",
- "slug": "currency_exchange",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "heavy dollar sign",
- "slug": "heavy_dollar_sign",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "medical symbol",
- "slug": "medical_symbol",
- "group": "Symbols",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "โป๏ธ": {
- "name": "recycling symbol",
- "slug": "recycling_symbol",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "fleur-de-lis",
- "slug": "fleur_de_lis",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ฑ": {
- "name": "trident emblem",
- "slug": "trident_emblem",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "name badge",
- "slug": "name_badge",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ": {
- "name": "Japanese symbol for beginner",
- "slug": "japanese_symbol_for_beginner",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โญ": {
- "name": "hollow red circle",
- "slug": "hollow_red_circle",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ
": {
- "name": "check mark button",
- "slug": "check_mark_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "check box with check",
- "slug": "check_box_with_check",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "check mark",
- "slug": "check_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "cross mark",
- "slug": "cross_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ": {
- "name": "cross mark button",
- "slug": "cross_mark_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฐ": {
- "name": "curly loop",
- "slug": "curly_loop",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฟ": {
- "name": "double curly loop",
- "slug": "double_curly_loop",
- "group": "Symbols",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "ใฝ๏ธ": {
- "name": "part alternation mark",
- "slug": "part_alternation_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โณ๏ธ": {
- "name": "eight-spoked asterisk",
- "slug": "eight_spoked_asterisk",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โด๏ธ": {
- "name": "eight-pointed star",
- "slug": "eight_pointed_star",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "sparkle",
- "slug": "sparkle",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "ยฉ๏ธ": {
- "name": "copyright",
- "slug": "copyright",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "ยฎ๏ธ": {
- "name": "registered",
- "slug": "registered",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โข๏ธ": {
- "name": "trade mark",
- "slug": "trade_mark",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "#๏ธโฃ": {
- "name": "keycap #",
- "slug": "keycap_number_sign",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "*๏ธโฃ": {
- "name": "keycap *",
- "slug": "keycap_asterisk",
- "group": "Symbols",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "0๏ธโฃ": {
- "name": "keycap 0",
- "slug": "keycap_0",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "1๏ธโฃ": {
- "name": "keycap 1",
- "slug": "keycap_1",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "2๏ธโฃ": {
- "name": "keycap 2",
- "slug": "keycap_2",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "3๏ธโฃ": {
- "name": "keycap 3",
- "slug": "keycap_3",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "4๏ธโฃ": {
- "name": "keycap 4",
- "slug": "keycap_4",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "5๏ธโฃ": {
- "name": "keycap 5",
- "slug": "keycap_5",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "6๏ธโฃ": {
- "name": "keycap 6",
- "slug": "keycap_6",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "7๏ธโฃ": {
- "name": "keycap 7",
- "slug": "keycap_7",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "8๏ธโฃ": {
- "name": "keycap 8",
- "slug": "keycap_8",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "9๏ธโฃ": {
- "name": "keycap 9",
- "slug": "keycap_9",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "keycap 10",
- "slug": "keycap_10",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "input latin uppercase",
- "slug": "input_latin_uppercase",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "input latin lowercase",
- "slug": "input_latin_lowercase",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "input numbers",
- "slug": "input_numbers",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "input symbols",
- "slug": "input_symbols",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "input latin letters",
- "slug": "input_latin_letters",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
ฐ๏ธ": {
- "name": "A button (blood type)",
- "slug": "a_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "AB button (blood type)",
- "slug": "ab_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
ฑ๏ธ": {
- "name": "B button (blood type)",
- "slug": "b_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "CL button",
- "slug": "cl_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "COOL button",
- "slug": "cool_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "FREE button",
- "slug": "free_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โน๏ธ": {
- "name": "information",
- "slug": "information",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "ID button",
- "slug": "id_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โ๏ธ": {
- "name": "circled M",
- "slug": "circled_m",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "NEW button",
- "slug": "new_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "NG button",
- "slug": "ng_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
พ๏ธ": {
- "name": "O button (blood type)",
- "slug": "o_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "OK button",
- "slug": "ok_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐
ฟ๏ธ": {
- "name": "P button",
- "slug": "p_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "SOS button",
- "slug": "sos_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "UP! button",
- "slug": "up_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "VS button",
- "slug": "vs_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "Japanese โhereโ button",
- "slug": "japanese_here_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐๏ธ": {
- "name": "Japanese โservice chargeโ button",
- "slug": "japanese_service_charge_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ท๏ธ": {
- "name": "Japanese โmonthly amountโ button",
- "slug": "japanese_monthly_amount_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "Japanese โnot free of chargeโ button",
- "slug": "japanese_not_free_of_charge_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏ": {
- "name": "Japanese โreservedโ button",
- "slug": "japanese_reserved_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "Japanese โbargainโ button",
- "slug": "japanese_bargain_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "Japanese โdiscountโ button",
- "slug": "japanese_discount_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "Japanese โfree of chargeโ button",
- "slug": "japanese_free_of_charge_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "Japanese โprohibitedโ button",
- "slug": "japanese_prohibited_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "Japanese โacceptableโ button",
- "slug": "japanese_acceptable_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "Japanese โapplicationโ button",
- "slug": "japanese_application_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "Japanese โpassing gradeโ button",
- "slug": "japanese_passing_grade_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "Japanese โvacancyโ button",
- "slug": "japanese_vacancy_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "ใ๏ธ": {
- "name": "Japanese โcongratulationsโ button",
- "slug": "japanese_congratulations_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "ใ๏ธ": {
- "name": "Japanese โsecretโ button",
- "slug": "japanese_secret_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "Japanese โopen for businessโ button",
- "slug": "japanese_open_for_business_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "Japanese โno vacancyโ button",
- "slug": "japanese_no_vacancy_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "red circle",
- "slug": "red_circle",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "orange circle",
- "slug": "orange_circle",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ก": {
- "name": "yellow circle",
- "slug": "yellow_circle",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ข": {
- "name": "green circle",
- "slug": "green_circle",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ต": {
- "name": "blue circle",
- "slug": "blue_circle",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฃ": {
- "name": "purple circle",
- "slug": "purple_circle",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ค": {
- "name": "brown circle",
- "slug": "brown_circle",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "โซ": {
- "name": "black circle",
- "slug": "black_circle",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โช": {
- "name": "white circle",
- "slug": "white_circle",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฅ": {
- "name": "red square",
- "slug": "red_square",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ง": {
- "name": "orange square",
- "slug": "orange_square",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐จ": {
- "name": "yellow square",
- "slug": "yellow_square",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "green square",
- "slug": "green_square",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ฆ": {
- "name": "blue square",
- "slug": "blue_square",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ช": {
- "name": "purple square",
- "slug": "purple_square",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "๐ซ": {
- "name": "brown square",
- "slug": "brown_square",
- "group": "Symbols",
- "emoji_version": "12.0",
- "unicode_version": "12.0",
- "skin_tone_support": false
- },
- "โฌ": {
- "name": "black large square",
- "slug": "black_large_square",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฌ": {
- "name": "white large square",
- "slug": "white_large_square",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โผ๏ธ": {
- "name": "black medium square",
- "slug": "black_medium_square",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โป๏ธ": {
- "name": "white medium square",
- "slug": "white_medium_square",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โพ": {
- "name": "black medium-small square",
- "slug": "black_medium_small_square",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โฝ": {
- "name": "white medium-small square",
- "slug": "white_medium_small_square",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โช๏ธ": {
- "name": "black small square",
- "slug": "black_small_square",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "โซ๏ธ": {
- "name": "white small square",
- "slug": "white_small_square",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ถ": {
- "name": "large orange diamond",
- "slug": "large_orange_diamond",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ท": {
- "name": "large blue diamond",
- "slug": "large_blue_diamond",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ธ": {
- "name": "small orange diamond",
- "slug": "small_orange_diamond",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐น": {
- "name": "small blue diamond",
- "slug": "small_blue_diamond",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ": {
- "name": "red triangle pointed up",
- "slug": "red_triangle_pointed_up",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ป": {
- "name": "red triangle pointed down",
- "slug": "red_triangle_pointed_down",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ ": {
- "name": "diamond with a dot",
- "slug": "diamond_with_a_dot",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "radio button",
- "slug": "radio_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ณ": {
- "name": "white square button",
- "slug": "white_square_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฒ": {
- "name": "black square button",
- "slug": "black_square_button",
- "group": "Symbols",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "chequered flag",
- "slug": "chequered_flag",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉ": {
- "name": "triangular flag",
- "slug": "triangular_flag",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐": {
- "name": "crossed flags",
- "slug": "crossed_flags",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ด": {
- "name": "black flag",
- "slug": "black_flag",
- "group": "Flags",
- "emoji_version": "1.0",
- "unicode_version": "1.0",
- "skin_tone_support": false
- },
- "๐ณ๏ธ": {
- "name": "white flag",
- "slug": "white_flag",
- "group": "Flags",
- "emoji_version": "0.7",
- "unicode_version": "0.7",
- "skin_tone_support": false
- },
- "๐ณ๏ธโ๐": {
- "name": "rainbow flag",
- "slug": "rainbow_flag",
- "group": "Flags",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐ณ๏ธโโง๏ธ": {
- "name": "transgender flag",
- "slug": "transgender_flag",
- "group": "Flags",
- "emoji_version": "13.0",
- "unicode_version": "13.0",
- "skin_tone_support": false
- },
- "๐ดโโ ๏ธ": {
- "name": "pirate flag",
- "slug": "pirate_flag",
- "group": "Flags",
- "emoji_version": "11.0",
- "unicode_version": "11.0",
- "skin_tone_support": false
- },
- "๐ฆ๐จ": {
- "name": "flag Ascension Island",
- "slug": "flag_ascension_island",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ฉ": {
- "name": "flag Andorra",
- "slug": "flag_andorra",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ช": {
- "name": "flag United Arab Emirates",
- "slug": "flag_united_arab_emirates",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ซ": {
- "name": "flag Afghanistan",
- "slug": "flag_afghanistan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ฌ": {
- "name": "flag Antigua & Barbuda",
- "slug": "flag_antigua_barbuda",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ฎ": {
- "name": "flag Anguilla",
- "slug": "flag_anguilla",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ฑ": {
- "name": "flag Albania",
- "slug": "flag_albania",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ฒ": {
- "name": "flag Armenia",
- "slug": "flag_armenia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ด": {
- "name": "flag Angola",
- "slug": "flag_angola",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ถ": {
- "name": "flag Antarctica",
- "slug": "flag_antarctica",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ท": {
- "name": "flag Argentina",
- "slug": "flag_argentina",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ธ": {
- "name": "flag American Samoa",
- "slug": "flag_american_samoa",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐น": {
- "name": "flag Austria",
- "slug": "flag_austria",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐บ": {
- "name": "flag Australia",
- "slug": "flag_australia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ผ": {
- "name": "flag Aruba",
- "slug": "flag_aruba",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ฝ": {
- "name": "flag ร
land Islands",
- "slug": "flag_aland_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฆ๐ฟ": {
- "name": "flag Azerbaijan",
- "slug": "flag_azerbaijan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ฆ": {
- "name": "flag Bosnia & Herzegovina",
- "slug": "flag_bosnia_herzegovina",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ง": {
- "name": "flag Barbados",
- "slug": "flag_barbados",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ฉ": {
- "name": "flag Bangladesh",
- "slug": "flag_bangladesh",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ช": {
- "name": "flag Belgium",
- "slug": "flag_belgium",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ซ": {
- "name": "flag Burkina Faso",
- "slug": "flag_burkina_faso",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ฌ": {
- "name": "flag Bulgaria",
- "slug": "flag_bulgaria",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ญ": {
- "name": "flag Bahrain",
- "slug": "flag_bahrain",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ฎ": {
- "name": "flag Burundi",
- "slug": "flag_burundi",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ฏ": {
- "name": "flag Benin",
- "slug": "flag_benin",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ฑ": {
- "name": "flag St. Barthรฉlemy",
- "slug": "flag_st_barthelemy",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ฒ": {
- "name": "flag Bermuda",
- "slug": "flag_bermuda",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ณ": {
- "name": "flag Brunei",
- "slug": "flag_brunei",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ด": {
- "name": "flag Bolivia",
- "slug": "flag_bolivia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ถ": {
- "name": "flag Caribbean Netherlands",
- "slug": "flag_caribbean_netherlands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ท": {
- "name": "flag Brazil",
- "slug": "flag_brazil",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ธ": {
- "name": "flag Bahamas",
- "slug": "flag_bahamas",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐น": {
- "name": "flag Bhutan",
- "slug": "flag_bhutan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ป": {
- "name": "flag Bouvet Island",
- "slug": "flag_bouvet_island",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ผ": {
- "name": "flag Botswana",
- "slug": "flag_botswana",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐พ": {
- "name": "flag Belarus",
- "slug": "flag_belarus",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ง๐ฟ": {
- "name": "flag Belize",
- "slug": "flag_belize",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฆ": {
- "name": "flag Canada",
- "slug": "flag_canada",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐จ": {
- "name": "flag Cocos (Keeling) Islands",
- "slug": "flag_cocos_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฉ": {
- "name": "flag Congo - Kinshasa",
- "slug": "flag_congo_kinshasa",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ซ": {
- "name": "flag Central African Republic",
- "slug": "flag_central_african_republic",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฌ": {
- "name": "flag Congo - Brazzaville",
- "slug": "flag_congo_brazzaville",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ญ": {
- "name": "flag Switzerland",
- "slug": "flag_switzerland",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฎ": {
- "name": "flag Cรดte dโIvoire",
- "slug": "flag_cote_d_ivoire",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฐ": {
- "name": "flag Cook Islands",
- "slug": "flag_cook_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฑ": {
- "name": "flag Chile",
- "slug": "flag_chile",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฒ": {
- "name": "flag Cameroon",
- "slug": "flag_cameroon",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ณ": {
- "name": "flag China",
- "slug": "flag_china",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐จ๐ด": {
- "name": "flag Colombia",
- "slug": "flag_colombia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ต": {
- "name": "flag Clipperton Island",
- "slug": "flag_clipperton_island",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ท": {
- "name": "flag Costa Rica",
- "slug": "flag_costa_rica",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐บ": {
- "name": "flag Cuba",
- "slug": "flag_cuba",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ป": {
- "name": "flag Cape Verde",
- "slug": "flag_cape_verde",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ผ": {
- "name": "flag Curaรงao",
- "slug": "flag_curacao",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฝ": {
- "name": "flag Christmas Island",
- "slug": "flag_christmas_island",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐พ": {
- "name": "flag Cyprus",
- "slug": "flag_cyprus",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐จ๐ฟ": {
- "name": "flag Czechia",
- "slug": "flag_czechia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉ๐ช": {
- "name": "flag Germany",
- "slug": "flag_germany",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฉ๐ฌ": {
- "name": "flag Diego Garcia",
- "slug": "flag_diego_garcia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉ๐ฏ": {
- "name": "flag Djibouti",
- "slug": "flag_djibouti",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉ๐ฐ": {
- "name": "flag Denmark",
- "slug": "flag_denmark",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉ๐ฒ": {
- "name": "flag Dominica",
- "slug": "flag_dominica",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉ๐ด": {
- "name": "flag Dominican Republic",
- "slug": "flag_dominican_republic",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฉ๐ฟ": {
- "name": "flag Algeria",
- "slug": "flag_algeria",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ช๐ฆ": {
- "name": "flag Ceuta & Melilla",
- "slug": "flag_ceuta_melilla",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ช๐จ": {
- "name": "flag Ecuador",
- "slug": "flag_ecuador",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ช๐ช": {
- "name": "flag Estonia",
- "slug": "flag_estonia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ช๐ฌ": {
- "name": "flag Egypt",
- "slug": "flag_egypt",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ช๐ญ": {
- "name": "flag Western Sahara",
- "slug": "flag_western_sahara",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ช๐ท": {
- "name": "flag Eritrea",
- "slug": "flag_eritrea",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ช๐ธ": {
- "name": "flag Spain",
- "slug": "flag_spain",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ช๐น": {
- "name": "flag Ethiopia",
- "slug": "flag_ethiopia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ช๐บ": {
- "name": "flag European Union",
- "slug": "flag_european_union",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ซ๐ฎ": {
- "name": "flag Finland",
- "slug": "flag_finland",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ซ๐ฏ": {
- "name": "flag Fiji",
- "slug": "flag_fiji",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ซ๐ฐ": {
- "name": "flag Falkland Islands",
- "slug": "flag_falkland_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ซ๐ฒ": {
- "name": "flag Micronesia",
- "slug": "flag_micronesia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ซ๐ด": {
- "name": "flag Faroe Islands",
- "slug": "flag_faroe_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ซ๐ท": {
- "name": "flag France",
- "slug": "flag_france",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฌ๐ฆ": {
- "name": "flag Gabon",
- "slug": "flag_gabon",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ง": {
- "name": "flag United Kingdom",
- "slug": "flag_united_kingdom",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฌ๐ฉ": {
- "name": "flag Grenada",
- "slug": "flag_grenada",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ช": {
- "name": "flag Georgia",
- "slug": "flag_georgia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ซ": {
- "name": "flag French Guiana",
- "slug": "flag_french_guiana",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ฌ": {
- "name": "flag Guernsey",
- "slug": "flag_guernsey",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ญ": {
- "name": "flag Ghana",
- "slug": "flag_ghana",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ฎ": {
- "name": "flag Gibraltar",
- "slug": "flag_gibraltar",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ฑ": {
- "name": "flag Greenland",
- "slug": "flag_greenland",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ฒ": {
- "name": "flag Gambia",
- "slug": "flag_gambia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ณ": {
- "name": "flag Guinea",
- "slug": "flag_guinea",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ต": {
- "name": "flag Guadeloupe",
- "slug": "flag_guadeloupe",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ถ": {
- "name": "flag Equatorial Guinea",
- "slug": "flag_equatorial_guinea",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ท": {
- "name": "flag Greece",
- "slug": "flag_greece",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ธ": {
- "name": "flag South Georgia & South Sandwich Islands",
- "slug": "flag_south_georgia_south_sandwich_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐น": {
- "name": "flag Guatemala",
- "slug": "flag_guatemala",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐บ": {
- "name": "flag Guam",
- "slug": "flag_guam",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐ผ": {
- "name": "flag Guinea-Bissau",
- "slug": "flag_guinea_bissau",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฌ๐พ": {
- "name": "flag Guyana",
- "slug": "flag_guyana",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ญ๐ฐ": {
- "name": "flag Hong Kong SAR China",
- "slug": "flag_hong_kong_sar_china",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ญ๐ฒ": {
- "name": "flag Heard & McDonald Islands",
- "slug": "flag_heard_mcdonald_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ญ๐ณ": {
- "name": "flag Honduras",
- "slug": "flag_honduras",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ญ๐ท": {
- "name": "flag Croatia",
- "slug": "flag_croatia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ญ๐น": {
- "name": "flag Haiti",
- "slug": "flag_haiti",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ญ๐บ": {
- "name": "flag Hungary",
- "slug": "flag_hungary",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐จ": {
- "name": "flag Canary Islands",
- "slug": "flag_canary_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ฉ": {
- "name": "flag Indonesia",
- "slug": "flag_indonesia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ช": {
- "name": "flag Ireland",
- "slug": "flag_ireland",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ฑ": {
- "name": "flag Israel",
- "slug": "flag_israel",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ฒ": {
- "name": "flag Isle of Man",
- "slug": "flag_isle_of_man",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ณ": {
- "name": "flag India",
- "slug": "flag_india",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ด": {
- "name": "flag British Indian Ocean Territory",
- "slug": "flag_british_indian_ocean_territory",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ถ": {
- "name": "flag Iraq",
- "slug": "flag_iraq",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ท": {
- "name": "flag Iran",
- "slug": "flag_iran",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐ธ": {
- "name": "flag Iceland",
- "slug": "flag_iceland",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฎ๐น": {
- "name": "flag Italy",
- "slug": "flag_italy",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฏ๐ช": {
- "name": "flag Jersey",
- "slug": "flag_jersey",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฏ๐ฒ": {
- "name": "flag Jamaica",
- "slug": "flag_jamaica",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฏ๐ด": {
- "name": "flag Jordan",
- "slug": "flag_jordan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฏ๐ต": {
- "name": "flag Japan",
- "slug": "flag_japan",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ๐ช": {
- "name": "flag Kenya",
- "slug": "flag_kenya",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐ฌ": {
- "name": "flag Kyrgyzstan",
- "slug": "flag_kyrgyzstan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐ญ": {
- "name": "flag Cambodia",
- "slug": "flag_cambodia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐ฎ": {
- "name": "flag Kiribati",
- "slug": "flag_kiribati",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐ฒ": {
- "name": "flag Comoros",
- "slug": "flag_comoros",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐ณ": {
- "name": "flag St. Kitts & Nevis",
- "slug": "flag_st_kitts_nevis",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐ต": {
- "name": "flag North Korea",
- "slug": "flag_north_korea",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐ท": {
- "name": "flag South Korea",
- "slug": "flag_south_korea",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ฐ๐ผ": {
- "name": "flag Kuwait",
- "slug": "flag_kuwait",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐พ": {
- "name": "flag Cayman Islands",
- "slug": "flag_cayman_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฐ๐ฟ": {
- "name": "flag Kazakhstan",
- "slug": "flag_kazakhstan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐ฆ": {
- "name": "flag Laos",
- "slug": "flag_laos",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐ง": {
- "name": "flag Lebanon",
- "slug": "flag_lebanon",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐จ": {
- "name": "flag St. Lucia",
- "slug": "flag_st_lucia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐ฎ": {
- "name": "flag Liechtenstein",
- "slug": "flag_liechtenstein",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐ฐ": {
- "name": "flag Sri Lanka",
- "slug": "flag_sri_lanka",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐ท": {
- "name": "flag Liberia",
- "slug": "flag_liberia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐ธ": {
- "name": "flag Lesotho",
- "slug": "flag_lesotho",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐น": {
- "name": "flag Lithuania",
- "slug": "flag_lithuania",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐บ": {
- "name": "flag Luxembourg",
- "slug": "flag_luxembourg",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐ป": {
- "name": "flag Latvia",
- "slug": "flag_latvia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฑ๐พ": {
- "name": "flag Libya",
- "slug": "flag_libya",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ฆ": {
- "name": "flag Morocco",
- "slug": "flag_morocco",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐จ": {
- "name": "flag Monaco",
- "slug": "flag_monaco",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ฉ": {
- "name": "flag Moldova",
- "slug": "flag_moldova",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ช": {
- "name": "flag Montenegro",
- "slug": "flag_montenegro",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ซ": {
- "name": "flag St. Martin",
- "slug": "flag_st_martin",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ฌ": {
- "name": "flag Madagascar",
- "slug": "flag_madagascar",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ญ": {
- "name": "flag Marshall Islands",
- "slug": "flag_marshall_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ฐ": {
- "name": "flag North Macedonia",
- "slug": "flag_north_macedonia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ฑ": {
- "name": "flag Mali",
- "slug": "flag_mali",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ฒ": {
- "name": "flag Myanmar (Burma)",
- "slug": "flag_myanmar",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ณ": {
- "name": "flag Mongolia",
- "slug": "flag_mongolia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ด": {
- "name": "flag Macao SAR China",
- "slug": "flag_macao_sar_china",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ต": {
- "name": "flag Northern Mariana Islands",
- "slug": "flag_northern_mariana_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ถ": {
- "name": "flag Martinique",
- "slug": "flag_martinique",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ท": {
- "name": "flag Mauritania",
- "slug": "flag_mauritania",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ธ": {
- "name": "flag Montserrat",
- "slug": "flag_montserrat",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐น": {
- "name": "flag Malta",
- "slug": "flag_malta",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐บ": {
- "name": "flag Mauritius",
- "slug": "flag_mauritius",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ป": {
- "name": "flag Maldives",
- "slug": "flag_maldives",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ผ": {
- "name": "flag Malawi",
- "slug": "flag_malawi",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ฝ": {
- "name": "flag Mexico",
- "slug": "flag_mexico",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐พ": {
- "name": "flag Malaysia",
- "slug": "flag_malaysia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฒ๐ฟ": {
- "name": "flag Mozambique",
- "slug": "flag_mozambique",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ฆ": {
- "name": "flag Namibia",
- "slug": "flag_namibia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐จ": {
- "name": "flag New Caledonia",
- "slug": "flag_new_caledonia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ช": {
- "name": "flag Niger",
- "slug": "flag_niger",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ซ": {
- "name": "flag Norfolk Island",
- "slug": "flag_norfolk_island",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ฌ": {
- "name": "flag Nigeria",
- "slug": "flag_nigeria",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ฎ": {
- "name": "flag Nicaragua",
- "slug": "flag_nicaragua",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ฑ": {
- "name": "flag Netherlands",
- "slug": "flag_netherlands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ด": {
- "name": "flag Norway",
- "slug": "flag_norway",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ต": {
- "name": "flag Nepal",
- "slug": "flag_nepal",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ท": {
- "name": "flag Nauru",
- "slug": "flag_nauru",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐บ": {
- "name": "flag Niue",
- "slug": "flag_niue",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ณ๐ฟ": {
- "name": "flag New Zealand",
- "slug": "flag_new_zealand",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ด๐ฒ": {
- "name": "flag Oman",
- "slug": "flag_oman",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ฆ": {
- "name": "flag Panama",
- "slug": "flag_panama",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ช": {
- "name": "flag Peru",
- "slug": "flag_peru",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ซ": {
- "name": "flag French Polynesia",
- "slug": "flag_french_polynesia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ฌ": {
- "name": "flag Papua New Guinea",
- "slug": "flag_papua_new_guinea",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ญ": {
- "name": "flag Philippines",
- "slug": "flag_philippines",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ฐ": {
- "name": "flag Pakistan",
- "slug": "flag_pakistan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ฑ": {
- "name": "flag Poland",
- "slug": "flag_poland",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ฒ": {
- "name": "flag St. Pierre & Miquelon",
- "slug": "flag_st_pierre_miquelon",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ณ": {
- "name": "flag Pitcairn Islands",
- "slug": "flag_pitcairn_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ท": {
- "name": "flag Puerto Rico",
- "slug": "flag_puerto_rico",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ธ": {
- "name": "flag Palestinian Territories",
- "slug": "flag_palestinian_territories",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐น": {
- "name": "flag Portugal",
- "slug": "flag_portugal",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐ผ": {
- "name": "flag Palau",
- "slug": "flag_palau",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ต๐พ": {
- "name": "flag Paraguay",
- "slug": "flag_paraguay",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ถ๐ฆ": {
- "name": "flag Qatar",
- "slug": "flag_qatar",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ท๐ช": {
- "name": "flag Rรฉunion",
- "slug": "flag_reunion",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ท๐ด": {
- "name": "flag Romania",
- "slug": "flag_romania",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ท๐ธ": {
- "name": "flag Serbia",
- "slug": "flag_serbia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ท๐บ": {
- "name": "flag Russia",
- "slug": "flag_russia",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐ท๐ผ": {
- "name": "flag Rwanda",
- "slug": "flag_rwanda",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฆ": {
- "name": "flag Saudi Arabia",
- "slug": "flag_saudi_arabia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ง": {
- "name": "flag Solomon Islands",
- "slug": "flag_solomon_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐จ": {
- "name": "flag Seychelles",
- "slug": "flag_seychelles",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฉ": {
- "name": "flag Sudan",
- "slug": "flag_sudan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ช": {
- "name": "flag Sweden",
- "slug": "flag_sweden",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฌ": {
- "name": "flag Singapore",
- "slug": "flag_singapore",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ญ": {
- "name": "flag St. Helena",
- "slug": "flag_st_helena",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฎ": {
- "name": "flag Slovenia",
- "slug": "flag_slovenia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฏ": {
- "name": "flag Svalbard & Jan Mayen",
- "slug": "flag_svalbard_jan_mayen",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฐ": {
- "name": "flag Slovakia",
- "slug": "flag_slovakia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฑ": {
- "name": "flag Sierra Leone",
- "slug": "flag_sierra_leone",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฒ": {
- "name": "flag San Marino",
- "slug": "flag_san_marino",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ณ": {
- "name": "flag Senegal",
- "slug": "flag_senegal",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ด": {
- "name": "flag Somalia",
- "slug": "flag_somalia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ท": {
- "name": "flag Suriname",
- "slug": "flag_suriname",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ธ": {
- "name": "flag South Sudan",
- "slug": "flag_south_sudan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐น": {
- "name": "flag Sรฃo Tomรฉ & Prรญncipe",
- "slug": "flag_sao_tome_principe",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ป": {
- "name": "flag El Salvador",
- "slug": "flag_el_salvador",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฝ": {
- "name": "flag Sint Maarten",
- "slug": "flag_sint_maarten",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐พ": {
- "name": "flag Syria",
- "slug": "flag_syria",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ธ๐ฟ": {
- "name": "flag Eswatini",
- "slug": "flag_eswatini",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ฆ": {
- "name": "flag Tristan da Cunha",
- "slug": "flag_tristan_da_cunha",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐จ": {
- "name": "flag Turks & Caicos Islands",
- "slug": "flag_turks_caicos_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ฉ": {
- "name": "flag Chad",
- "slug": "flag_chad",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ซ": {
- "name": "flag French Southern Territories",
- "slug": "flag_french_southern_territories",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ฌ": {
- "name": "flag Togo",
- "slug": "flag_togo",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ญ": {
- "name": "flag Thailand",
- "slug": "flag_thailand",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ฏ": {
- "name": "flag Tajikistan",
- "slug": "flag_tajikistan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ฐ": {
- "name": "flag Tokelau",
- "slug": "flag_tokelau",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ฑ": {
- "name": "flag Timor-Leste",
- "slug": "flag_timor_leste",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ฒ": {
- "name": "flag Turkmenistan",
- "slug": "flag_turkmenistan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ณ": {
- "name": "flag Tunisia",
- "slug": "flag_tunisia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ด": {
- "name": "flag Tonga",
- "slug": "flag_tonga",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ท": {
- "name": "flag Tรผrkiye",
- "slug": "flag_turkiye",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐น": {
- "name": "flag Trinidad & Tobago",
- "slug": "flag_trinidad_tobago",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ป": {
- "name": "flag Tuvalu",
- "slug": "flag_tuvalu",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ผ": {
- "name": "flag Taiwan",
- "slug": "flag_taiwan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐น๐ฟ": {
- "name": "flag Tanzania",
- "slug": "flag_tanzania",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐บ๐ฆ": {
- "name": "flag Ukraine",
- "slug": "flag_ukraine",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐บ๐ฌ": {
- "name": "flag Uganda",
- "slug": "flag_uganda",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐บ๐ฒ": {
- "name": "flag U.S. Outlying Islands",
- "slug": "flag_u_s_outlying_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐บ๐ณ": {
- "name": "flag United Nations",
- "slug": "flag_united_nations",
- "group": "Flags",
- "emoji_version": "4.0",
- "unicode_version": "4.0",
- "skin_tone_support": false
- },
- "๐บ๐ธ": {
- "name": "flag United States",
- "slug": "flag_united_states",
- "group": "Flags",
- "emoji_version": "0.6",
- "unicode_version": "0.6",
- "skin_tone_support": false
- },
- "๐บ๐พ": {
- "name": "flag Uruguay",
- "slug": "flag_uruguay",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐บ๐ฟ": {
- "name": "flag Uzbekistan",
- "slug": "flag_uzbekistan",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ป๐ฆ": {
- "name": "flag Vatican City",
- "slug": "flag_vatican_city",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ป๐จ": {
- "name": "flag St. Vincent & Grenadines",
- "slug": "flag_st_vincent_grenadines",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ป๐ช": {
- "name": "flag Venezuela",
- "slug": "flag_venezuela",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ป๐ฌ": {
- "name": "flag British Virgin Islands",
- "slug": "flag_british_virgin_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ป๐ฎ": {
- "name": "flag U.S. Virgin Islands",
- "slug": "flag_u_s_virgin_islands",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ป๐ณ": {
- "name": "flag Vietnam",
- "slug": "flag_vietnam",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ป๐บ": {
- "name": "flag Vanuatu",
- "slug": "flag_vanuatu",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ผ๐ซ": {
- "name": "flag Wallis & Futuna",
- "slug": "flag_wallis_futuna",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ผ๐ธ": {
- "name": "flag Samoa",
- "slug": "flag_samoa",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฝ๐ฐ": {
- "name": "flag Kosovo",
- "slug": "flag_kosovo",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐พ๐ช": {
- "name": "flag Yemen",
- "slug": "flag_yemen",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐พ๐น": {
- "name": "flag Mayotte",
- "slug": "flag_mayotte",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฟ๐ฆ": {
- "name": "flag South Africa",
- "slug": "flag_south_africa",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฟ๐ฒ": {
- "name": "flag Zambia",
- "slug": "flag_zambia",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ฟ๐ผ": {
- "name": "flag Zimbabwe",
- "slug": "flag_zimbabwe",
- "group": "Flags",
- "emoji_version": "2.0",
- "unicode_version": "2.0",
- "skin_tone_support": false
- },
- "๐ด๓ ง๓ ข๓ ฅ๓ ฎ๓ ง๓ ฟ": {
- "name": "flag England",
- "slug": "flag_england",
- "group": "Flags",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ด๓ ง๓ ข๓ ณ๓ ฃ๓ ด๓ ฟ": {
- "name": "flag Scotland",
- "slug": "flag_scotland",
- "group": "Flags",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- },
- "๐ด๓ ง๓ ข๓ ท๓ ฌ๓ ณ๓ ฟ": {
- "name": "flag Wales",
- "slug": "flag_wales",
- "group": "Flags",
- "emoji_version": "5.0",
- "unicode_version": "5.0",
- "skin_tone_support": false
- }
-}
diff --git a/config/assets/modus.json b/config/assets/modus.json
deleted file mode 100644
index 530c99d..0000000
--- a/config/assets/modus.json
+++ /dev/null
@@ -1,17 +0,0 @@
-{
- "commands": {
- "/wp": "launcher.open(\"wallpapers\")",
- "/em": "launcher.open(\"emoji\")",
- "/ch": "launcher.open(\"cliphist\")",
- "/kb": "launcher.open(\"kanban\")",
- "/bt": "launcher.open(\"bluetooth\")",
- "/sh": "launcher.open(\"sh\")",
- "/pw": "launcher.open(\"power\")",
- "/nw": "launcher.open(\"wifi\")",
- "/cl": "launcher.open(\"calendar\")",
- "/tl": "launcher.open(\"tools\")",
- "/tx": "launcher.open(\"tmux\")",
- "/ws": "launcher.open(\"window-switcher\")"
- },
- "pinned_apps": []
-}
\ No newline at end of file
diff --git a/config/config.py b/config/config.py
deleted file mode 100644
index 44b7424..0000000
--- a/config/config.py
+++ /dev/null
@@ -1,1292 +0,0 @@
-import os
-import shutil
-import json
-import sys
-from pathlib import Path
-import random
-import subprocess
-import toml
-import gi
-
-gi.require_version("Gtk", "3.0")
-from gi.repository import Gtk
-
-# Fabric imports
-from fabric import Application
-from fabric.widgets.window import Window
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.widgets.entry import Entry
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from fabric.widgets.image import Image
-from fabric.widgets.stack import Stack
-from fabric.widgets.scale import Scale
-from fabric.utils import exec_shell_command, exec_shell_command_async
-
-# Assuming data.py exists in the same directory or is accessible via sys.path
-# If data.py is in ./config/data.py relative to this script's original location:
-try:
- # Adjust path relative to the *original* location if needed
- sys.path.insert(0, str(Path(__file__).resolve().parent / "../config"))
- from data import (
- APP_NAME,
- APP_NAME_CAP,
- WALLPAPERS_DIR_DEFAULT,
- )
-except ImportError as e:
- print(f"Error importing data constants: {e}")
- # Provide fallback defaults if import fails
- APP_NAME = "modus"
- APP_NAME_CAP = "Modus"
- CONFIG_DIR = "~/Modus"
- HOME_DIR = "~"
- WALLPAPERS_DIR_DEFAULT = "~/Pictures/wallpapers"
-
-SOURCE_STRING = f"""
-# {APP_NAME_CAP}
-source = ~/{APP_NAME_CAP}/config/hypr/{APP_NAME}.conf
-"""
-
-DEFAULTS = {
- "prefix_restart": "SHIFT ALT",
- "suffix_restart": "T",
- "prefix_bluetooth": "SHIFT ALT",
- "suffix_bluetooth": "B",
- "prefix_emoji": "SUPER",
- "suffix_emoji": "E",
- "prefix_cliphist": "SUPER",
- "suffix_cliphist": "V",
- "prefix_kanban": "SUPER",
- "suffix_kanban": "T",
- "prefix_toolbox": "SUPER",
- "suffix_toolbox": "S",
- "prefix_walls": "SUPER",
- "suffix_walls": "W",
- "prefix_launcher": "SUPER",
- "suffix_launcher": "D",
- "prefix_power": "SUPER",
- "suffix_power": "X",
- "prefix_tmux": "SUPER",
- "suffix_tmux": "Z",
- "prefix_notifications": "SUPER",
- "suffix_notifications": "N",
- "prefix_window_switcher": "ALT",
- "suffix_window_switcher": "W",
- "prefix_restart_inspector": "SUPER SHIFT",
- "suffix_restart_inspector": "T",
- "vertical": False,
- "vertical_right_align": True,
- "bottom_bar": False,
- "centered_bar": False,
- "terminal_command": "kitty -e",
- "wallpapers_dir": WALLPAPERS_DIR_DEFAULT,
- "dock_enabled": True,
- "dock_always_occluded": False, # Added default
- "dock_icon_size": 28,
- "osd_enabled": True, # Added default
- # Defaults for bar components (assuming True initially)
- "bar_button_app_visible": True,
- "bar_tray_visible": True,
- "bar_workspaces_visible": True,
- "bar_metrics_visible": True,
- "bar_language_visible": True,
- "bar_date_time_visible": True,
- "bar_updates_visible": True,
- "bar_indicators_visible": True,
- "corners_visible": True, # Added default for corners visibility
- "bar_metrics_disks": ["/"],
- # Add metric visibility defaults
- "metrics_visible": {
- "cpu": True,
- "ram": True,
- "disk": True,
- "swap": True,
- },
-}
-
-bind_vars = DEFAULTS.copy()
-
-
-def deep_update(target: dict, update: dict) -> dict:
- """
- Recursively update a nested dictionary with values from another dictionary.
- """
- for key, value in update.items():
- if isinstance(value, dict):
- target[key] = deep_update(target.get(key, {}), value)
- else:
- target[key] = value
- return target
-
-
-def ensure_matugen_config():
- """
- Ensure that the matugen configuration file exists and is updated
- with the expected settings.
- """
- expected_config = {
- "config": {
- "reload_apps": True,
- "wallpaper": {
- "command": "swww",
- "arguments": [
- "img",
- "-t",
- "outer",
- "--transition-duration",
- "1.5",
- "--transition-step",
- "255",
- "--transition-fps",
- "60",
- "-f",
- "Nearest",
- ],
- "set": True,
- },
- "custom_colors": {
- "red": {"color": "#FF0000", "blend": True},
- "green": {"color": "#00FF00", "blend": True},
- "yellow": {"color": "#FFFF00", "blend": True},
- "blue": {"color": "#0000FF", "blend": True},
- "magenta": {"color": "#FF00FF", "blend": True},
- "cyan": {"color": "#00FFFF", "blend": True},
- "white": {"color": "#FFFFFF", "blend": True},
- },
- },
- "templates": {
- "hyprland": {
- "input_path": f"~/{APP_NAME_CAP}/config/matugen/templates/hyprland-colors.conf",
- "output_path": f"~/{APP_NAME_CAP}/config/hypr/colors.conf",
- },
- f"{APP_NAME}": {
- "input_path": f"~/{APP_NAME_CAP}/config/matugen/templates/{APP_NAME}.css",
- "output_path": f"~/{APP_NAME_CAP}/styles/colors.css",
- "post_hook": f"fabric-cli exec {APP_NAME} 'app.set_css()' &",
- },
- },
- }
-
- config_path = os.path.expanduser("~/.config/matugen/config.toml")
- os.makedirs(os.path.dirname(config_path), exist_ok=True)
-
- # Load any existing configuration
- existing_config = {}
- if os.path.exists(config_path):
- with open(config_path, "r") as f:
- existing_config = toml.load(f)
- # Backup existing configuration
- shutil.copyfile(config_path, config_path + ".bak")
-
- # Merge configurations
- merged_config = deep_update(existing_config, expected_config)
- with open(config_path, "w") as f:
- toml.dump(merged_config, f)
-
- # Check if colors.css exists in styles directory
- colors_css_path = os.path.expanduser(f"~/{APP_NAME_CAP}/styles/colors.css")
- if not os.path.exists(colors_css_path):
- # Get the wallpapers directory from bind_vars or use default
- wallpapers_dir = bind_vars.get("wallpapers_dir", WALLPAPERS_DIR_DEFAULT)
- wallpapers_dir = os.path.expanduser(wallpapers_dir)
-
- # Get list of image files in the directory
- image_extensions = (".png", ".jpg", ".jpeg", ".gif", ".bmp")
- image_files = [
- f
- for f in os.listdir(wallpapers_dir)
- if f.lower().endswith(image_extensions)
- ]
-
- if image_files:
- # Select a random image
- random_image = random.choice(image_files)
- image_path = os.path.join(wallpapers_dir, random_image)
- # Apply the random wallpaper using matugen
- os.system(f"matugen image {image_path}")
- else:
- # If no images found in user's directory, try the default directory
- default_dir = os.path.expanduser(f"~/{APP_NAME_CAP}/assets/wallpaper/")
- default_images = [
- f
- for f in os.listdir(default_dir)
- if f.lower().endswith(image_extensions)
- ]
-
- random_image = random.choice(default_images)
- image_path = os.path.join(default_dir, random_image)
- os.system(f"matugen image {image_path}")
-
-
-def load_bind_vars():
- """
- Load saved key binding variables from JSON, if available.
- """
- config_json = os.path.expanduser(f"~/{APP_NAME_CAP}/config/assets/config.json")
- if os.path.exists(config_json):
- try:
- with open(config_json, "r") as f:
- saved_vars = json.load(f)
- # Update defaults with saved values, ensuring all keys exist
- for key in DEFAULTS:
- if key in saved_vars:
- bind_vars[key] = saved_vars[key]
- else:
- bind_vars[key] = DEFAULTS[
- key
- ] # Use default if missing in saved
- # Add any new keys from DEFAULTS not present in saved_vars
- for key in saved_vars:
- if key not in bind_vars:
- bind_vars[key] = saved_vars[
- key
- ] # Keep saved if it's not in new defaults (less likely)
- for vis_key in ["metrics_visible"]:
- if vis_key in DEFAULTS:
- if vis_key not in bind_vars or not isinstance(
- bind_vars[vis_key], dict
- ):
- bind_vars[vis_key] = DEFAULTS[vis_key].copy()
- else:
- for m in DEFAULTS[vis_key]:
- if m not in bind_vars[vis_key]:
- bind_vars[vis_key][m] = DEFAULTS[vis_key][m]
- except json.JSONDecodeError:
- print(f"Warning: Could not decode JSON from {config_json}. Using defaults.")
- bind_vars.update(DEFAULTS) # Ensure defaults on error
- except Exception as e:
- print(f"Error loading config from {config_json}: {e}. Using defaults.")
- bind_vars.update(DEFAULTS) # Ensure defaults on error
- else:
- # Ensure defaults are set if file doesn't exist
- bind_vars.update(DEFAULTS)
-
-
-def generate_hyprconf() -> str:
- """
- Generate the Hypr configuration string using the current bind_vars.
- """
- home = os.path.expanduser("~")
- return f"""# Generated by {APP_NAME_CAP} - DO NOT EDIT MANUALLY
-exec-once = python {home}/{APP_NAME_CAP}/main.py
-exec = pgrep -x "hypridle" > /dev/null || hypridle
-exec = swww-daemon
-exec-once = wl-paste --type text --watch cliphist store
-exec-once = wl-paste --type image --watch cliphist store
-
-$fabricSend = fabric-cli exec {APP_NAME}
-$scriptsDir = {home}/{APP_NAME_CAP}/config/scripts/
-
-# Key Bindings
-bind = {bind_vars["prefix_restart"]}, {bind_vars["suffix_restart"]}, exec, killall {APP_NAME}; python {home}/{APP_NAME_CAP}/main.py # Reload {APP_NAME_CAP}
-bind = {bind_vars["prefix_bluetooth"]}, {bind_vars["suffix_bluetooth"]}, exec, $fabricSend 'launcher.open("bluetooth")' # Bluetooth
-bind = {bind_vars["prefix_notifications"]}, {bind_vars["suffix_notifications"]}, exec, $fabricSend 'launcher.open("notification-center")' # Notifications
-bind = {bind_vars["prefix_emoji"]}, {bind_vars["suffix_emoji"]}, exec, $fabricSend 'launcher.open("emoji")' # Emoji
-bind = {bind_vars["prefix_cliphist"]}, {bind_vars["suffix_cliphist"]}, exec, $fabricSend 'launcher.open("cliphist")' # Clipboard History
-bind = {bind_vars["prefix_kanban"]}, {bind_vars["suffix_kanban"]}, exec, $fabricSend 'launcher.open("kanban")' # Kanban
-bind = {bind_vars["prefix_tmux"]}, {bind_vars["suffix_tmux"]}, exec, $fabricSend 'launcher.open("tmux")' # Tmux Manager
-bind = {bind_vars["prefix_toolbox"]}, {bind_vars["suffix_toolbox"]}, exec, $fabricSend 'launcher.open("tools")' # Toolbox
-bind = {bind_vars["prefix_walls"]}, {bind_vars["suffix_walls"]}, exec, $fabricSend 'launcher.open("wallpapers")' # Wallpaper Selector
-bind = {bind_vars["prefix_launcher"]}, {bind_vars["suffix_launcher"]}, exec, $fabricSend 'launcher.open("launcher")' # App Launcher
-bind = {bind_vars["prefix_window_switcher"]}, {bind_vars["suffix_window_switcher"]}, exec, $fabricSend 'launcher.open("window-switcher")' # Window Switcher
-bind = {bind_vars["prefix_power"]}, {bind_vars["suffix_power"]}, exec, $fabricSend 'launcher.open("power")' # Power Menu
-bind = {bind_vars["prefix_restart_inspector"]}, {bind_vars["suffix_restart_inspector"]}, exec, killall {APP_NAME}; GTK_DEBUG=interactive python {home}/{APP_NAME_CAP}/main.py # Restart with inspector
-
-# Wallpapers directory: {bind_vars["wallpapers_dir"]}
-
-# Source color scheme
-source = {home}/{APP_NAME_CAP}/config/hypr/colors.conf
-
-general {{
- col.active_border = 0xff$primary
- col.inactive_border = 0xff$surface
- gaps_in = 2
- gaps_out = 4
- border_size = 2
- layout = dwindle
-}}
-
-decoration {{
- blur {{
- enabled = yes
- size = 5
- passes = 3
- new_optimizations = yes
- contrast = 1
- brightness = 1
- }}
- rounding = 14
- shadow {{
- enabled = true
- range = 10
- render_power = 2
- color = rgba(0, 0, 0, 0.25)
- }}
-}}
-
-animations {{
- enabled = true
- # Animation curves
- bezier = linear, 0, 0, 1, 1
- bezier = md3_standard, 0.2, 0, 0, 1
- bezier = md3_decel, 0.05, 0.7, 0.1, 1
- bezier = md3_accel, 0.3, 0, 0.8, 0.15
- bezier = overshot, 0.05, 0.9, 0.1, 1.1
- bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
- bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
- bezier = menu_decel, 0.1, 1, 0, 1
- bezier = menu_accel, 0.38, 0.04, 1, 0.07
- bezier = easeInOutCirc, 0.85, 0, 0.15, 1
- bezier = easeOutCirc, 0, 0.55, 0.45, 1
- bezier = easeOutExpo, 0.16, 1, 0.3, 1
- bezier = softAcDecel, 0.26, 0.26, 0.15, 1
- bezier = md2, 0.4, 0, 0.2, 1
-
- animation = windows, 1, 3, md3_decel, popin 60%
- animation = windowsIn, 1, 3, md3_decel, popin 60%
- animation = windowsOut, 1, 3, md3_accel, popin 60%
- animation = border, 1, 10, default
- animation = fade, 1, 3, md3_decel
- animation = layers, 1, 2, md3_decel, slide
- animation = layersIn, 1, 3, menu_decel, slide
- animation = layersOut, 1, 1.6, menu_accel, slide
- animation = fadeLayersIn, 1, 2, menu_decel
- animation = fadeLayersOut, 1, 4.5, menu_accel
- animation = workspaces, 1, 7, menu_decel, slide
- animation = specialWorkspace, 1, 3, md3_decel, slidevert
-}}
-"""
-
-
-def backup_and_replace(src: str, dest: str, config_name: str):
- """
- Backup the existing configuration file and replace it with a new one.
- """
- if os.path.exists(dest):
- backup_path = dest + ".bak"
- shutil.copy(dest, backup_path)
- print(f"{config_name} config backed up to {backup_path}")
- shutil.copy(src, dest)
- print(f"{config_name} config replaced from {src}")
-
-
-class HyprConfGUI(Window):
- def __init__(self, show_lock_checkbox: bool, show_idle_checkbox: bool, **kwargs):
- super().__init__(
- title="Modus Settings",
- name="modus-settings-widnow",
- size=(650, 550),
- **kwargs,
- )
- self.set_resizable(False)
-
- self.show_lock_checkbox = show_lock_checkbox
- self.show_idle_checkbox = show_idle_checkbox
-
- # Overall vertical box to hold the main content and bottom buttons
- root_box = Box(orientation="v", spacing=10, style="margin: 10px;")
- self.add(root_box)
-
- # Main horizontal box for switcher and stack
- main_content_box = Box(orientation="h", spacing=6, v_expand=True, h_expand=True)
- root_box.add(main_content_box)
-
- # --- Tab Control ---
- self.tab_stack = Stack(
- transition_type="slide-up-down", # Change transition for vertical feel
- transition_duration=250,
- v_expand=True,
- h_expand=True,
- )
-
- # Create tabs and add to stack
- self.key_bindings_tab_content = self.create_key_bindings_tab()
- self.appearance_tab_content = self.create_appearance_tab()
- self.system_tab_content = self.create_system_tab()
- self.about_tab_content = self.create_about_tab()
-
- self.tab_stack.add_titled(
- self.key_bindings_tab_content, "key_bindings", "Key Bindings"
- )
- self.tab_stack.add_titled(
- self.appearance_tab_content, "appearance", "Appearance"
- )
- self.tab_stack.add_titled(self.system_tab_content, "system", "System")
- self.tab_stack.add_titled(
- self.about_tab_content, "about", "About"
- ) # Add About tab to stack
-
- # Use Gtk.StackSwitcher vertically on the left
- tab_switcher = Gtk.StackSwitcher()
- tab_switcher.set_stack(self.tab_stack)
- tab_switcher.set_orientation(
- Gtk.Orientation.VERTICAL
- ) # Set vertical orientation
- # Optional: Adjust alignment if needed
- # tab_switcher.set_valign(Gtk.Align.START)
-
- # Add switcher to the left of the main content box
- main_content_box.add(tab_switcher)
-
- # Add stack to the right of the main content box
- main_content_box.add(self.tab_stack)
-
- # --- Bottom Buttons ---
- button_box = Box(orientation="h", spacing=10, h_align="end")
-
- reset_btn = Button(label="Reset to Defaults", on_clicked=self.on_reset)
- button_box.add(reset_btn)
-
- # Add Close button back
- close_btn = Button(label="Close", on_clicked=self.on_close)
- button_box.add(close_btn)
-
- accept_btn = Button(label="Apply & Reload", on_clicked=self.on_accept)
- button_box.add(accept_btn)
-
- # Add button box to the bottom of the root box
- root_box.add(button_box)
-
- def create_key_bindings_tab(self):
- """Create tab for key bindings configuration using Fabric widgets and Gtk.Grid."""
- scrolled_window = ScrolledWindow(
- h_scrollbar_policy="never",
- v_scrollbar_policy="automatic",
- h_expand=True,
- v_expand=True,
- )
- # Remove fixed height constraints to allow stack to fill space
- scrolled_window.set_min_content_height(300)
- scrolled_window.set_max_content_height(300)
-
- # Main container with padding
- main_vbox = Box(orientation="v", spacing=10, style="margin: 15px;")
- scrolled_window.add(main_vbox)
-
- # Create a grid for key bindings
- keybind_grid = Gtk.Grid()
- keybind_grid.set_column_spacing(10)
- keybind_grid.set_row_spacing(8)
- keybind_grid.set_margin_start(5)
- keybind_grid.set_margin_end(5)
- keybind_grid.set_margin_top(5)
- keybind_grid.set_margin_bottom(5)
-
- # Header Row
- action_label = Label(
- markup="Action", h_align="start", style="margin-bottom: 5px;"
- )
- modifier_label = Label(
- markup="Modifier", h_align="start", style="margin-bottom: 5px;"
- )
- separator_label = Label(
- label="+", h_align="center", style="margin-bottom: 5px;"
- )
- key_label = Label(
- markup="Key", h_align="start", style="margin-bottom: 5px;"
- )
-
- keybind_grid.attach(action_label, 0, 0, 1, 1)
- keybind_grid.attach(modifier_label, 1, 0, 1, 1)
- keybind_grid.attach(separator_label, 2, 0, 1, 1)
- keybind_grid.attach(key_label, 3, 0, 1, 1)
-
- self.entries = []
- bindings = [
- (f"Reload {APP_NAME_CAP}", "prefix_restart", "suffix_restart"),
- ("Bluetooth", "prefix_bluetooth", "suffix_bluetooth"),
- ("Notifications", "prefix_notifications", "suffix_notifications"),
- ("Kanban", "prefix_kanban", "suffix_kanban"),
- ("App Launcher", "prefix_launcher", "suffix_launcher"),
- ("Tmux", "prefix_tmux", "suffix_tmux"),
- ("Toolbox", "prefix_toolbox", "suffix_toolbox"),
- ("Wallpapers", "prefix_walls", "suffix_walls"),
- ("Emoji Picker", "prefix_emoji", "suffix_emoji"),
- ("Clipboard History", "prefix_cliphist", "suffix_cliphist"),
- ("Window Switcher", "prefix_window_switcher", "suffix_window_switcher"),
- ("Power Menu", "prefix_power", "suffix_power"),
- (
- "Restart with inspector",
- "prefix_restart_inspector",
- "suffix_restart_inspector",
- ),
- ]
-
- # Populate the grid with entries
- for i, (label_text, prefix_key, suffix_key) in enumerate(bindings):
- row = i + 1 # Start at row 1 after headers
-
- # Action label
- binding_label = Label(label=label_text, h_align="start")
- keybind_grid.attach(binding_label, 0, row, 1, 1)
-
- # Prefix entry
- prefix_entry = Entry(text=bind_vars[prefix_key])
- keybind_grid.attach(prefix_entry, 1, row, 1, 1)
-
- # Plus separator
- plus_label = Label(label="+", h_align="center")
- keybind_grid.attach(plus_label, 2, row, 1, 1)
-
- # Suffix entry
- suffix_entry = Entry(text=bind_vars[suffix_key])
- keybind_grid.attach(suffix_entry, 3, row, 1, 1)
-
- self.entries.append((prefix_key, suffix_key, prefix_entry, suffix_entry))
-
- main_vbox.add(keybind_grid)
- return scrolled_window
-
- def create_appearance_tab(self):
- """Create tab for appearance settings using Fabric widgets and Gtk.Grid."""
- scrolled_window = ScrolledWindow(
- h_scrollbar_policy="never",
- v_scrollbar_policy="automatic",
- h_expand=True,
- v_expand=True,
- )
- scrolled_window.set_min_content_height(300)
- scrolled_window.set_max_content_height(300)
-
- # Main container with padding
- vbox = Box(orientation="v", spacing=15, style="margin: 15px;")
- scrolled_window.add(vbox)
-
- # === WALLPAPERS SECTION ===
- wall_header = Label(markup="Wallpapers", h_align="start")
- vbox.add(wall_header)
-
- wall_box = Box(orientation="h", spacing=10, style="margin-left: 10px;")
- vbox.add(wall_box)
-
- wall_label = Label(label="Directory:", h_align="start", v_align="center")
- wall_box.add(wall_label)
-
- self.wall_dir_chooser = Gtk.FileChooserButton(
- title="Select a folder", action=Gtk.FileChooserAction.SELECT_FOLDER
- )
- self.wall_dir_chooser.set_tooltip_text(
- "Select the directory containing your wallpaper images"
- )
- self.wall_dir_chooser.set_filename(bind_vars["wallpapers_dir"])
- self.wall_dir_chooser.set_size_request(180, -1)
- wall_box.add(self.wall_dir_chooser)
-
- # === LAYOUT OPTIONS SECTION ===
- layout_header = Label(markup="Layout Options", h_align="start")
- vbox.add(layout_header)
-
- # Create a grid for layout options
- layout_grid = Gtk.Grid()
- layout_grid.set_column_spacing(20)
- layout_grid.set_row_spacing(15)
- layout_grid.set_margin_start(10)
- layout_grid.set_margin_top(5)
- layout_grid.set_margin_bottom(10)
- vbox.add(layout_grid)
-
- # Column 1 (Left)
- # Vertical Layout
- vertical_label = Label(
- label="Vertical Layout", h_align="start", v_align="center"
- )
- layout_grid.attach(vertical_label, 0, 0, 1, 1)
-
- vertical_switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- vertical_switch_container.set_halign(Gtk.Align.START)
- vertical_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.vertical_switch = Gtk.Switch()
- self.vertical_switch.set_active(bind_vars.get("vertical", False))
- self.vertical_switch.connect("notify::active", self.on_vertical_changed)
- vertical_switch_container.add(self.vertical_switch)
-
- layout_grid.attach(vertical_switch_container, 1, 0, 1, 1)
-
- # Vertical Right Align (new)
- vertical_right_label = Label(
- label="Right Side (Vertical)", h_align="start", v_align="center"
- )
- layout_grid.attach(vertical_right_label, 0, 1, 1, 1)
-
- vertical_right_switch_container = Gtk.Box(
- orientation=Gtk.Orientation.HORIZONTAL
- )
- vertical_right_switch_container.set_halign(Gtk.Align.START)
- vertical_right_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.vertical_right_switch = Gtk.Switch()
- self.vertical_right_switch.set_active(
- bind_vars.get("vertical_right_align", True)
- )
- self.vertical_right_switch.set_sensitive(self.vertical_switch.get_active())
- vertical_right_switch_container.add(self.vertical_right_switch)
- layout_grid.attach(vertical_right_switch_container, 1, 1, 1, 1)
-
- # Bottom Layout
- bottom_bar_label = Label(
- label="Bottom Layout", h_align="start", v_align="center"
- )
- layout_grid.attach(bottom_bar_label, 0, 2, 1, 1)
-
- bottom_bar_switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- bottom_bar_switch_container.set_halign(Gtk.Align.START)
- bottom_bar_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.bottom_bar_switch = Gtk.Switch()
- self.bottom_bar_switch.set_active(bind_vars.get("bottom_bar", False))
- self.bottom_bar_switch.connect("notify::active", self.on_bottom_bar_changed)
- bottom_bar_switch_container.add(self.bottom_bar_switch)
- layout_grid.attach(bottom_bar_switch_container, 1, 2, 1, 1)
-
- # Dock Options
- dock_label = Label(label="Show Dock", h_align="start", v_align="center")
- layout_grid.attach(dock_label, 0, 3, 1, 1)
-
- dock_switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- dock_switch_container.set_halign(Gtk.Align.START)
- dock_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.dock_switch = Gtk.Switch()
- self.dock_switch.set_active(bind_vars.get("dock_enabled", True))
- self.dock_switch.connect("notify::active", self.on_dock_enabled_changed)
- dock_switch_container.add(self.dock_switch)
- layout_grid.attach(dock_switch_container, 1, 3, 1, 1)
-
- # Column 2 (Right)
- # Centered Bar
- centered_label = Label(
- label="Centered Bar (Vertical Only)", h_align="start", v_align="center"
- )
- layout_grid.attach(centered_label, 2, 0, 1, 1)
-
- centered_switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- centered_switch_container.set_halign(Gtk.Align.START)
- centered_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.centered_switch = Gtk.Switch()
- self.centered_switch.set_active(bind_vars.get("centered_bar", False))
- self.centered_switch.set_sensitive(self.vertical_switch.get_active())
- centered_switch_container.add(self.centered_switch)
- layout_grid.attach(centered_switch_container, 3, 0, 1, 1)
-
- # Dock Hover
- dock_hover_label = Label(
- label="Show Dock Only on Hover", h_align="start", v_align="center"
- )
- layout_grid.attach(dock_hover_label, 2, 1, 1, 1)
-
- dock_hover_switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- dock_hover_switch_container.set_halign(Gtk.Align.START)
- dock_hover_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.dock_hover_switch = Gtk.Switch()
- self.dock_hover_switch.set_active(bind_vars.get("dock_always_occluded", False))
- self.dock_hover_switch.set_sensitive(self.dock_switch.get_active())
- dock_hover_switch_container.add(self.dock_hover_switch)
- layout_grid.attach(dock_hover_switch_container, 3, 1, 1, 1)
-
- # Show OSD
- osd_label = Label(label="Show OSD", h_align="start", v_align="center")
- layout_grid.attach(osd_label, 2, 2, 1, 1)
-
- osd_hover_switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- osd_hover_switch_container.set_halign(Gtk.Align.START)
- osd_hover_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.osd_switch = Gtk.Switch()
- self.osd_switch.set_active(bind_vars.get("osd_enabled", True))
- osd_hover_switch_container.add(self.osd_switch)
- layout_grid.attach(osd_hover_switch_container, 3, 2, 1, 1)
-
- # Dock Icon Size (Full width)
- dock_size_label = Label(
- label="Dock Icon Size", h_align="start", v_align="center"
- )
- layout_grid.attach(dock_size_label, 0, 4, 1, 1)
-
- self.dock_size_scale = Scale(
- min_value=16,
- max_value=48,
- value=bind_vars.get("dock_icon_size", 28),
- increments=(2, 4),
- draw_value=True,
- value_position="right",
- digits=0,
- h_expand=True,
- )
- layout_grid.attach(self.dock_size_scale, 1, 4, 3, 1)
-
- # === BAR COMPONENTS SECTION ===
- components_header = Label(markup="Bar Components", h_align="start")
- vbox.add(components_header)
-
- # Create a grid for bar components
- components_grid = Gtk.Grid()
- components_grid.set_column_spacing(15)
- components_grid.set_row_spacing(8)
- components_grid.set_margin_start(10)
- components_grid.set_margin_top(5)
- vbox.add(components_grid)
-
- self.component_switches = {}
- component_display_names = {
- "button_app": "App Launcher Button",
- "tray": "System Tray",
- "workspaces": "Workspaces",
- "metrics": "System Metrics",
- "language": "Language Indicator",
- "date_time": "Date & Time",
- "updates": "Update Indicator",
- "indicators": "System Indicators",
- }
-
- # Add corners visibility switch
- self.corners_switch = Gtk.Switch()
- self.corners_switch.set_active(bind_vars.get("corners_visible", True))
-
- # Calculate number of rows needed (we'll use 2 columns)
- num_components = len(component_display_names) + 1 # +1 for corners
- rows_per_column = (num_components + 1) // 2 # Ceiling division
-
- # First add corners to the top of first column
- corners_label = Label(
- label="Rounded Corners", h_align="start", v_align="center"
- )
- components_grid.attach(corners_label, 0, 0, 1, 1)
-
- switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- switch_container.set_halign(Gtk.Align.START)
- switch_container.set_valign(Gtk.Align.CENTER)
- switch_container.add(self.corners_switch)
- components_grid.attach(switch_container, 1, 0, 1, 1)
-
- # Add components to grid in two columns
- for i, (component_name, display_name) in enumerate(
- component_display_names.items()
- ):
- # Determine position: first half in column 0, second half in column 2
- # Start at row 1 to account for corners at row 0
- row = (i + 1) % rows_per_column # +1 to start after corners
- col = 0 if i < (rows_per_column - 1) else 2 # Adjust column calculation
-
- component_label = Label(
- label=display_name, h_align="start", v_align="center"
- )
- components_grid.attach(component_label, col, row, 1, 1)
-
- # Container for switch to prevent stretching
- switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- switch_container.set_halign(Gtk.Align.START)
- switch_container.set_valign(Gtk.Align.CENTER)
-
- component_switch = Gtk.Switch()
- config_key = f"bar_{component_name}_visible"
- component_switch.set_active(bind_vars.get(config_key, True))
- switch_container.add(component_switch)
-
- components_grid.attach(switch_container, col + 1, row, 1, 1)
-
- self.component_switches[component_name] = component_switch
-
- return scrolled_window
-
- def create_system_tab(self):
- """Create tab for system configurations using Fabric widgets and Gtk.Grid."""
- scrolled_window = ScrolledWindow(
- h_scrollbar_policy="never",
- v_scrollbar_policy="automatic",
- h_expand=True,
- v_expand=True,
- )
- # Remove fixed height constraints
- scrolled_window.set_min_content_height(300)
- scrolled_window.set_max_content_height(300)
-
- # Main container with padding
- vbox = Box(orientation="v", spacing=15, style="margin: 15px;")
- scrolled_window.add(vbox)
-
- # Create a grid for system settings
- system_grid = Gtk.Grid()
- system_grid.set_column_spacing(20)
- system_grid.set_row_spacing(10)
- system_grid.set_margin_bottom(15)
- vbox.add(system_grid)
-
- # === TERMINAL SETTINGS ===
- terminal_header = Label(markup="Terminal Settings", h_align="start")
- system_grid.attach(terminal_header, 0, 0, 2, 1)
-
- terminal_label = Label(label="Command:", h_align="start", v_align="center")
- system_grid.attach(terminal_label, 0, 1, 1, 1)
-
- self.terminal_entry = Entry(
- text=bind_vars["terminal_command"],
- tooltip_text="Command used to launch terminal apps (e.g., 'kitty -e')",
- h_expand=True,
- )
- system_grid.attach(self.terminal_entry, 1, 1, 1, 1)
-
- hint_label = Label(
- markup="Examples: 'kitty -e', 'alacritty -e', 'foot -e'",
- h_align="start",
- )
- system_grid.attach(hint_label, 0, 2, 2, 1)
-
- # === HYPRLAND INTEGRATION ===
- hypr_header = Label(markup="Hyprland Integration", h_align="start")
- system_grid.attach(hypr_header, 2, 0, 2, 1)
-
- row = 1
-
- # Hyprland locks and idle settings
- self.lock_switch = None
- if self.show_lock_checkbox:
- lock_label = Label(
- label="Replace Hyprlock config", h_align="start", v_align="center"
- )
- system_grid.attach(lock_label, 2, row, 1, 1)
-
- # Container for switch to prevent stretching
- lock_switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- lock_switch_container.set_halign(Gtk.Align.START)
- lock_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.lock_switch = Gtk.Switch()
- self.lock_switch.set_tooltip_text(
- "Replace Hyprlock configuration with Ax-Shell's custom config"
- )
- lock_switch_container.add(self.lock_switch)
-
- system_grid.attach(lock_switch_container, 3, row, 1, 1)
- row += 1
-
- self.idle_switch = None
- if self.show_idle_checkbox:
- idle_label = Label(
- label="Replace Hypridle config", h_align="start", v_align="center"
- )
- system_grid.attach(idle_label, 2, row, 1, 1)
-
- # Container for switch to prevent stretching
- idle_switch_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- idle_switch_container.set_halign(Gtk.Align.START)
- idle_switch_container.set_valign(Gtk.Align.CENTER)
-
- self.idle_switch = Gtk.Switch()
- self.idle_switch.set_tooltip_text(
- "Replace Hypridle configuration with Ax-Shell's custom config"
- )
- idle_switch_container.add(self.idle_switch)
-
- system_grid.attach(idle_switch_container, 3, row, 1, 1)
- row += 1
-
- if self.show_lock_checkbox or self.show_idle_checkbox:
- note_label = Label(
- markup="Existing configs will be backed up",
- h_align="start",
- )
- system_grid.attach(note_label, 2, row, 2, 1)
-
- # --- System Metrics Options (moved from appearance tab) ---
- metrics_header = Label(markup="System Metrics Options", h_align="start")
- vbox.add(metrics_header)
-
- # Metrics visibility toggles
- metrics_grid = Gtk.Grid()
- metrics_grid.set_column_spacing(15)
- metrics_grid.set_row_spacing(8)
- metrics_grid.set_margin_start(10)
- metrics_grid.set_margin_top(5)
- vbox.add(metrics_grid)
-
- self.metrics_switches = {}
-
- metric_names = {
- "cpu": "CPU",
- "ram": "RAM",
- "disk": "Disk",
- "swap": "Swap",
- }
-
- # metrics toggles
- metrics_grid.attach(Label(label="Show in Metrics", h_align="start"), 2, 0, 1, 1)
- for i, (key, label) in enumerate(metric_names.items()):
- switch = Gtk.Switch()
- switch.set_active(bind_vars.get("metrics_visible", {}).get(key, True))
- self.metrics_switches[key] = switch
- metrics_grid.attach(Label(label=label, h_align="start"), 2, i + 1, 1, 1)
- metrics_grid.attach(switch, 4, i + 1, 1, 1)
-
- # Enforce minimum 4 enabled metrics
- def enforce_minimum_metrics(switch_dict):
- enabled = [k for k, s in switch_dict.items() if s.get_active()]
- for k, s in switch_dict.items():
- if len(enabled) <= 4 and s.get_active():
- s.set_sensitive(False)
- else:
- s.set_sensitive(True)
-
- def on_metric_toggle(switch, gparam, which):
- enforce_minimum_metrics(self.metrics_switches)
-
- for k, s in self.metrics_switches.items():
- s.connect("notify::active", on_metric_toggle, k)
- enforce_minimum_metrics(self.metrics_switches)
-
- # Disk directories
- disks_label = Label(label="Disk directories", h_align="start", v_align="center")
- vbox.add(disks_label)
-
- self.disk_entries = Box(orientation="v", spacing=8, h_align="start")
-
- def create_disk_edit(path):
- bar = Box(orientation="h", spacing=10, h_align="start")
- entry = Entry(text=path, h_expand=True)
- bar.add(entry)
- x = Button(label="X", on_clicked=lambda _: self.disk_entries.remove(bar))
- bar.add(x)
- self.disk_entries.add(bar)
-
- vbox.add(self.disk_entries)
- for path in bind_vars.get("bar_metrics_disks"):
- create_disk_edit(path)
- add_container = Gtk.Box(orientation=Gtk.Orientation.HORIZONTAL)
- add_container.set_halign(Gtk.Align.START)
- add_container.set_valign(Gtk.Align.CENTER)
- add = Button(label="Add new disk", on_clicked=lambda _: create_disk_edit("/"))
- add_container.add(add)
- vbox.add(add_container)
-
- return scrolled_window
-
- def create_about_tab(self):
- """Create an About tab with project info, repo link, and Ko-Fi button."""
- vbox = Box(orientation="v", spacing=18, style="margin: 30px;")
- # Project title
- vbox.add(
- Label(
- markup=f"{APP_NAME_CAP}",
- h_align="start",
- style="font-size: 1.5em; margin-bottom: 8px;",
- )
- )
- # Description
- vbox.add(
- Label(
- label="A hackable shell for Hyprland, powered by Fabric.",
- h_align="start",
- style="margin-bottom: 12px;",
- )
- )
- # Repo link
- repo_box = Box(orientation="h", spacing=6, h_align="start")
- repo_label = Label(label="GitHub:", h_align="start")
- repo_link = Label()
- repo_link.set_markup(
- f'https://github.com/S4NKALP/Modus'
- )
- repo_box.add(repo_label)
- repo_box.add(repo_link)
- vbox.add(repo_box)
-
- # Ko-Fi button
- def on_kofi_clicked(_):
- import webbrowser
-
- webbrowser.open("https://ko-fi.com/S4NKALP")
-
- kofi_btn = Button(
- label="Support on Ko-Fi โค๏ธ",
- on_clicked=on_kofi_clicked,
- tooltip_text="Support S4NKALP on Ko-Fi",
- )
- kofi_btn.set_style("margin-top: 18px; min-width: 160px;")
- vbox.add(kofi_btn)
- # Spacer
- vbox.add(Box(v_expand=True))
- return vbox
-
- def on_vertical_changed(self, switch, gparam):
- """Callback for vertical switch."""
- is_active = switch.get_active()
- self.centered_switch.set_sensitive(is_active)
- self.vertical_right_switch.set_sensitive(is_active)
-
- if is_active:
- # Disable bottom bar when vertical layout is enabled
- bind_vars["bottom_bar"] = False
- self.bottom_bar_switch.set_active(False)
-
- def on_dock_enabled_changed(self, switch, gparam):
- """Callback for dock enabled switch."""
- is_active = switch.get_active()
- self.dock_hover_switch.set_sensitive(is_active)
- if not is_active:
- self.dock_hover_switch.set_active(False)
-
- def on_bottom_bar_changed(self, switch, gparam):
- """Handle bottom bar switch changes"""
- bind_vars["bottom_bar"] = switch.get_active()
- if switch.get_active():
- # Disable vertical layout
- bind_vars["vertical"] = False
- self.vertical_switch.set_active(False)
-
- # Only disable centered bar without turning it off
- self.centered_switch.set_sensitive(False)
-
- # Disable right side option and reset it to False
- self.vertical_right_switch.set_sensitive(False)
- self.vertical_right_switch.set_active(False)
- bind_vars["vertical_right_align"] = False
-
- def on_accept(self, widget):
- """
- Save the configuration and update the necessary files without closing the window.
- """
- for prefix_key, suffix_key, prefix_entry, suffix_entry in self.entries:
- bind_vars[prefix_key] = prefix_entry.get_text()
- bind_vars[suffix_key] = suffix_entry.get_text()
-
- bind_vars["wallpapers_dir"] = self.wall_dir_chooser.get_filename()
- bind_vars["vertical"] = self.vertical_switch.get_active()
- bind_vars["vertical_right_align"] = self.vertical_right_switch.get_active()
- bind_vars["bottom_bar"] = self.bottom_bar_switch.get_active()
- bind_vars["enabled_osd"] = self.osd_switch.get_active()
- bind_vars["centered_bar"] = self.centered_switch.get_active()
- bind_vars["dock_enabled"] = self.dock_switch.get_active()
- bind_vars["dock_always_occluded"] = self.dock_hover_switch.get_active()
- bind_vars["dock_icon_size"] = int(self.dock_size_scale.get_value())
- bind_vars["terminal_command"] = self.terminal_entry.get_text()
- bind_vars["corners_visible"] = self.corners_switch.get_active()
-
- for component_name, switch in self.component_switches.items():
- config_key = f"bar_{component_name}_visible"
- bind_vars[config_key] = switch.get_active()
-
- # Save metrics visibility
- bind_vars["metrics_visible"] = {
- k: s.get_active() for k, s in self.metrics_switches.items()
- }
-
- bind_vars["bar_metrics_disks"] = []
- for entry in self.disk_entries.children:
- bind_vars["bar_metrics_disks"].append(entry.children[0].get_text())
-
- config_json = os.path.expanduser(f"~/{APP_NAME_CAP}/config/assets/config.json")
- os.makedirs(os.path.dirname(config_json), exist_ok=True)
- try:
- with open(config_json, "w") as f:
- json.dump(bind_vars, f, indent=4)
- except Exception as e:
- print(f"Error saving config.json: {e}")
-
- if self.lock_switch and self.lock_switch.get_active():
- src_lock = os.path.expanduser(f"~/{APP_NAME_CAP}/config/hypr/hyprlock.conf")
- dest_lock = os.path.expanduser("~/.config/hypr/hyprlock.conf")
- if os.path.exists(src_lock):
- backup_and_replace(src_lock, dest_lock, "Hyprlock")
- else:
- print(f"Warning: Source hyprlock config not found at {src_lock}")
-
- if self.idle_switch and self.idle_switch.get_active():
- src_idle = os.path.expanduser(f"~/{APP_NAME_CAP}/config/hypr/hypridle.conf")
- dest_idle = os.path.expanduser("~/.config/hypr/hypridle.conf")
- if os.path.exists(src_idle):
- backup_and_replace(src_idle, dest_idle, "Hypridle")
- else:
- print(f"Warning: Source hypridle config not found at {src_idle}")
-
- hyprland_config_path = os.path.expanduser("~/.config/hypr/hyprland.conf")
- try:
- needs_append = True
- if os.path.exists(hyprland_config_path):
- with open(hyprland_config_path, "r") as f:
- content = f.read()
- if SOURCE_STRING.strip() in content:
- needs_append = False
- else:
- os.makedirs(os.path.dirname(hyprland_config_path), exist_ok=True)
-
- if needs_append:
- with open(hyprland_config_path, "a") as f:
- f.write("\n" + SOURCE_STRING)
- print(f"Appended source string to {hyprland_config_path}")
-
- except Exception as e:
- print(f"Error updating {hyprland_config_path}: {e}")
-
- start_config()
-
- # Restart Modus using fabric's async command executor
- main_script_path = os.path.expanduser(f"~/{APP_NAME_CAP}/main.py")
- kill_cmd = f"killall {APP_NAME}"
- start_cmd = f"python {main_script_path}"
-
- try:
- # Use fabric's helper to run the command asynchronously
- exec_shell_command(kill_cmd)
- exec_shell_command_async(start_cmd)
- print(f"{APP_NAME_CAP} restart initiated.")
- except Exception as e:
- print(f"Error restarting {APP_NAME_CAP}: {e}")
-
- def on_reset(self, widget):
- """
- Reset all settings to default values. Uses Gtk.MessageDialog.
- """
- dialog = Gtk.MessageDialog(
- transient_for=self.get_toplevel(),
- flags=0,
- message_type=Gtk.MessageType.QUESTION,
- buttons=Gtk.ButtonsType.YES_NO,
- text="Reset all settings to defaults?",
- )
- dialog.format_secondary_text(
- "This will reset all keybindings and appearance settings to their default values."
- )
- response = dialog.run()
- dialog.destroy()
-
- if response == Gtk.ResponseType.YES:
- global bind_vars
- bind_vars = DEFAULTS.copy()
-
- for prefix_key, suffix_key, prefix_entry, suffix_entry in self.entries:
- prefix_entry.set_text(bind_vars[prefix_key])
- suffix_entry.set_text(bind_vars[suffix_key])
-
- self.wall_dir_chooser.set_filename(bind_vars["wallpapers_dir"])
- self.vertical_switch.set_active(bind_vars.get("vertical", False))
- self.vertical_right_switch.set_active(
- bind_vars.get("vertical_right_align", True)
- )
- self.centered_switch.set_active(bind_vars.get("centered_bar", False))
- self.centered_switch.set_sensitive(self.vertical_switch.get_active())
- self.vertical_right_switch.set_sensitive(self.vertical_switch.get_active())
- self.dock_switch.set_active(bind_vars.get("dock_enabled", True))
- self.dock_hover_switch.set_active(
- bind_vars.get("dock_always_occluded", False)
- )
- self.dock_hover_switch.set_sensitive(self.dock_switch.get_active())
- self.dock_size_scale.value = bind_vars.get("dock_icon_size", 28)
- self.terminal_entry.set_text(bind_vars["terminal_command"])
- self.osd_switch.set_active(bind_vars.get("osd_enabled", True))
- self.bottom_bar_switch.set_active(bind_vars.get("bottom_bar", False))
-
- for component_name, switch in self.component_switches.items():
- config_key = f"bar_{component_name}_visible"
- switch.set_active(bind_vars.get(config_key, True))
-
- self.corners_switch.set_active(bind_vars.get("corners_visible", True))
-
- # Reset metrics visibility
- for k, s in self.metrics_switches.items():
- s.set_active(DEFAULTS["metrics_visible"][k])
-
- # Reset disk entries
- if True:
- for i in self.disk_entries.children:
- self.disk_entries.remove(i)
- bar = Box(orientation="h", spacing=10, h_align="start")
- entry = Entry(text="/", h_expand=True)
- bar.add(entry)
- x = Button(
- label="X", on_clicked=lambda _: self.disk_entries.remove(bar)
- )
- bar.add(x)
- self.disk_entries.add(bar)
-
- if self.lock_switch:
- self.lock_switch.set_active(False)
- if self.idle_switch:
- self.idle_switch.set_active(False)
-
- print("Settings reset to defaults.")
-
- def on_close(self, widget):
- """Close the settings window."""
- if self.application:
- self.application.quit()
- else:
- self.destroy()
-
-
-def start_config():
- """
- Run final configuration steps: ensure necessary configs, write the hyprconf, and reload.
- """
- ensure_matugen_config()
-
- hypr_config_dir = os.path.expanduser(f"~/{APP_NAME_CAP}/config/hypr/")
- os.makedirs(hypr_config_dir, exist_ok=True)
- hypr_conf_path = os.path.join(hypr_config_dir, f"{APP_NAME}.conf")
- try:
- with open(hypr_conf_path, "w") as f:
- f.write(generate_hyprconf())
- print(f"Generated Hyprland config at {hypr_conf_path}")
- except Exception as e:
- print(f"Error writing Hyprland config: {e}")
-
- try:
- subprocess.run(["hyprctl", "reload"], check=True, capture_output=True)
- print("Hyprland configuration reloaded.")
- except subprocess.CalledProcessError as e:
- print(f"Error reloading Hyprland: {e}\nstderr: {e.stderr.decode()}")
- except FileNotFoundError:
- print("Error: hyprctl command not found. Cannot reload Hyprland.")
- except Exception as e:
- print(f"An unexpected error occurred during hyprctl reload: {e}")
-
-
-def open_config():
- """
- Entry point for opening the configuration GUI using Fabric Application.
- """
- load_bind_vars()
-
- dest_lock = os.path.expanduser("~/.config/hypr/hyprlock.conf")
- src_lock = os.path.expanduser(f"~/{APP_NAME_CAP}/config/hypr/hyprlock.conf")
- show_lock_checkbox = True
- if not os.path.exists(dest_lock) and os.path.exists(src_lock):
- try:
- os.makedirs(os.path.dirname(dest_lock), exist_ok=True)
- shutil.copy(src_lock, dest_lock)
- show_lock_checkbox = False
- print(f"Copied default hyprlock config to {dest_lock}")
- except Exception as e:
- print(f"Error copying default hyprlock config: {e}")
- show_lock_checkbox = os.path.exists(src_lock)
-
- dest_idle = os.path.expanduser("~/.config/hypr/hypridle.conf")
- src_idle = os.path.expanduser(f"~/{APP_NAME_CAP}/config/hypr/hypridle.conf")
- show_idle_checkbox = True
- if not os.path.exists(dest_idle) and os.path.exists(src_idle):
- try:
- os.makedirs(os.path.dirname(dest_idle), exist_ok=True)
- shutil.copy(src_idle, dest_idle)
- show_idle_checkbox = False
- print(f"Copied default hypridle config to {dest_idle}")
- except Exception as e:
- print(f"Error copying default hypridle config: {e}")
- show_idle_checkbox = os.path.exists(src_idle)
-
- app = Application(f"{APP_NAME}-settings")
- window = HyprConfGUI(
- show_lock_checkbox=show_lock_checkbox,
- show_idle_checkbox=show_idle_checkbox,
- application=app,
- on_destroy=lambda *_: app.quit(),
- )
- app.add_window(window)
-
- window.show_all()
- app.run()
-
-
-if __name__ == "__main__":
- open_config()
diff --git a/config/data.py b/config/data.py
deleted file mode 100644
index f3b3271..0000000
--- a/config/data.py
+++ /dev/null
@@ -1,120 +0,0 @@
-import os
-import json
-import gi
-
-gi.require_version("Gtk", "3.0")
-from gi.repository import Gdk, GLib
-
-from fabric.utils import get_relative_path
-
-APP_NAME = "modus"
-APP_NAME_CAP = "Modus"
-
-CACHE_DIR = str(GLib.get_user_cache_dir()) + f"/{APP_NAME}"
-STATE_DIR = str(GLib.get_user_state_dir() + f"/{APP_NAME}")
-
-
-USERNAME = os.getlogin()
-HOSTNAME = os.uname().nodename
-HOME_DIR = os.path.expanduser("~")
-
-CONFIG_DIR = os.path.expanduser(f"~/{APP_NAME}")
-
-screen = Gdk.Screen.get_default()
-CURRENT_WIDTH = screen.get_width()
-CURRENT_HEIGHT = screen.get_height()
-
-WALLPAPERS_DIR_DEFAULT = get_relative_path("../assets/wallpaper")
-MATUGEN_STATE_FILE = os.path.join(CONFIG_DIR, "matugen")
-CONFIG_FILE = get_relative_path("../config/assets/config.json")
-
-if os.path.exists(CONFIG_FILE):
- with open(CONFIG_FILE, "r") as f:
- config = json.load(f)
- WALLPAPERS_DIR = config.get("wallpapers_dir", WALLPAPERS_DIR_DEFAULT)
- VERTICAL = config.get("vertical", False) # Use saved value or False as default
- VERTICAL_RIGHT_ALIGN = config.get("vertical_right_align", False) # Right side by default
- BOTTOM_BAR = config.get("bottom_bar", False) # Use saved value or False as default
- CENTERED_BAR = config.get("centered_bar", False) # Load centered bar setting
- TERMINAL_COMMAND = config.get(
- "terminal_command", "kitty -e"
- ) # Load terminal command
- DOCK_ENABLED = config.get("dock_enabled", True) # Load dock visibility setting
- DOCK_ALWAYS_OCCLUDED = config.get(
- "dock_always_occluded", False
- ) # Load dock hover-only setting
- DOCK_ICON_SIZE = config.get("dock_icon_size", 28) # Load dock icon size setting
- OSD_ENABLED = config.get("osd_enabled", True) # Load OSD visibility setting
-
- # Load bar component visibility settings
- BAR_COMPONENTS_VISIBILITY = {
- "button_app": config.get("bar_button_app_visible", True),
- "tray": config.get("bar_tray_visible", True),
- # "button_tools": config.get("bar_button_tools_visible", True),
- "workspaces": config.get("bar_workspaes_visible", True),
- "metrics": config.get("bar_metrics_visible", True),
- "language": config.get("bar_language_visible", True),
- "date_time": config.get("bar_date_time_visible", True),
- "updates": config.get("bar_updates_visible", True),
- "indicators": config.get("bar_indicators", True),
- }
-
- BAR_METRICS_DISKS = config.get("bar_metrics_disks", ["/"])
- METRICS_VISIBLE = config.get(
- "metrics_visible",
- {"cpu": True, "ram": True, "disk": True, "swap": True},
- )
-else:
- WALLPAPERS_DIR = WALLPAPERS_DIR_DEFAULT
- VERTICAL = False # Default value when no config exists
- VERTICAL_RIGHT_ALIGN = False # Default to right alignment
- BOTTOM_BAR = False # Default value for bottom bar
- CENTERED_BAR = False # Default value for centered bar
- DOCK_ENABLED = True # Default value for dock visibility
- DOCK_ALWAYS_OCCLUDED = False # Default value for dock hover-only mode
- OSD_ENABLED = True # Default value for OSD visibility
- TERMINAL_COMMAND = "kitty -e" # Default terminal command when no config
- DOCK_ICON_SIZE = 28 # Default dock icon size when no config
-
- # Default values for component visibility (all visible)
- BAR_COMPONENTS_VISIBILITY = {
- "button_aps": True,
- "tray": True,
- # "button_tools": True,
- "workspaces": True,
- "metrics": True,
- "language": True,
- "date_time": True,
- "indicators": True,
- "updates": True,
- }
-
- BAR_METRICS_DISKS = ["/"]
- METRICS_VISIBLE = {
- "cpu": True,
- "ram": True,
- "disk": True,
- "swap": True,
- }
-
-
-def read_config():
- config_path = get_relative_path("./assets/modus.json")
-
- with open(config_path, "r") as file:
- # Load JSON data into a Python dictionary
- data = json.load(file)
- return data
-
-
-def load_config():
- """Load the configuration from config.json"""
- config_path = get_relative_path("./assets/config.json")
- config = {}
- if os.path.exists(config_path):
- try:
- with open(config_path, "r") as f:
- config = json.load(f)
- except Exception as e:
- print(f"Error loading config: {e}")
- return config
diff --git a/config/hypr/colors.conf b/config/hypr/colors.conf
deleted file mode 100644
index e839fe7..0000000
--- a/config/hypr/colors.conf
+++ /dev/null
@@ -1,11 +0,0 @@
-$wallpaper = /home/sankalp/Pictures/wallpapers/background.png
-$background = 0e1514
-$foreground = dde4e2
-
-$primary = 81d5ca
-$secondary = b1ccc7
-$tertiary = aec9e6
-$surface = 0e1514
-$surface_bright = 343a39
-$outline = 899390
-$error = ff5945
diff --git a/config/hypr/hypridle.conf b/config/hypr/hypridle.conf
deleted file mode 100644
index 48bd7d7..0000000
--- a/config/hypr/hypridle.conf
+++ /dev/null
@@ -1,41 +0,0 @@
-
-listener {
- timeout = 500 # in seconds.
- on-timeout = notify-send "You are idle!" # command to run when timeout has passed.
- on-resume = notify-send "Welcome back!" # command to run when activity is detected after timeout has fired.
-}
-
-general {
- lock_cmd = pidof hyprlock || hyprlock # avoid starting multiple hyprlock instances.
- before_sleep_cmd = loginctl lock-session # lock before suspend.
- after_sleep_cmd = hyprctl dispatch dpms on # to avoid having to press a key twice to turn on the display.
-}
-
-listener {
- timeout = 150 # 2.5min.
- on-timeout = brightnessctl -s set 10 # set monitor backlight to minimum, avoid 0 on OLED monitor.
- on-resume = brightnessctl -r # monitor backlight restore.
-}
-
-# turn off keyboard backlight, comment out this section if you dont have a keyboard backlight.
-listener {
- timeout = 150 # 2.5min.
- on-timeout = brightnessctl -sd rgb:kbd_backlight set 0 # turn off keyboard backlight.
- on-resume = brightnessctl -rd rgb:kbd_backlight # turn on keyboard backlight.
-}
-
-listener {
- timeout = 300 # 5min
- on-timeout = loginctl lock-session # lock screen when timeout has passed
-}
-
-listener {
- timeout = 330 # 5.5min
- on-timeout = hyprctl dispatch dpms off # screen off when timeout has passed
- on-resume = hyprctl dispatch dpms on # screen on when activity is detected after timeout has fired.
-}
-
-listener {
- timeout = 1800 # 30min
- on-timeout = systemctl suspend # suspend pc
-}
diff --git a/config/hypr/hyprlock.conf b/config/hypr/hyprlock.conf
deleted file mode 100644
index 80977af..0000000
--- a/config/hypr/hyprlock.conf
+++ /dev/null
@@ -1,150 +0,0 @@
-
-
-source = ~/Modus/config/hypr/colors.conf
-
-# Configuration
- # (general settings)
- general {
- grace = 2
- hide_cursor = true
- ignore_empty_input = true
- text_trim = true
- }
- # (background config)
- background {
- monitor =
- path = $wallpaper # only png supported for now
- # color = rgba(26,17,16, 1.0)
- blur_passes = 3 # 0 disables blurring
- blur_size = 1
- noise = 0.01
- contrast = 0.8916
- brightness = 0.7
- vibrancy = 0.1696
- vibrancy_darkness = 0.0
- }
-
-
-# Password input
- input-field {
- monitor =
- size = 256, 48
- outline_thickness = -1
- dots_size = 0.33
- dots_spacing = 0.45
- dots_center = true
- dots_rounding = -1
- outer_color = rgba(0,0,0,0)
- inner_color = rgba(0,0,0,0)
- font_color = 0xff$foreground
- fade_on_empty = true
- fade_timeout = 1000
- font_family = Product Sans
- placeholder_text =
- hide_input = false
- rounding = 4
- check_color = 0xff$tertiary
- fail_color = 0xff$error
- capslock_color = 0xff$secondary
- fail_text = $FAIL ($ATTEMPTS)
- fail_transition = 200
- capslock_color = -1
- numlock_color = -1
- bothlock_color = -1
- invert_numlock = false
- swap_font_color = false
- position = 0, 150
- halign = center
- valign = bottom
- }
-#
-
-
-# Time
- # (hours)
- label {
- monitor =
- text = cmd[update:1000] echo -e "$(date +"%H")"
- color = 0xff$foreground
- shadow_pass = 2
- shadow_size = 3
- shadow_color = rgb(0,0,0)
- shadow_boost = 1.2
- font_size = 150
- font_family = Product Sans Light
- position = 0, 90
- halign = center
- valign = center
- }
- # (minutes)
- label {
- monitor =
- text = cmd[update:1000] echo -e "$(date +"%M")"
- color = 0xff$foreground
- font_size = 150
- font_family = Product Sans Light
- position = 0, -90
- halign = center
- valign = center
- }
-#
-
-# Date
- # (label)
- label {
- monitor =
- text = cmd[update:1000] echo -e "$(date +"%A, %B %d")"
- color = 0xff$foreground
- font_size = 15
- font_family = Product Sans Medium
- position = 100, -75
- shadow_passes = 5
- shadow_size = 10
- halign = left
- valign = top
- }
- # (icon)
- label {
- monitor =
- text = calendar_today
- color = 0xff$foreground
- font_size = 15
- font_family = Material Symbols Outlined
- position = 75, -75
- shadow_passes = 5
- shadow_size = 10
- halign = left
- valign = top
- }
-#
-
-# Input layout
- # (label)
- label {
- monitor =
- text = $LAYOUT
- color = 0xff$foreground
- font_size = 15
- font_family = Product Sans Medium
- position = 100, -110
- shadow_passes = 5
- shadow_size = 10
- halign = left
- valign = top
- }
- # (icon)
- label {
- monitor =
- text = keyboard
- color = 0xff$foreground
- font_size = 15
- font_family = Material Symbols Outlined
- position = 75, -112
- shadow_passes = 5
- shadow_size = 10
- halign = left
- valign = top
- }
-#
-
-
diff --git a/config/hypr/modus.conf b/config/hypr/modus.conf
deleted file mode 100644
index edd9ec3..0000000
--- a/config/hypr/modus.conf
+++ /dev/null
@@ -1,88 +0,0 @@
-# Generated by Modus - DO NOT EDIT MANUALLY
-exec-once = python /home/sankalp/Modus/main.py
-exec = pgrep -x "hypridle" > /dev/null || hypridle
-exec = swww-daemon
-exec-once = wl-paste --type text --watch cliphist store
-exec-once = wl-paste --type image --watch cliphist store
-
-$fabricSend = fabric-cli exec modus
-$scriptsDir = /home/sankalp/Modus/config/scripts/
-
-# Key Bindings
-bind = SHIFT ALT, T, exec, killall modus; python /home/sankalp/Modus/main.py # Reload Modus
-bind = SHIFT ALT, B, exec, $fabricSend 'launcher.open("bluetooth")' # Bluetooth
-bind = SUPER, N, exec, $fabricSend 'launcher.open("notification-center")' # Notifications
-bind = SUPER, E, exec, $fabricSend 'launcher.open("emoji")' # Emoji
-bind = SUPER, V, exec, $fabricSend 'launcher.open("cliphist")' # Clipboard History
-bind = SUPER, T, exec, $fabricSend 'launcher.open("kanban")' # Kanban
-bind = SUPER, Z, exec, $fabricSend 'launcher.open("tmux")' # Tmux Manager
-bind = SUPER, S, exec, $fabricSend 'launcher.open("tools")' # Toolbox
-bind = SUPER, W, exec, $fabricSend 'launcher.open("wallpapers")' # Wallpaper Selector
-bind = SUPER, D, exec, $fabricSend 'launcher.open("launcher")' # App Launcher
-bind = ALT, W, exec, $fabricSend 'launcher.open("window-switcher")' # Window Switcher
-bind = SUPER, X, exec, $fabricSend 'launcher.open("power")' # Power Menu
-bind = SUPER SHIFT, T, exec, killall modus; GTK_DEBUG=interactive python /home/sankalp/Modus/main.py # Restart with inspector
-
-# Wallpapers directory: /home/sankalp/Pictures/wallpapers
-
-# Source color scheme
-source = /home/sankalp/Modus/config/hypr/colors.conf
-
-general {
- col.active_border = 0xff$primary
- col.inactive_border = 0xff$surface
- gaps_in = 2
- gaps_out = 4
- border_size = 2
- layout = dwindle
-}
-
-decoration {
- blur {
- enabled = yes
- size = 5
- passes = 3
- new_optimizations = yes
- contrast = 1
- brightness = 1
- }
- rounding = 14
- shadow {
- enabled = true
- range = 10
- render_power = 2
- color = rgba(0, 0, 0, 0.25)
- }
-}
-
-animations {
- enabled = true
- # Animation curves
- bezier = linear, 0, 0, 1, 1
- bezier = md3_standard, 0.2, 0, 0, 1
- bezier = md3_decel, 0.05, 0.7, 0.1, 1
- bezier = md3_accel, 0.3, 0, 0.8, 0.15
- bezier = overshot, 0.05, 0.9, 0.1, 1.1
- bezier = crazyshot, 0.1, 1.5, 0.76, 0.92
- bezier = hyprnostretch, 0.05, 0.9, 0.1, 1.0
- bezier = menu_decel, 0.1, 1, 0, 1
- bezier = menu_accel, 0.38, 0.04, 1, 0.07
- bezier = easeInOutCirc, 0.85, 0, 0.15, 1
- bezier = easeOutCirc, 0, 0.55, 0.45, 1
- bezier = easeOutExpo, 0.16, 1, 0.3, 1
- bezier = softAcDecel, 0.26, 0.26, 0.15, 1
- bezier = md2, 0.4, 0, 0.2, 1
-
- animation = windows, 1, 3, md3_decel, popin 60%
- animation = windowsIn, 1, 3, md3_decel, popin 60%
- animation = windowsOut, 1, 3, md3_accel, popin 60%
- animation = border, 1, 10, default
- animation = fade, 1, 3, md3_decel
- animation = layers, 1, 2, md3_decel, slide
- animation = layersIn, 1, 3, menu_decel, slide
- animation = layersOut, 1, 1.6, menu_accel, slide
- animation = fadeLayersIn, 1, 2, menu_decel
- animation = fadeLayersOut, 1, 4.5, menu_accel
- animation = workspaces, 1, 7, menu_decel, slide
- animation = specialWorkspace, 1, 3, md3_decel, slidevert
-}
diff --git a/config/matugen/templates/hyprland-colors.conf b/config/matugen/templates/hyprland-colors.conf
deleted file mode 100644
index a375c24..0000000
--- a/config/matugen/templates/hyprland-colors.conf
+++ /dev/null
@@ -1,11 +0,0 @@
-$wallpaper = {{image}}
-$background = {{colors.background.default.hex_stripped}}
-$foreground = {{colors.on_background.default.hex_stripped}}
-
-$primary = {{colors.primary.default.hex_stripped}}
-$secondary = {{colors.secondary.default.hex_stripped}}
-$tertiary = {{colors.tertiary.default.hex_stripped}}
-$surface = {{colors.surface.default.hex_stripped}}
-$surface_bright = {{colors.surface_bright.default.hex_stripped}}
-$outline = {{colors.outline.default.hex_stripped}}
-$error = {{colors.error.default.hex_stripped | set_lightness: -20.0}}
diff --git a/config/matugen/templates/modus.css b/config/matugen/templates/modus.css
deleted file mode 100644
index db192d7..0000000
--- a/config/matugen/templates/modus.css
+++ /dev/null
@@ -1,44 +0,0 @@
-:vars {
- --foreground: {{colors.on_background.default.hex}};
- --background: {{colors.background.default.hex}};
- --cursor: {{colors.on_background.default.hex}};
- --primary: {{colors.primary.default.hex}};
- --primary-container: {{colors.primary_container.default.hex}};
- --on-primary: {{colors.on_primary.default.hex}};
- --on-primary-container: {{colors.on_primary_container.default.hex}};
- --secondary: {{colors.secondary.default.hex}};
- --on-secondary: {{colors.on_secondary.default.hex}};
- --secondary-container: {{colors.secondary_container.default.hex}};
- --tertiary: {{colors.tertiary.default.hex}};
- --tertiary-container: {{colors.tertiary_container.default.hex}};
- --on-tertiary: {{colors.on_tertiary.default.hex}};
- --on-tertiary-container: {{colors.on_tertiary_container.default.hex}};
- --surface: {{colors.surface.default.hex}};
- --surface-container: {{colors.surface_container.default.hex}};
- --surface-bright: {{colors.surface_bright.default.hex}};
- --surface-variant: {{colors.surface_variant.default.hex}};
- --surface-container-low: {{colors.surface_container_low.default.hex}};
- --on-surface: {{colors.on_surface.default.hex}};
- --error: {{colors.error.default.hex}};
- --error-dim: {{colors.error.default.hex | set_lightness: -10.0}};
- --on-error: {{colors.on_error.default.hex}};
- --error-container: {{colors.error_container.default.hex}};
- --on-error-container: {{colors.on_error_container.default.hex}};
- --outline: {{colors.outline.default.hex}};
- --outline-variant: {{colors.outline_variant.default.hex}};
- --shadow: {{colors.shadow.default.hex}};
- --red: {{colors.red.default.hex}};
- --red-dim: {{colors.red.default.hex | set_lightness: -10.0}};
- --green: {{colors.green.default.hex}};
- --green-dim: {{colors.green.default.hex | set_lightness: -10.0}};
- --yellow: {{colors.yellow.default.hex}};
- --yellow-dim: {{colors.yellow.default.hex | set_lightness: -10.0}};
- --blue: {{colors.blue.default.hex}};
- --blue-dim: {{colors.blue.default.hex | set_lightness: -10.0}};
- --magenta: {{colors.magenta.default.hex}};
- --magenta-dim: {{colors.magenta.default.hex | set_lightness: -10.0}};
- --cyan: {{colors.cyan.default.hex}};
- --cyan-dim: {{colors.cyan.default.hex | set_lightness: -10.0}};
- --white: {{colors.white.default.hex}};
-
-}
diff --git a/config/matugen/templates/modus.scss b/config/matugen/templates/modus.scss
deleted file mode 100644
index aba4239..0000000
--- a/config/matugen/templates/modus.scss
+++ /dev/null
@@ -1,29 +0,0 @@
-@use "sass:color";
-<* for name, value in colors *>
-${{name}}: {{value.default.hex}};
-<* endfor *>
-
-// Custom Color
-$red: {{colors.red.default.hex}};
-$red-dim: {{colors.red.default.hex | set_lightness: -10.0}};
-$green: {{colors.green.default.hex}};
-$green-dim: {{colors.green.default.hex | set_lightness: -10.0}};
-$yellow: {{colors.yellow.default.hex}};
-$yellow-dim: {{colors.yellow.default.hex | set_lightness: -10.0}};
-$blue: {{colors.blue.default.hex}};
-$blue-dim: {{colors.blue.default.hex | set_lightness: -10.0}};
-$magenta: {{colors.magenta.default.hex}};
-$magenta-dim: {{colors.magenta.default.hex | set_lightness: -10.0}};
-$cyan: {{colors.cyan.default.hex}};
-$cyan-dim: {{colors.cyan.default.hex | set_lightness: -10.0}};
-$white: {{colors.white.default.hex}};
-
-$theme: 'dark';
-$surface_transparent: color.scale($surface, $alpha: -20%);
-$inverse_surface_transparent: color.scale($inverse_surface, $alpha: -92%);
-$inverse_surface_transparent_variant: color.scale(
- $inverse_surface,
- $alpha: -82%
-);
-$inverse_surface_transparent_overlay: linear-gradient(0deg, $inverse_surface_transparent, $inverse_surface_transparent);
-$inverse_surface_transparent_variant_overlay: linear-gradient(0deg, $inverse_surface_transparent_variant, $inverse_surface_transparent_variant);
diff --git a/config/scripts/capture.sh b/config/scripts/capture.sh
deleted file mode 100755
index 8204108..0000000
--- a/config/scripts/capture.sh
+++ /dev/null
@@ -1,166 +0,0 @@
-#!/bin/bash
-
-# Print usage information
-print_usage() {
- cat <<"EOF"
-Usage: screen-tools.sh [mode] [option]
-Modes:
- screenshot [option] Take a screenshot
- Options:
- p Print all screens
- s Snip current screen
- sf Snip current screen (frozen)
- m Print focused monitor
- [mockup] Optional: Add mockup effect to screenshot
-
- record Start/Stop screen recording
-
- ocr Perform OCR on selected area
-EOF
-}
-
-# OCR functionality
-do_ocr() {
- sleep 0.25
-
- ocr_text=$(grimblast --freeze save area - | tesseract -l eng - - 2>/dev/null)
-
- # Check if OCR was successful
- if [[ -n "$ocr_text" ]]; then
- echo -n "$ocr_text" | wl-copy
- notify-send -a "Modus" "OCR Success" "Text Copied to Clipboard"
- else
- notify-send -a "Modus" "OCR Failed" "No text recognized or operation failed"
- fi
-}
-
-# Screen recording functionality
-do_screenrecord() {
-
- if [ -z "$XDG_VIDEOS_DIR" ]; then
- XDG_VIDEOS_DIR="$HOME/Videos"
-
- fi
- local save_dir="${XDG_VIDEOS_DIR:-$HOME/Videos}/Recordings"
- mkdir -p "$save_dir"
-
- if pgrep -f "gpu-screen-recorder" >/dev/null; then
- pkill -SIGINT -f "gpu-screen-recorder"
- sleep 1
-
- local last_video=$(ls -t "$save_dir"/*.mp4 2>/dev/null | head -n 1)
- local action=$(notify-send -a "Modus" "โฌ Recording stopped" \
- -A "view=View" -A "open=Open folder")
-
- if [ "$action" = "view" ] && [ -n "$last_video" ]; then
- xdg-open "$last_video"
- elif [ "$action" = "open" ]; then
- xdg-open "$save_dir"
- fi
- exit 0
- fi
-
- local output_file="$save_dir/$(date +%Y-%m-%d-%H-%M-%S).mp4"
- notify-send -a "Modus" "๐ด Recording started"
- gpu-screen-recorder -w screen -q ultra -a default_output -ac opus -cr full -f 60 -o "$output_file"
-}
-
-# Screenshot functionality
-do_screenshot() {
- sleep 0.25
-
- if [ -z "$XDG_PICTURES_DIR" ]; then
- XDG_PICTURES_DIR="$HOME/Pictures"
- fi
-
- local save_dir="${3:-$XDG_PICTURES_DIR/Screenshots}"
- local save_file=$(date +'%y%m%d_%Hh%Mm%Ss_screenshot.png')
- local full_path="$save_dir/$save_file"
- mkdir -p "$save_dir"
-
- local mockup_mode="$2"
-
- case $1 in
- p) grimblast copysave screen "$full_path" ;;
- s) grimblast copysave area "$full_path" ;;
- sf) grimblast --freeze copysave area "$full_path" ;;
- m) grimblast copysave output "$full_path" ;;
- *)
- echo "Invalid screenshot mode"
- print_usage
- exit 1
- ;;
- esac
-
- if [ -f "$full_path" ]; then
- if [ "$mockup_mode" = "mockup" ]; then
- local temp_file="${full_path%.png}_temp.png"
- cropped_file="${full_path%.png}_cropped.png"
- local mockup_file="${full_path%.png}_mockup.png"
-
- # First crop the top pixel using a different approach
-
- # Use +0+1 to start at y-coordinate 1 (skipping the first pixel row)
-
- magick "$full_path" -crop +0+1 +repage "$cropped_file"
-
- magick "$cropped_file" \
- \( +clone -alpha extract -draw 'fill black polygon 0,0 0,20 20,0 fill white circle 20,20 20,0' \
- \( +clone -flip \) -compose Multiply -composite \
- \( +clone -flop \) -compose Multiply -composite \
- \) -alpha off -compose CopyOpacity -composite "$temp_file"
-
- magick "$temp_file" \
- \( +clone -background black -shadow 60x20+0+10 -alpha set -channel A -evaluate multiply 1 +channel \) \
- +swap -background none -layers merge +repage "$mockup_file"
-
- rm "$temp_file" "$cropped_file"
- mv "$mockup_file" "$full_path"
-
- if command -v wl-copy >/dev/null 2>&1; then
- wl-copy <"$full_path"
- elif command -v xclip >/dev/null 2>&1; then
- xclip -selection clipboard -t image/png <"$full_path"
- fi
- fi
-
- local action=$(notify-send -a "Modus" -i "$full_path" "Screenshot saved" "in $full_path" \
- -A "view=View" -A "edit=Edit" -A "open=Open Folder")
-
- case "$action" in
- view) xdg-open "$full_path" ;;
- edit) swappy -f "$full_path" ;;
- open) xdg-open "$save_dir" ;;
- esac
- else
- notify-send -a "Modus" "Screenshot Aborted"
- fi
-}
-
-# Handle command line arguments
-if [ $# -eq 0 ]; then
- print_usage
- exit 1
-fi
-
-case "$1" in
-screenshot)
- if [ $# -lt 2 ]; then
- echo "Error: Screenshot mode requires an option"
- print_usage
- exit 1
- fi
- do_screenshot "$2" "$3"
- ;;
-record)
- do_screenrecord
- ;;
-ocr)
- do_ocr
- ;;
-*)
- echo "Error: Invalid mode '$1'"
- print_usage
- exit 1
- ;;
-esac
diff --git a/config/scripts/gamemode.sh b/config/scripts/gamemode.sh
deleted file mode 100755
index 3652ccd..0000000
--- a/config/scripts/gamemode.sh
+++ /dev/null
@@ -1,40 +0,0 @@
-#!/usr/bin/env sh
-
-# Check if animations are disabled (game mode is active)
-check_gamemode() {
- HYPRGAMEMODE=$(hyprctl getoption animations:enabled | awk 'NR==1{print $2}')
- if [ "$HYPRGAMEMODE" = 0 ]; then
- echo "t"
- return 0
- else
- echo "f"
- return 1
- fi
-}
-
-# Toggle game mode state
-toggle_gamemode() {
- HYPRGAMEMODE=$(hyprctl getoption animations:enabled | awk 'NR==1{print $2}')
- if [ "$HYPRGAMEMODE" = 1 ]; then
- hyprctl --batch "\
- keyword animations:enabled 0;\
- keyword decoration:shadow:enabled 0;\
- keyword decoration:blur:enabled 0;\
- keyword general:gaps_in 0;\
- keyword general:gaps_out 0;\
- keyword general:border_size 1;\
- keyword decoration:rounding 0"
- exit
- fi
- hyprctl reload
-}
-
-# Main script logic
-case "$1" in
-check)
- check_gamemode
- ;;
-*)
- toggle_gamemode
- ;;
-esac
diff --git a/config/scripts/hyprpicker.sh b/config/scripts/hyprpicker.sh
deleted file mode 100755
index 703c7df..0000000
--- a/config/scripts/hyprpicker.sh
+++ /dev/null
@@ -1,75 +0,0 @@
-#!/bin/bash
-
-pick_rgb() {
-
- # Execute hyprpicker with RGB format and save the output to a variable
- hyprpicker -a -n -f rgb && sleep 0.1
-
- # Create a temporal 64x64 PNG file with the color in /tmp using convert
- magick -size 64x64 xc:"rgb($(wl-paste))" /tmp/color.png
-
- # Send a notification using the file as an icon
- notify-send "RGB color picked" "rgb($(wl-paste))" -i /tmp/color.png -a "Hyprpicker"
-
- # Remove the temporal file
- rm /tmp/color.png
-
- # Exit
- exit 0
-
-}
-
-pick_hex() {
-
- # Execute hyprpicker and save the output to a variable
- hyprpicker -a -n -f hex && sleep 0.1
-
- # Create a temporal 64x64 PNG file with the color in /tmp using convert
- magick -size 64x64 xc:"$(wl-paste)" /tmp/color.png
-
- # Send a notification using the file as an icon
- notify-send "HEX color picked" "$(wl-paste)" -i /tmp/color.png -a "Hyprpicker"
-
- # Remove the temporal file
- rm /tmp/color.png
-
- # Exit
- exit 0
-
-}
-
-pick_hsv() {
-
- # Copy the color to the clipboard
- echo -n "$(hyprpicker -n -f hsv)" | wl-copy -n
-
- # Create a temporal 64x64 PNG file with the color in /tmp using convert
- magick -size 64x64 xc:"hsv($(wl-paste))" /tmp/color.png
-
- # Send a notification using the file as an icon
- notify-send "HSV color picked" "hsv($(wl-paste))" -i /tmp/color.png -a "Hyprpicker"
-
- # Remove the temporal file
- rm /tmp/color.png
-
- # Exit
- exit 0
-
-}
-
-case "$1" in
--rgb)
- pick_rgb
- ;;
--hsv)
- pick_hsv
- ;;
--hex)
- pick_hex
- ;;
-
-*)
- echo "Usage: $0 [-rgb|-hex|-hsv]"
- exit 1
- ;;
-esac
diff --git a/config/scripts/systemupdates.sh b/config/scripts/systemupdates.sh
deleted file mode 100755
index a2677ff..0000000
--- a/config/scripts/systemupdates.sh
+++ /dev/null
@@ -1,182 +0,0 @@
-#!/bin/bash
-
-# Word of caution: Changing this may break the update module, be careful!
-
-# Function to check updates for Arch-based distros
-check_arch_updates() {
- official_updates=0
- aur_updates=0
- flatpak_updates=0
- tooltip=""
-
- # Get the number of official updates
- official_updates=$(checkupdates 2>/dev/null | wc -l)
-
- # Determine the AUR helper
- if command -v paru &>/dev/null; then
- aur_helper="paru"
- else
- aur_helper="yay"
- fi
-
- # Get the number of AUR updates
- aur_updates=$($aur_helper -Qum 2>/dev/null | wc -l)
-
- # Check for Flatpak updates
- if command -v flatpak &>/dev/null; then
- flatpak_updates=$(flatpak remote-ls --updates | wc -l)
- fi
-
- # Calculate total updates
- total_updates=$((official_updates + aur_updates + flatpak_updates))
-
- # Build the tooltip
- tooltip="๓ฐฃ Official: $official_updates\n๓ฐฎฏ AUR: $aur_updates\n๏ Flatpak: $flatpak_updates"
-
- # Output JSON
- echo "{\"total\":\"$total_updates\", \"tooltip\":\"$tooltip\"}"
-}
-
-# Function to check updates for Ubuntu-based distros
-check_ubuntu_updates() {
- official_updates=0
- flatpak_updates=0
- tooltip=""
-
- # Get the number of official updates
- official_updates=$(apt-get -s -o Debug::NoLocking=true upgrade | grep -c ^Inst)
-
- # Check for Flatpak updates
- if command -v flatpak &>/dev/null; then
- flatpak_updates=$(flatpak remote-ls --updates | wc -l)
- fi
-
- # Calculate total updates
- total_updates=$((official_updates + flatpak_updates))
-
- # Build the tooltip
- tooltip="๓ฐ Official: $official_updates\n๏ Flatpak: $flatpak_updates"
-
- # Output JSON
- echo "{\"total\":\"$total_updates\", \"tooltip\":\"$tooltip\"}"
-}
-
-# Function to check updates for Fedora-based distros
-check_fedora_updates() {
- official_updates=0
- flatpak_updates=0
- tooltip=""
-
- # Get the number of official updates
- official_updates=$(dnf list updates -q | tail -n +2 | wc -l)
-
- # Check for Flatpak updates
- if command -v flatpak &>/dev/null; then
- flatpak_updates=$(flatpak remote-ls --updates | wc -l)
- fi
-
- # Calculate total updates
- total_updates=$((official_updates + flatpak_updates))
-
- # Build the tooltip
- tooltip="๓ฐฃ Official: $official_updates\n๏ Flatpak: $flatpak_updates"
-
- # Output JSON
- echo "{\"total\":\"$total_updates\", \"tooltip\":\"$tooltip\"}"
-}
-
-# Function to check updates for openSUSE-based distros
-check_opensuse_updates() {
- official_updates=0
- flatpak_updates=0
- tooltip=""
-
- # Get the number of official updates (skip header lines)
- official_updates=$(zypper lu | tail -n +3 | wc -l)
-
- # Check for Flatpak updates
- if command -v flatpak &>/dev/null; then
- flatpak_updates=$(flatpak remote-ls --updates | wc -l)
- fi
-
- # Calculate total updates
- total_updates=$((official_updates + flatpak_updates))
-
- # Build the tooltip
- tooltip="๎ฝญ Official: $official_updates\n๏ Flatpak: $flatpak_updates"
-
- # Output JSON
- echo "{\"total\":\"$total_updates\", \"tooltip\":\"$tooltip\"}"
-}
-
-# Function to update Arch-based distros
-update_arch() {
- kitty --title "System Update" sh -c "
- fastfetch
- yay -Syu
- read -n 1 -p 'Press any key to continue...'
- "
-}
-
-# Function to update Ubuntu-based distros
-update_ubuntu() {
- kitty --title "System Update" sh -c "
- neofetch
- sudo apt update && sudo apt upgrade -y
- read -n 1 -p 'Press any key to continue...'
- "
-}
-
-# Function to update Fedora-based distros
-update_fedora() {
- kitty --title "System Update" sh -c "
- neofetch
- sudo dnf upgrade -y
- read -n 1 -p 'Press any key to continue...'
- "
-}
-
-# Function to update openSUSE-based distros
-update_opensuse() {
- kitty --title "System Update" sh -c "
- neofetch
- sudo zypper up -y
- read -n 1 -p 'Press any key to continue...'
- "
-}
-
-# Main case handling for different distributions
-case "$1" in
--arch)
- if [ -z "$2" ]; then
- check_arch_updates
- else
- update_arch
- fi
- ;;
--ubuntu)
- if [ -z "$2" ]; then
- check_ubuntu_updates
- else
- update_ubuntu
- fi
- ;;
--fedora)
- if [ -z "$2" ]; then
- check_fedora_updates
- else
- update_fedora
- fi
- ;;
--suse)
- if [ -z "$2" ]; then
- check_opensuse_updates
- else
- update_opensuse
- fi
- ;;
-*)
- echo "Usage: $0 [-arch|-ubuntu|-fedora|-suse] [up (optional)]"
- exit 1
- ;;
-esac
diff --git a/main.py b/main.py
deleted file mode 100644
index 7488f5d..0000000
--- a/main.py
+++ /dev/null
@@ -1,61 +0,0 @@
-import os
-
-import setproctitle
-from fabric import Application
-from fabric.utils import get_relative_path, monitor_file, exec_shell_command_async
-from loguru import logger
-
-from modules.bar.bar import Bar, Corners
-from modules.launcher.launcher import Launcher
-
-from modules.dock import Dock
-from modules.notification_popup import NotificationPopup
-from modules.osd import OSD
-from config.data import APP_NAME, CACHE_DIR, CONFIG_FILE, DOCK_ICON_SIZE, VERTICAL, APP_NAME_CAP
-
-
-for log in [
- "fabric.hyprland.widgets",
- "fabric.audio.service",
- "fabric.bluetooth.service",
-]:
- logger.disable(log)
-
-
-
-if __name__ == "__main__":
- setproctitle.setproctitle(APP_NAME)
-
- if not os.path.isfile(CONFIG_FILE):
- exec_shell_command_async(f"python {get_relative_path('../config/config.py')}")
- bar = Bar()
- dock = Dock()
- corners = Corners()
- osd = OSD()
- notif = NotificationPopup()
- launcher = Launcher()
- bar.launcher = launcher
- launcher.bar = bar
-
- app = Application(f"{APP_NAME}", bar, launcher, osd, dock)
-
- # Monitor CSS files for changes
- css_file = monitor_file(get_relative_path("styles"))
- _ = css_file.connect("changed", lambda *_: set_css())
-
- color_css_file = monitor_file(get_relative_path("./styles/colors.css"))
- _ = color_css_file.connect("changed", lambda *_: set_css())
-
- def set_css():
- logger.info("[Main] Applying CSS")
- app.set_stylesheet_from_file(get_relative_path("styles/main.css"),
- exposed_functions={
- "dock_nmargin": lambda: f"margin-bottom: -{32 + DOCK_ICON_SIZE}px;" if not VERTICAL else f"margin-right: -{32 + DOCK_ICON_SIZE}px;",
- "dock_sep": lambda: f"margin: 8px 0;" if not VERTICAL else f"margin: 0 8px;",
- },)
-
- app.set_css = set_css
-
- app.set_css()
-
- app.run()
diff --git a/modules/bar/bar.py b/modules/bar/bar.py
deleted file mode 100644
index 46a9d5f..0000000
--- a/modules/bar/bar.py
+++ /dev/null
@@ -1,312 +0,0 @@
-from fabric.hyprland.widgets import HyprlandLanguage, get_hyprland_connection
-from fabric.hyprland.service import HyprlandEvent
-from fabric.system_tray.widgets import SystemTray
-from fabric.widgets.shapes import Corner
-from fabric.widgets.box import Box
-from fabric.widgets.button import Button
-from fabric.widgets.centerbox import CenterBox
-from fabric.widgets.datetime import DateTime
-from fabric.widgets.label import Label
-from utils.wayland import WaylandWindow as Window
-from modules.bar.components import Metrics, workspace, SystemIndicators, UpdatesWidget
-import config.data as data
-from gi.repository import Gdk, GLib
-import utils.icons as icons
-import os
-import json
-
-
-class Bar(Window):
- def __init__(self, **kwargs):
- super().__init__(
- name="bar",
- layer="top",
- anchor="left top right"
- if not data.VERTICAL and not data.BOTTOM_BAR
- else "top right bottom"
- if data.VERTICAL and data.VERTICAL_RIGHT_ALIGN
- else "top left bottom"
- if data.VERTICAL
- else "bottom left right",
- exclusivity="auto",
- visible=True,
- all_visible=True,
- )
-
- self.launcher = kwargs.get("launcher", None)
- self.component_visibility = data.BAR_COMPONENTS_VISIBILITY
-
- # Determine if we should use vertical layout for components
- is_vertical_layout = data.VERTICAL
-
- self.workspaces = Button(
- child=workspace,
- name="workspaces",
- )
-
- self.connection = get_hyprland_connection()
- self.tray = SystemTray(
- name="tray",
- spacing=4,
- icon_size=16,
- orientation="h" if not is_vertical_layout else "v",
- )
-
- self.date_time = Button(
- name="datetime",
- child=DateTime(
- formatters=["%-I:%M:%p ๓ฐง %a %d %b"]
- if not is_vertical_layout
- else ["%I\n%M\n%p"],
- h_align="center" if not is_vertical_layout else "fill",
- v_align="center",
- ),
- on_clicked=lambda *_: self.calendar(),
- )
- self.date_time.connect("enter_notify_event", self.on_button_enter)
- self.date_time.connect("leave_notify_event", self.on_button_leave)
-
- self.button_app = Button(
- name="button-bar",
- child=Label(name="button-bar-label", markup=icons.apps),
- on_clicked=lambda *_: self.search_apps(),
- )
- self.button_app.connect("enter_notify_event", self.on_button_enter)
- self.button_app.connect("leave_notify_event", self.on_button_leave)
-
- self.updates = UpdatesWidget()
- self.updates.connect("enter_notify_event", self.on_button_enter)
- self.updates.connect("leave_notify_event", self.on_button_leave)
-
- self.lang_label = Label(name="lang-label")
- self.language = Button(
- name="language", h_align="center", v_align="center", child=self.lang_label
- )
- self.on_language_switch()
- self.connection.connect("event::activelayout", self.on_language_switch)
-
- self.metrics = Metrics()
- self.indicators = SystemIndicators(launcher=self.launcher)
-
- self.applets = Box(
- name="system-indicator",
- spacing=4,
- orientation="h" if not is_vertical_layout else "v",
- children=[self.language, self.indicators],
- )
-
- # Apply visibility settings to components
- self.apply_component_visibility()
-
- self.h_start_children = [self.button_app, self.updates]
-
- self.h_center_children = [
- self.date_time,
- self.workspaces,
- self.metrics,
- self.applets,
- ]
-
- self.h_end_children = [self.tray]
-
- self.v_start_children = [
- self.button_app,
- self.updates,
- self.workspaces,
- self.metrics,
- ]
- self.v_end_children = [self.tray, self.applets, self.date_time]
-
- self.v_all_children = []
- self.v_all_children.extend(self.v_start_children)
- self.v_all_children.extend(self.v_end_children)
-
- # Use centered layout when both vertical and centered_bar are enabled
- is_centered_bar = is_vertical_layout and getattr(data, "CENTERED_BAR", False)
-
- self.bar = CenterBox(
- name="bar",
- orientation="h" if not is_vertical_layout else "v",
- h_align="fill",
- v_align="fill",
- start_children=None
- if is_centered_bar
- else Box(
- name="start-container",
- spacing=4,
- orientation="h" if not is_vertical_layout else "v",
- children=self.h_start_children
- if not is_vertical_layout
- else self.v_start_children,
- ),
- center_children=Box(
- orientation="v",
- spacing=4,
- children=self.v_all_children
- if is_centered_bar
- else Box(
- name="center-container",
- spacing=4,
- orientation="h" if not is_vertical_layout else "v",
- children=self.h_center_children if not is_vertical_layout else None,
- ),
- )
- if is_vertical_layout
- else Box(
- name="center-container",
- spacing=4,
- orientation="h" if not is_vertical_layout else "v",
- children=self.h_center_children if not is_vertical_layout else None,
- ),
- end_children=None
- if is_centered_bar
- else Box(
- name="end-container",
- spacing=4,
- orientation="h" if not is_vertical_layout else "v",
- children=self.h_end_children
- if not is_vertical_layout
- else self.v_end_children,
- ),
- )
-
- self.children = self.bar
-
- def apply_component_visibility(self):
- """Apply saved visibility settings to all components"""
- components = {
- "metrics": self.metrics,
- "indicators": self.applets,
- "updates": self.updates,
- "workspaces": self.workspaces,
- "button_app": self.button_app,
- # "button_tools": self.button_tools,
- "language": self.language,
- "date_time": self.date_time,
- "tray": self.tray,
- }
-
- for component_name, widget in components.items():
- if component_name in self.component_visibility:
- widget.set_visible(self.component_visibility[component_name])
-
- def toggle_component_visibility(self, component_name):
- """Toggle visibility for a specific component"""
- components = {
- "metrics": self.metrics,
- "indicators": self.applets,
- "updates": self.updates,
- "workspaces": self.workspaces,
- "button_app": self.button_app,
- # "button_tools": self.button_tools,
- "language": self.language,
- "date_time": self.date_time,
- "tray": self.tray,
- }
-
- if component_name in components and component_name in self.component_visibility:
- # Toggle the visibility state
- self.component_visibility[component_name] = not self.component_visibility[
- component_name
- ]
- # Apply the new state
- components[component_name].set_visible(
- self.component_visibility[component_name]
- )
-
- # Update the configuration
- config_file = os.path.expanduser(
- f"~/{data.APP_NAME}/config/assets/config.json"
- )
- try:
- if os.path.exists(config_file):
- with open(config_file, "r") as f:
- config = json.load(f)
-
- # Update the config with the new visibility state
- config[f"bar_{component_name}_visible"] = self.component_visibility[
- component_name
- ]
-
- # Write the updated config back to file
- with open(config_file, "w") as f:
- json.dump(config, f, indent=4)
- except (json.JSONDecodeError, IOError) as e:
- print(f"Error updating config file: {e}")
- # Revert the visibility change if config update fails
- self.component_visibility[
- component_name
- ] = not self.component_visibility[component_name]
- components[component_name].set_visible(
- self.component_visibility[component_name]
- )
-
- return self.component_visibility[component_name]
-
- return None
-
- def on_button_enter(self, widget, event):
- window = widget.get_window()
- if window:
- window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
-
- def on_button_leave(self, widget, event):
- window = widget.get_window()
- if window:
- window.set_cursor(None)
-
- def search_apps(self):
- self.launcher.open("launcher")
-
- def calendar(self):
- self.launcher.open("calendar")
-
- def on_language_switch(self, _=None, event: HyprlandEvent = None):
- lang = event.data[1] if event else HyprlandLanguage().get_label()
- self.language.set_tooltip_text(lang)
- if not data.VERTICAL:
- self.lang_label.set_label(lang[:2].lower())
- else:
- self.lang_label.add_style_class("icon")
- self.lang_label.set_markup(icons.keyboard)
-
-
-class MyCorner(Box):
- def __init__(self, position):
- super().__init__(
- name="corner-container",
- children=Corner(
- name="corner",
- orientation=position,
- size=20,
- ),
- )
-
-
-class Corners(Window):
- def __init__(self):
- super().__init__(
- layer="top",
- anchor="top left bottom right",
- pass_through=True,
- child=Box(
- orientation="vertical",
- children=[
- Box(
- children=[
- MyCorner("top-left"),
- Box(h_expand=True),
- MyCorner("top-right"),
- ]
- ),
- Box(v_expand=True),
- Box(
- children=[
- MyCorner("bottom-left"),
- Box(h_expand=True),
- MyCorner("bottom-right"),
- ]
- ),
- ],
- ),
- )
diff --git a/modules/bar/components/__init__.py b/modules/bar/components/__init__.py
deleted file mode 100644
index 161c235..0000000
--- a/modules/bar/components/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .metrics import Metrics
-from .workspace import workspace
-from .system_indicators import SystemIndicators
-from .updates import UpdatesWidget
diff --git a/modules/bar/components/metrics.py b/modules/bar/components/metrics.py
deleted file mode 100644
index b716b4d..0000000
--- a/modules/bar/components/metrics.py
+++ /dev/null
@@ -1,368 +0,0 @@
-import subprocess
-import json
-import psutil
-from gi.repository import GLib, Gdk
-
-from fabric.widgets.label import Label
-from fabric.widgets.box import Box
-from fabric.widgets.circularprogressbar import CircularProgressBar
-from fabric.widgets.revealer import Revealer
-from fabric.core.fabricator import Fabricator
-from fabric.widgets.button import Button
-import utils.icons as icons
-import config.data as data
-
-
-class MetricsProvider:
- def __init__(self):
- self.cpu = 0.0
- self.mem = 0.0
- self.swap = 0.0
- self.disk = []
- self.bat_percent = 0.0
- self.bat_charging = None
-
- GLib.timeout_add_seconds(1, self._update)
-
- def _update(self):
- # Get non-blocking usage percentages (interval=0)
- # The first call may return 0, but subsequent calls will provide consistent values.
- self.cpu = psutil.cpu_percent(interval=0)
- mem = psutil.virtual_memory()
- self.mem = mem.percent
- swap = psutil.swap_memory()
- self.swap = swap.percent
- self.disk = [psutil.disk_usage(path).percent for path in data.BAR_METRICS_DISKS]
-
- battery = psutil.sensors_battery()
- if battery is None:
- self.bat_percent = 0.0
- self.bat_charging = None
- else:
- self.bat_percent = battery.percent
- self.bat_charging = battery.power_plugged
- return True
-
- def get_metrics(self):
- return (self.cpu, self.mem, self.swap, self.disk)
-
- def get_battery(self):
- return (self.bat_percent, self.bat_charging)
-
-
-shared_provider = MetricsProvider()
-
-
-class SingularMetric:
- def __init__(self, id, name, icon):
- self.is_vertical_layout = data.VERTICAL
- self.name_markup = name
- self.icon_markup = icon
-
- self.icon = Label(name="metrics-icon", markup=icon)
- self.circle = CircularProgressBar(
- name="metrics-circle",
- value=0,
- size=26,
- line_width=2,
- start_angle=150,
- end_angle=390,
- style_classes=id,
- child=self.icon,
- )
-
- self.circle_button = Button(
- name=f"metrics-{id}-button",
- child=self.circle,
- style="padding: 0; margin: 0; background: none;",
- )
-
- self.circle_button.add_events(
- Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK
- )
- self.circle_button.connect("enter-notify-event", self.on_button_enter)
- self.circle_button.connect("leave-notify-event", self.on_button_leave)
-
- self.circle_button.connect("clicked", self.on_circle_clicked)
-
- self.level = Label(
- name="metrics-level",
- style_classes=id,
- label="0%",
- style="margin: 4px 0px;" if self.is_vertical_layout else "margin: 0 4px;",
- )
- self.revealer = Revealer(
- name=f"metrics-{id}-revealer",
- transition_duration=250,
- transition_type="slide-up" if self.is_vertical_layout else "slide-left",
- child=self.level,
- child_revealed=False,
- )
-
- self.box = Box(
- name=f"metrics-{id}-box",
- orientation="v" if self.is_vertical_layout else "h",
- spacing=0,
- children=[self.circle_button, self.revealer],
- )
-
- # Connect events for cursor changes to the box as well
- self.box.connect("enter-notify-event", self.on_button_enter)
- self.box.connect("leave-notify-event", self.on_button_leave)
-
- # Set initial tooltip - moved after box is created
- self.update_tooltip("0%")
-
- def update_tooltip(self, value_text):
- tooltip = f"{self.icon_markup} {self.name_markup}: {value_text}"
- # self.box.set_tooltip_markup(tooltip)
- self.circle_button.set_tooltip_markup(tooltip)
- self.circle.set_tooltip_markup(tooltip)
- self.icon.set_tooltip_markup(tooltip)
-
- def markup(self):
- """Get formatted text for tooltip display"""
- return f"{self.icon_markup} {self.name_markup}: {self.level.get_label()}"
-
- def on_button_enter(self, widget, event):
- window = widget.get_window()
- if window:
- window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
-
- def on_button_leave(self, widget, event):
- window = widget.get_window()
- if window:
- window.set_cursor(None)
-
- def on_circle_clicked(self, button):
- # Toggle the revealed state of the label
- current_state = self.revealer.get_child_revealed()
- self.revealer.set_reveal_child(not current_state)
-
-
-class Metrics(Box):
- def __init__(self, **kwargs):
- # Determine if we should use vertical layout for components
- self.is_vertical_layout = data.VERTICAL
-
- super().__init__(
- name="metrics",
- spacing=0,
- orientation="h" if not self.is_vertical_layout else "v",
- visible=True,
- all_visible=True,
- style="padding:4px 0px" if self.is_vertical_layout else "padding: 0px 4px;",
- **kwargs,
- )
-
- self.batt_fabricator = Fabricator(
- poll_from=lambda v: shared_provider.get_battery(),
- on_changed=lambda f, v: self.update_battery,
- interval=1000,
- stream=False,
- default_value=0,
- )
- self.batt_fabricator.changed.connect(self.update_battery)
- GLib.idle_add(self.update_battery, None, shared_provider.get_battery())
-
- visible = getattr(
- data,
- "METRICS_VISIBLE",
- {"cpu": True, "ram": True, "disk": True, "swap": True},
- )
- disks = (
- [
- SingularMetric(
- "disk",
- f"DISK ({path})" if len(data.BAR_METRICS_DISKS) != 1 else "DISK",
- icons.disk,
- )
- for path in data.BAR_METRICS_DISKS
- ]
- if visible.get("disk", True)
- else []
- )
-
- self.cpu = (
- SingularMetric("cpu", "CPU", icons.cpu)
- if visible.get("cpu", True)
- else None
- )
- self.ram = (
- SingularMetric("ram", "RAM", icons.memory)
- if visible.get("ram", True)
- else None
- )
- self.swap = (
- SingularMetric("swap", "SWAP", icons.swap)
- if visible.get("swap", True)
- else None
- )
- self.bat = (
- SingularMetric("bat", "BATTERY", icons.battery)
- if visible.get("bat", True)
- else None
- )
- self.disk = disks
-
- # Add only enabled metrics
- for disk in self.disk:
- self.add(disk.box)
- self.add(Box(name="metrics-sep"))
- if self.ram:
- self.add(self.ram.box)
- self.add(Box(name="metrics-sep"))
- if self.cpu:
- self.add(self.cpu.box)
- if self.swap:
- self.add(Box(name="metrics-sep"))
- self.add(self.swap.box)
- if self.bat:
- self.add(Box(name="metrics-sep"))
- self.add(self.bat.box)
-
- # Connect events directly to the button
- self.connect("enter-notify-event", self.on_mouse_enter)
- self.connect("leave-notify-event", self.on_mouse_leave)
-
- GLib.timeout_add_seconds(1, self.update_metrics)
-
- self.hide_timer = None
- self.hover_counter = 0
-
- def _format_percentage(self, value: int) -> str:
- return f"{value}%"
-
- def on_mouse_enter(self, widget, event):
- window = widget.get_window()
- if window:
- window.set_cursor(Gdk.Cursor(Gdk.CursorType.HAND2))
-
- if not data.VERTICAL:
- self.hover_counter += 1
- if self.hide_timer is not None:
- GLib.source_remove(self.hide_timer)
- self.hide_timer = None
- if self.cpu:
- self.cpu.revealer.set_reveal_child(True)
- if self.ram:
- self.ram.revealer.set_reveal_child(True)
- for disk in self.disk:
- disk.revealer.set_reveal_child(True)
- if self.swap:
- self.swap.revealer.set_reveal_child(True)
- if self.bat:
- self.bat.revealer.set_reveal_child(True)
- return False
-
- def on_mouse_leave(self, widget, event):
- window = widget.get_window()
- if window:
- window.set_cursor(None)
-
- if not data.VERTICAL:
- if self.hover_counter > 0:
- self.hover_counter -= 1
- if self.hover_counter == 0:
- if self.hide_timer is not None:
- GLib.source_remove(self.hide_timer)
- self.hide_timer = GLib.timeout_add(500, self.hide_revealer)
- return False
-
- def hide_revealer(self):
- if not data.VERTICAL:
- if self.cpu:
- self.cpu.revealer.set_reveal_child(False)
- if self.ram:
- self.ram.revealer.set_reveal_child(False)
- for disk in self.disk:
- disk.revealer.set_reveal_child(False)
- self.hide_timer = None
- return False
-
- def update_metrics(self):
- cpu, mem, swap, disk = shared_provider.get_metrics()
- if self.cpu:
- self.cpu.circle.set_value(cpu / 100.0)
- value_text = self._format_percentage(int(cpu))
- self.cpu.level.set_label(value_text)
- self.cpu.update_tooltip(value_text)
- if self.ram:
- self.ram.circle.set_value(mem / 100.0)
- value_text = self._format_percentage(int(mem))
- self.ram.level.set_label(value_text)
- self.ram.update_tooltip(value_text)
- for i, disk_metric in enumerate(self.disk):
- if i < len(disk):
- value_text = self._format_percentage(int(disk[i]))
- disk_metric.circle.set_value(disk[i] / 100.0)
- disk_metric.level.set_label(value_text)
- disk_metric.update_tooltip(value_text)
- if self.swap:
- self.swap.circle.set_value(swap / 100.0)
- value_text = self._format_percentage(int(swap))
- self.swap.level.set_label(value_text)
- self.swap.update_tooltip(value_text)
- if self.bat:
- bat_percent, _ = shared_provider.get_battery()
- self.bat.circle.set_value(bat_percent / 100.0)
- value_text = self._format_percentage(int(bat_percent))
- self.bat.level.set_label(value_text)
- self.bat.update_tooltip(value_text)
-
- # Tooltip: only show enabled metrics
- tooltip_metrics = []
- if self.disk: tooltip_metrics.extend(self.disk)
- if self.swap: tooltip_metrics.append(self.swap)
- if self.bat: tooltip_metrics.append(self.bat)
- if self.ram: tooltip_metrics.append(self.ram)
- if self.cpu: tooltip_metrics.append(self.cpu)
- self.set_tooltip_markup((" - " if not data.VERTICAL else "\n").join([v.markup() for v in tooltip_metrics]))
-
- return True
-
- def update_battery(self, sender, battery_data):
- if self.bat is None:
- return
-
- value, charging = battery_data
- if value == 0:
- if self.bat:
- self.bat.box.set_visible(False)
- else:
- if self.bat:
- self.bat.box.set_visible(True)
- self.bat.circle.set_value(value / 100)
- percentage = int(value)
- self.bat.level.set_label(self._format_percentage(percentage))
-
- # Apply alert styling if battery is low AND not charging
- if percentage <= 15 and charging == False:
- self.bat.icon.add_style_class("alert")
- self.bat.circle.add_style_class("alert")
- else:
- self.bat.icon.remove_style_class("alert")
- self.bat.circle.remove_style_class("alert")
-
- # Choose the icon based on charging state first, then battery level
- if percentage == 100:
- self.bat.icon.set_markup(icons.battery)
- charging_status = f"{icons.bat_full} Fully Charged"
- elif charging == True:
- self.bat.icon.set_markup(icons.charging)
- charging_status = f"{icons.bat_charging} Charging"
- elif percentage <= 15 and charging == False:
- self.bat.icon.set_markup(icons.alert)
- charging_status = f"{icons.bat_low} Low Battery"
- elif charging == False:
- self.bat.icon.set_markup(icons.discharging)
- charging_status = f"{icons.bat_discharging} Discharging"
- else:
- self.bat.icon.set_markup(icons.battery)
- charging_status = "Battery"
-
- # Update individual tooltip for battery
- self.bat.update_tooltip(f"{charging_status}: {self._format_percentage(percentage)}")
-
- # Update the combined tooltip
- self.update_metrics()
diff --git a/modules/bar/components/system_indicators.py b/modules/bar/components/system_indicators.py
deleted file mode 100644
index 7d9b453..0000000
--- a/modules/bar/components/system_indicators.py
+++ /dev/null
@@ -1,258 +0,0 @@
-import subprocess
-from fabric import Fabricator
-from fabric.widgets.label import Label
-from fabric.widgets.box import Box
-from fabric.widgets.button import Button
-from fabric.bluetooth import BluetoothClient
-from services import network_client, audio, notification_service
-import utils.icons as icons
-import config.data as data
-
-
-class SystemIndicators(Box):
- def __init__(self, **kwargs):
- # Determine if we should use vertical layout for components
- is_vertical_layout = data.VERTICAL
-
- super().__init__(
- orientation="h" if not is_vertical_layout else "v", spacing=0, **kwargs
- )
- self.launcher = kwargs.get("launcher", None)
-
- self.bluetooth_icon = Label(name="system-indicator-icon")
- self.wifi_icon = Label(name="system-indicator-icon")
- self.volume_icon_button = Label(name="system-indicator-icon")
- self.microphone_icon = Label(name="system-indicator-icon")
- self.idle_label = Label(name="system-indicator-icon")
- self.night_label = Label(name="system-indicator-icon")
- self.power_label = Label(name="system-indicator-icon")
- self.notification_icon = Button(name="system-indicator-icon")
- self.notification_label = Label(name="system-notif-label")
- self.notification_icon.add(self.notification_label)
- self.notification_icon.connect("clicked", self.open_notif_center)
-
- for widget in [
- self.bluetooth_icon,
- self.wifi_icon,
- self.volume_icon_button,
- self.microphone_icon,
- self.idle_label,
- self.night_label,
- self.power_label,
- self.notification_icon,
- ]:
- self.add(widget)
-
- self.audio_service = audio
- self.bluetooth_client = BluetoothClient()
- self.network_client = network_client
- self.notification_service = notification_service
-
- # Connect signals
- self.bluetooth_client.connect("changed", self.update_bluetooth_status)
- self.audio_service.connect("microphone_changed", self.update_mic_status)
- self.audio_service.connect("changed", self.update_volume_status)
- self.network_client.connect("device_ready", self.update_network_status)
- self.notification_service.connect("dnd", self.update_notification_status)
- self.notification_service.connect(
- "notification_count", self.update_notification_status
- )
-
- if self.network_client.wifi_device:
- self.network_client.wifi_device.connect(
- "changed", self.update_network_status
- )
-
- if self.network_client.ethernet_device:
- self.network_client.ethernet_device.connect(
- "changed", self.update_network_status
- )
-
- Fabricator(interval=1000, poll_from=self.update_all_statuses)
-
- self.update_bluetooth_status()
- self.update_notification_status()
-
- def update_all_statuses(self, *_args):
- self.update_idle_night_status()
- self.update_power_profile()
- self.update_network_status()
-
- def update_idle_night_status(self, *_):
- # Update idle status
- has_idle = (
- subprocess.run(["pgrep", "-x", "wlinhibit"], capture_output=True).returncode
- == 0
- )
- self.idle_label.set_visible(has_idle)
- self.idle_label.set_markup(icons.coffee if has_idle else "")
-
- # Update night mode status
- has_night = (
- subprocess.run(
- ["pgrep", "-x", "hyprsunset"], capture_output=True
- ).returncode
- == 0
- )
- self.night_label.set_visible(has_night)
- self.night_label.set_markup(icons.night if has_night else "")
-
- def update_volume_status(self, *_):
- stream = self.audio_service.speaker
- if not stream:
- self.volume_icon_button.set_visible(False)
- return
-
- self.volume_icon_button.set_visible(True)
- # Normalize volume to integer percentage (0-100)
- volume_level = stream.volume
- if volume_level > 1:
- volume_level = min(int(volume_level), 100)
- else:
- volume_level = int(volume_level * 100)
-
- is_muted = stream.muted
-
- if is_muted:
- icon = icons.vol_off
- elif volume_level > 74:
- icon = icons.vol_high
- elif volume_level > 0:
- icon = icons.vol_medium
- else:
- icon = icons.vol_mute
-
- self.volume_icon_button.set_markup(icon)
-
- tooltip_text = "Muted" if is_muted else f"Volume: {volume_level}%"
- self.volume_icon_button.set_tooltip_text(tooltip_text)
-
- def update_mic_status(self, *_):
- mic = self.audio_service.microphone
- if not mic:
- self.microphone_icon.set_visible(False)
- return
-
- self.microphone_icon.set_visible(True)
- # Normalize volume to integer percentage (0-100)
- volume_level = mic.volume
- if volume_level > 1:
- volume_level = min(int(volume_level), 100)
- else:
- volume_level = int(volume_level * 100)
-
- is_muted = mic.muted
- icon = icons.mic_muted if is_muted else icons.mic
-
- self.microphone_icon.set_markup(icon)
- tooltip_text = "Muted" if is_muted else f"Microphone: {volume_level}%"
- self.microphone_icon.set_tooltip_text(tooltip_text)
-
- def update_bluetooth_status(self, *_):
- if self.bluetooth_client.enabled:
- self.bluetooth_icon.set_visible(True)
- # Get a list of connected devices
- connected_devices = [
- device.name
- for device in self.bluetooth_client.devices
- if device.connected
- ]
-
- if connected_devices:
- self.bluetooth_icon.set_markup(icons.bluetooth_connected)
- self.bluetooth_icon.set_tooltip_text(f"{', '.join(connected_devices)}")
- else:
- self.bluetooth_icon.set_markup(icons.bluetooth)
- self.bluetooth_icon.set_tooltip_text(
- "Bluetooth is enabled, no devices connected"
- )
- else:
- self.bluetooth_icon.set_visible(True) # Keep it visible
- self.bluetooth_icon.set_markup(icons.bluetooth_off)
- self.bluetooth_icon.set_tooltip_text("Bluetooth is disabled")
-
- def update_network_status(self, *_):
- primary_device = self.network_client.primary_device
- self.wifi_icon.set_visible(True)
-
- if primary_device == "wifi" and self.network_client.wifi_device:
- wifi_device = self.network_client.wifi_device
-
- self.wifi_icon.set_tooltip_text(
- wifi_device.ssid if wifi_device.ssid else "No WiFi Connection"
- )
-
- if wifi_device.enabled:
- strength = wifi_device.strength
-
- if strength > 0:
- if strength < 25:
- icon_label = icons.wifi_0
- elif strength < 50:
- icon_label = icons.wifi_1
- elif strength < 75:
- icon_label = icons.wifi_2
- else:
- icon_label = icons.wifi_3
- else:
- icon_label = icons.wifi_off
- else:
- icon_label = icons.wifi_off
-
- elif primary_device == "wired":
- icon_label = icons.lan
- self.wifi_icon.set_tooltip_text("Wired Connection")
-
- else:
- icon_label = icons.wifi_off # No network
-
- self.wifi_icon.set_markup(icon_label)
-
- def update_power_profile(self, *_):
- try:
- result = subprocess.run(
- ["powerprofilesctl", "get"], capture_output=True, text=True
- )
- current_profile = result.stdout.strip()
-
- profile_icons = {
- "performance": icons.power_performance,
- "balanced": "",
- "power-saver": icons.power_saving,
- }
-
- icon = profile_icons.get(current_profile)
- tooltip = (
- f"Power Profile: {current_profile.capitalize()}"
- if current_profile
- else "Unknown Profile"
- )
-
- self.power_label.set_visible(bool(icon)) # Only show if there's an icon
- self.power_label.set_markup(icon)
- self.power_label.set_tooltip_text(tooltip)
-
- except Exception as e:
- self.power_label.set_visible(False)
- self.power_label.set_tooltip_text(f"Error fetching power profile: {e}")
-
- def update_notification_status(self, *_):
- """Update the notification icon based on DND state and notification count."""
- count = self.notification_service.count
-
- if count == 0 and not self.notification_service.dont_disturb:
- self.notification_icon.set_visible(False)
- return
-
- if self.notification_service.dont_disturb:
- self.notification_label.set_markup(icons.notifications_off)
- self.notification_icon.set_tooltip_text("Do Not Disturb: On")
- self.notification_icon.set_visible(True)
- else:
- # At this point count must be > 0
- self.notification_label.set_markup(icons.notifications_clear)
- self.notification_icon.set_tooltip_text(f"Notifications: {count}")
- self.notification_icon.set_visible(True)
-
- def open_notif_center(self, *_):
- self.launcher.open("notification-center")
diff --git a/modules/bar/components/updates.py b/modules/bar/components/updates.py
deleted file mode 100644
index f43add2..0000000
--- a/modules/bar/components/updates.py
+++ /dev/null
@@ -1,115 +0,0 @@
-import json
-from fabric.utils import exec_shell_command_async, get_relative_path, invoke_repeater
-from fabric.widgets.box import Box
-from fabric.widgets.centerbox import CenterBox
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.widgets.stack import Stack
-from gi.repository import Gtk
-import utils.icons as icons
-import config.data as data
-
-
-class UpdatesWidget(Button):
- """A widget to display the number of available updates."""
-
- def __init__(
- self,
- **kwargs,
- ):
- # Determine if we should use vertical layout for components
- is_vertical_layout = data.VERTICAL
-
- # Initialize the button with specific name and style
- super().__init__(
- name="update",
- orientation="h" if not is_vertical_layout else "v",
- **kwargs,
- )
-
- self.script_file = get_relative_path("../../../config/scripts/systemupdates.sh")
-
- self.update_level_label = Label(
- name="update-label",
- label="0",
- visible=False, # Initially hidden
- )
-
- self.update_icon = Label(name="update-icon", markup=icons.update)
- self.updated_icon = Label(name="updated-icon", markup=icons.updated)
-
- # Create boxes with proper orientation
- self.update_box = CenterBox(
- orientation="v" if is_vertical_layout else "h",
- center_children=[self.update_icon, self.update_level_label],
- )
-
- self.updated_box = CenterBox(
- orientation="v" if is_vertical_layout else "h",
- center_children=[self.updated_icon],
- )
-
- # Stack to switch between states
- self.stack = Stack()
- self.stack.add_named(self.update_box, "update_box")
- self.stack.add_named(self.updated_box, "updated_box")
- self.stack.set_visible_child_name("update_box")
-
- self.children = Box(
- orientation="v" if is_vertical_layout else "h",
- children=self.stack,
- )
-
- # Configure stack transitions
- self.stack.set_homogeneous(False)
- self.stack.set_transition_type(Gtk.StackTransitionType.CROSSFADE)
-
- # Connect click handler
- self.connect("button-press-event", self.on_button_press)
-
- # Check for updates every minute
- invoke_repeater(60000, self.update, initial_call=True)
-
- def update_values(self, value: str) -> bool:
- """Update the widget state based on update check results"""
- try:
- value = json.loads(value)
- except json.JSONDecodeError:
- return False
-
- update_count = str(value["total"])
- self.update_level_label.set_label(update_count)
-
- # Show appropriate state based on update count
- if update_count == "0":
- self.stack.set_visible_child_name("updated_box")
- self.update_level_label.set_visible(False)
- else:
- self.stack.set_visible_child_name("update_box")
- self.update_level_label.set_visible(True)
-
- self.set_tooltip_text(value.get("tooltip", ""))
- return True
-
- def on_button_press(self, _, event) -> bool:
- """Handle button press events"""
- if event.button == 1: # Left click
- # Run update command
- exec_shell_command_async(
- f"{self.script_file} -arch -up",
- lambda _: None,
- )
- self.update()
- return True
- else:
- # Other clicks just refresh
- self.update()
-
- def update(self) -> bool:
- """Check for system updates"""
- exec_shell_command_async(
- f"{self.script_file} -arch",
- lambda output: self.update_values(output),
- )
-
- return True
diff --git a/modules/bar/components/workspace.py b/modules/bar/components/workspace.py
deleted file mode 100644
index e24a3d1..0000000
--- a/modules/bar/components/workspace.py
+++ /dev/null
@@ -1,32 +0,0 @@
-import json
-
-from fabric.hyprland.service import Hyprland
-from fabric.widgets.label import Label
-import config.data as data
-
-connection = Hyprland()
-
-workspaceData = connection.send_command("j/activeworkspace").reply
-activeWorkspace = json.loads(workspaceData.decode("utf-8"))["name"]
-
-# Create the workspace label with proper initialization
-if not data.VERTICAL:
- workspace = Label(label=f"Workspace {activeWorkspace}", name="workspace-name")
-else:
- workspace = Label(
- v_align="center",
- label="W\no\nr\nk\ns\np\na\nc\ne\n\n" + activeWorkspace,
- name="workspace-name",
- )
-
-
-def on_workspace(obj, signal):
- global activeWorkspace
- activeWorkspace = json.loads(signal.data[0])
- if not data.VERTICAL:
- workspace.set_label(f"Workspace {activeWorkspace}")
- else:
- workspace.set_label(f"W\no\nr\nk\ns\np\na\nc\ne\n\n{activeWorkspace}")
-
-
-connection.connect("event::workspace", on_workspace)
diff --git a/modules/dock.py b/modules/dock.py
deleted file mode 100644
index 0821fbf..0000000
--- a/modules/dock.py
+++ /dev/null
@@ -1,1133 +0,0 @@
-import json
-from gi.repository import GLib, Gtk, Gdk
-import cairo
-from utils.icon_resolver import IconResolver
-from utils.occlusion import check_occlusion
-from fabric.widgets.box import Box
-from fabric.widgets.button import Button
-from fabric.widgets.image import Image
-from fabric.widgets.eventbox import EventBox
-from utils.wayland import WaylandWindow as Window
-from fabric.hyprland.widgets import get_hyprland_connection
-from fabric.utils import (
- exec_shell_command,
- exec_shell_command_async,
- idle_add,
- remove_handler,
- get_relative_path,
-)
-from fabric.utils.helpers import get_desktop_applications
-from modules.bar.bar import MyCorner # Add this import for corners
-
-import config.data as data
-import logging
-
-OCCLUSION = 36 + data.DOCK_ICON_SIZE
-
-
-# Credit to Aylur for the createSurfaceFromWidget code
-def createSurfaceFromWidget(widget: Gtk.Widget) -> cairo.ImageSurface:
- alloc = widget.get_allocation()
- surface = cairo.ImageSurface(
- cairo.Format.ARGB32,
- alloc.width,
- alloc.height,
- )
- cr = cairo.Context(surface)
- cr.set_source_rgba(255, 255, 255, 0)
- cr.rectangle(0, 0, alloc.width, alloc.height)
- cr.fill()
- widget.draw(cr)
- return surface
-
-
-class Dock(Window):
- # Static registry to track all active dock instances
- _instances = []
-
- def __init__(self, **kwargs):
- super().__init__(
- name="dock-window",
- layer="top",
- anchor="bottom center"
- if not data.VERTICAL and not data.BOTTOM_BAR
- else "left center"
- if data.VERTICAL_RIGHT_ALIGN
- else "right center",
- margin="-8px 0 -8px 0"
- if not data.VERTICAL and not data.BOTTOM_BAR
- else "-8px 0 -80px 0"
- if data.VERTICAL_RIGHT_ALIGN
- else "0 -80px 0 -8px",
- exclusivity="none",
- **kwargs,
- )
- # Add this instance to the registry
- Dock._instances.append(self)
-
- # Initialize configuration
- self.config = data.read_config()
- self.conn = get_hyprland_connection()
- self.icon = IconResolver()
- self.pinned = self.config.get("pinned_apps", [])
- self.config_path = get_relative_path("../config/assets/modus.json")
- self.app_map = {} # Initialize the app map
- self._all_apps = get_desktop_applications() # Get all apps for lookup
- # Create app identifiers mapping
- self.app_identifiers = self._build_app_identifiers_map()
- self.is_hidden = False
- self.hide_id = None
- self._arranger_handler = None
- self._drag_in_progress = False # Drag lock flag
- self.is_hovered = False
- self.always_occluded = (
- data.DOCK_ALWAYS_OCCLUDED
- ) # Track the always occluded setting
-
- # Set up UI containers
- self.view = Box(
- name="viewport",
- orientation="h"
- if not data.VERTICAL
- and not data.VERTICAL_RIGHT_ALIGN
- and not data.BOTTOM_BAR
- else "v",
- spacing=4,
- )
- self.wrapper = Box(name="dock", orientation="v", children=[self.view])
-
- # Main dock container with hover handling
- self.dock_eventbox = EventBox()
- self.dock_eventbox.add(self.wrapper)
- self.dock_eventbox.connect("enter-notify-event", self._on_dock_enter)
- self.dock_eventbox.connect("leave-notify-event", self._on_dock_leave)
-
- # Helper to create corner boxes
- def create_corner_box(name, orientation, align, corner_position):
- return Box(
- name=name,
- orientation=orientation,
- **align,
- children=[
- Box(
- **(
- {"v_expand": True, "v_align": "fill"}
- if orientation == "v"
- else {"h_expand": True, "h_align": "fill"}
- )
- ),
- MyCorner(corner_position),
- ],
- )
-
- # Determine dock layout based on orientation
- if not data.VERTICAL and not data.BOTTOM_BAR:
- # Horizontal layout: corners at the bottom
- self.corner_left = create_corner_box(
- name="dock-corner-left",
- orientation="v",
- align={"h_align": "start"},
- corner_position="bottom-right",
- )
-
- self.corner_right = create_corner_box(
- name="dock-corner-right",
- orientation="v",
- align={"h_align": "end"},
- corner_position="bottom-left",
- )
-
- self.dock_full = Box(
- name="dock-full",
- orientation="h",
- h_expand=True,
- h_align="fill",
- children=[
- self.corner_left,
- self.dock_eventbox,
- self.corner_right,
- ],
- )
-
- elif data.VERTICAL_RIGHT_ALIGN:
- # Vertical right-aligned layout: corners at top and bottom (left-angled corners)
- self.corner_top = create_corner_box(
- name="dock-corner-top",
- orientation="h",
- align={"v_align": "start"},
- corner_position="bottom-left",
- )
-
- self.corner_bottom = create_corner_box(
- name="dock-corner-bottom",
- orientation="h",
- align={"v_align": "end"},
- corner_position="top-left",
- )
-
- self.dock_full = Box(
- name="dock-full",
- orientation="v",
- v_expand=True,
- v_align="fill",
- children=[
- self.corner_top,
- self.dock_eventbox,
- self.corner_bottom,
- ],
- )
-
- else:
- # Vertical left/default alignment: corners at top and bottom (right-angled corners)
- self.corner_top = create_corner_box(
- name="dock-corner-top",
- orientation="h",
- align={"v_align": "start"},
- corner_position="bottom-right",
- )
-
- self.corner_bottom = create_corner_box(
- name="dock-corner-bottom",
- orientation="h",
- align={"v_align": "end"},
- corner_position="top-right",
- )
-
- self.dock_full = Box(
- name="dock-full",
- orientation="v",
- v_expand=True,
- v_align="fill",
- children=[
- self.corner_top,
- self.dock_eventbox,
- self.corner_bottom,
- ],
- )
- # Bottom/side hover activation area
- self.hover_activator = EventBox()
- self.hover_activator.set_size_request(
- -1
- if not data.VERTICAL
- and not data.VERTICAL_RIGHT_ALIGN
- and not data.BOTTOM_BAR
- else 1,
- 1
- if not data.VERTICAL
- and not data.VERTICAL_RIGHT_ALIGN
- and not data.BOTTOM_BAR
- else -1,
- )
- self.hover_activator.connect("enter-notify-event", self._on_hover_enter)
- self.hover_activator.connect("leave-notify-event", self._on_hover_leave)
-
- # Update main_box to include the dock_full and activator
- self.main_box = Box(
- orientation="v"
- if not data.VERTICAL
- and not data.VERTICAL_RIGHT_ALIGN
- and not data.BOTTOM_BAR
- else "h",
- children=[self.dock_full, self.hover_activator],
- )
- self.add(self.main_box)
-
- # Drag-and-drop setup for the viewport
- self.view.drag_source_set(
- Gdk.ModifierType.BUTTON1_MASK,
- [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)],
- Gdk.DragAction.MOVE,
- )
- self.view.drag_dest_set(
- Gtk.DestDefaults.ALL,
- [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)],
- Gdk.DragAction.MOVE,
- )
- self.view.connect("drag-data-get", self.on_drag_data_get)
- self.view.connect("drag-data-received", self.on_drag_data_received)
- self.view.connect("drag-begin", self.on_drag_begin)
- self.view.connect("drag-end", self.on_drag_end)
-
- # Initialization
- if self.conn.ready:
- self.update_dock()
- GLib.timeout_add(500, self.check_hide)
- else:
- self.conn.connect("event::ready", self.update_dock)
- self.conn.connect("event::ready", self.check_hide)
-
- for ev in ("activewindow", "openwindow", "closewindow", "changefloatingmode"):
- self.conn.connect(f"event::{ev}", self.update_dock)
- self.conn.connect("event::workspace", self.check_hide)
-
- if data.VERTICAL:
- self.wrapper.add_style_class("vertical")
- else:
- self.wrapper.remove_style_class("vertical")
-
- if data.BOTTOM_BAR:
- self.wrapper.add_style_class("vertical-right")
- else:
- self.wrapper.remove_style_class("vertical-right")
-
- if data.VERTICAL_RIGHT_ALIGN:
- self.wrapper.add_style_class("vertical-left")
- else:
- self.wrapper.remove_style_class("vertical-left")
-
- GLib.timeout_add(250, self.check_occlusion_state)
- # Monitor modus.json for changes
- GLib.timeout_add_seconds(1, self.check_config_change)
-
- # Check if dock should be visible based on configuration
- if not data.DOCK_ENABLED:
- self.set_visible(False)
-
- # Apply always occluded state if enabled in settings
- if self.always_occluded:
- self.dock_full.add_style_class("occluded")
-
- def _build_app_identifiers_map(self):
- """Build a mapping of app identifiers (class names, executables, names) to DesktopApp objects"""
- identifiers = {}
- for app in self._all_apps:
- # Map by name (lowercase)
- if app.name:
- identifiers[app.name.lower()] = app
-
- # Map by display name
- if app.display_name:
- identifiers[app.display_name.lower()] = app
-
- # Map by window class if available
- if app.window_class:
- identifiers[app.window_class.lower()] = app
-
- # Map by executable name if available
- if app.executable:
- exe_basename = app.executable.split("/")[-1].lower()
- identifiers[exe_basename] = app
-
- # Map by command line if available (without parameters)
- if app.command_line:
- cmd_base = app.command_line.split()[0].split("/")[-1].lower()
- identifiers[cmd_base] = app
-
- return identifiers
-
- def _normalize_window_class(self, class_name):
- """Normalize window class by removing common suffixes and lowercase."""
- if not class_name:
- return ""
-
- normalized = class_name.lower()
-
- # Remove common suffixes
- suffixes = [".bin", ".exe", ".so", "-bin", "-gtk"]
- for suffix in suffixes:
- if normalized.endswith(suffix):
- normalized = normalized[: -len(suffix)]
-
- return normalized
-
- def _classes_match(self, class1, class2):
- """Check if two window class names match with stricter comparison."""
- if not class1 or not class2:
- return False
-
- # Normalize both classes
- norm1 = self._normalize_window_class(class1)
- norm2 = self._normalize_window_class(class2)
-
- # Direct match after normalization
- if norm1 == norm2:
- return True
-
- # Don't do substring matching as it's too error-prone
- # This avoids incorrectly matching flatpak apps and others
- return False
-
- def on_drag_begin(self, widget, drag_context):
- """Handle drag begin event by setting the drag lock flag."""
- self._drag_in_progress = True
- # Set custom drag icon using the widget surface
- Gtk.drag_set_icon_surface(drag_context, createSurfaceFromWidget(widget))
-
- def _on_hover_enter(self, *args):
- """Handle hover over bottom activation area"""
- self.toggle_dock(show=True)
-
- def _on_hover_leave(self, *args):
- """Handle leave from bottom activation area"""
- self.delay_hide()
-
- def _on_dock_enter(self, widget, event):
- """Handle hover over dock content"""
- self.is_hovered = True
- self.dock_full.remove_style_class("occluded")
- # Cancel any pending hide operations
- if self.hide_id:
- GLib.source_remove(self.hide_id)
- self.hide_id = None
- self.toggle_dock(show=True)
- return True # Important: Stop event propagation
-
- def _on_dock_leave(self, widget, event):
- """Handle leave from dock content"""
- # Only trigger if mouse actually left the entire dock area
- if event.detail == Gdk.NotifyType.INFERIOR:
- return False # Ignore child-to-child mouse movements
-
- self.is_hovered = False
- self.delay_hide()
- # Immediate occlusion check on true leave using simplified format
- occlusion_region = (
- ("bottom", OCCLUSION)
- if not data.VERTICAL
- and not data.VERTICAL_RIGHT_ALIGN
- and not data.BOTTOM_BAR
- else ("left", OCCLUSION)
- if data.VERTICAL_RIGHT_ALIGN
- else ("right", OCCLUSION)
- )
- # Add occlusion style if not dragging an icon or if always_occluded is enabled
- if self.always_occluded or (
- not self._drag_in_progress
- and (check_occlusion(occlusion_region) or not self.view.get_children())
- ):
- self.dock_full.add_style_class("occluded")
- return True
-
- # Enhanced app lookup methods
- def find_app(self, app_identifier):
- """Return the DesktopApp object by matching any app identifier.
-
- If app_identifier is a dict, it will use all available keys for matching.
- """
- if not app_identifier:
- return None
-
- # If we got a dict object with app data (new format)
- if isinstance(app_identifier, dict):
- # Try to find by all available identifiers in priority order
- for key in [
- "window_class",
- "executable",
- "command_line",
- "name",
- "display_name",
- ]:
- if key in app_identifier and app_identifier[key]:
- app = self.find_app_by_key(app_identifier[key])
- if app:
- return app
- return None
-
- # Simple string identifier (backward compatibility)
- return self.find_app_by_key(app_identifier)
-
- def find_app_by_key(self, key_value):
- """Find app by a single identifier value"""
- if not key_value:
- return None
-
- # Try direct lookup in our identifiers map
- normalized_id = str(key_value).lower()
- if normalized_id in self.app_identifiers:
- return self.app_identifiers[normalized_id]
-
- # Try fuzzy matching - find apps where the identifier is part of their names
- for app in self._all_apps:
- if app.name and normalized_id in app.name.lower():
- return app
- if app.display_name and normalized_id in app.display_name.lower():
- return app
- if app.window_class and normalized_id in app.window_class.lower():
- return app
- if app.executable and normalized_id in app.executable.lower():
- return app
- if app.command_line and normalized_id in app.command_line.lower():
- return app
-
- return None
-
- # Update the dock's app map using DesktopApp objects from the system.
- def update_app_map(self):
- """Updates the mapping of commands to DesktopApp objects."""
- self._all_apps = get_desktop_applications() # Refresh all apps
- self.app_map = {
- app.name: app for app in self._all_apps if app.name
- } # Map app names to DesktopApp objects
- self.app_identifiers = (
- self._build_app_identifiers_map()
- ) # Rebuild identifiers map
-
- def create_button(self, app_identifier, instances):
- """Create dock application button"""
- desktop_app = self.find_app(app_identifier) # Find app by identifier
- icon_img = None
- display_name = None
-
- if desktop_app:
- icon_img = desktop_app.get_icon_pixbuf(size=data.DOCK_ICON_SIZE)
- display_name = desktop_app.display_name or desktop_app.name
-
- # Extract identifier for fallback
- id_value = (
- app_identifier["name"]
- if isinstance(app_identifier, dict)
- else app_identifier
- )
-
- if not icon_img:
- # Fallback to IconResolver with the app command
- icon_img = self.icon.get_icon_pixbuf(
- id_value, data.DOCK_ICON_SIZE
- ) # Use identifier for fallback
-
- if not icon_img: # Double check after exec path try
- # Fallback icon if no DesktopApp is found
- icon_img = self.icon.get_icon_pixbuf(
- "application-x-executable-symbolic", data.DOCK_ICON_SIZE
- )
- # Final fallback
- if not icon_img:
- icon_img = self.icon.get_icon_pixbuf(
- "image-missing", data.DOCK_ICON_SIZE
- )
-
- items = [Image(pixbuf=icon_img)]
-
- tooltip = display_name or (id_value if isinstance(id_value, str) else "Unknown")
- if not display_name and instances and instances[0].get("title"):
- tooltip = instances[0]["title"]
-
- button = Button(
- child=Box(
- name="dock-icon",
- orientation="v",
- h_align="center",
- children=items,
- ),
- on_clicked=lambda *a: self.handle_app(
- app_identifier, instances, desktop_app
- ), # Pass desktop_app as well
- tooltip_text=tooltip,
- name="dock-app-button",
- )
-
- # Store app data with the button for future reference
- button.app_identifier = app_identifier
- button.desktop_app = desktop_app
- button.instances = instances
-
- if instances:
- button.add_style_class("instance") # Style running apps
-
- # Enable DnD for ALL apps
- button.drag_source_set(
- Gdk.ModifierType.BUTTON1_MASK,
- [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)],
- Gdk.DragAction.MOVE,
- )
- button.connect("drag-begin", self.on_drag_begin)
- button.connect("drag-end", self.on_drag_end) # Connect drag-end to ALL buttons
-
- # Make all buttons drop targets for better reordering
- button.drag_dest_set(
- Gtk.DestDefaults.ALL,
- [Gtk.TargetEntry.new("text/plain", Gtk.TargetFlags.SAME_APP, 0)],
- Gdk.DragAction.MOVE,
- )
- button.connect("drag-data-get", self.on_drag_data_get)
- button.connect("drag-data-received", self.on_drag_data_received)
-
- button.connect("enter-notify-event", self._on_child_enter)
- return button
-
- # Enhanced app launching with multiple fallbacks
- def handle_app(self, app_identifier, instances, desktop_app=None):
- """Handle application button clicks with improved fallbacks"""
- if not instances:
- # Try to launch the app with multiple fallback methods
- if not desktop_app:
- desktop_app = self.find_app(app_identifier)
-
- if desktop_app:
- # Try the standard launch method first
- launch_success = desktop_app.launch()
-
- # If that fails, try command line or executable
- if not launch_success:
- if desktop_app.command_line:
- exec_shell_command_async(f"nohup {desktop_app.command_line}")
- elif desktop_app.executable:
- exec_shell_command_async(f"nohup {desktop_app.executable}")
- else:
- # No desktop entry found, try direct execution
- if isinstance(app_identifier, dict):
- # Try command_line first, then executable, then name as last resort
- if (
- "command_line" in app_identifier
- and app_identifier["command_line"]
- ):
- exec_shell_command_async(
- f"nohup {app_identifier['command_line']}"
- )
- elif (
- "executable" in app_identifier and app_identifier["executable"]
- ):
- exec_shell_command_async(
- f"nohup {app_identifier['executable']}"
- )
- elif "name" in app_identifier and app_identifier["name"]:
- exec_shell_command_async(f"nohup {app_identifier['name']}")
- elif isinstance(app_identifier, str):
- # Try direct execution with the identifier as fallback
- exec_shell_command_async(f"nohup {app_identifier}")
- else:
- # Handle window switching for running instances
- focused = self.get_focused()
- idx = next(
- (i for i, inst in enumerate(instances) if inst["address"] == focused),
- -1,
- )
- next_inst = instances[(idx + 1) % len(instances)]
- exec_shell_command(
- f"hyprctl dispatch focuswindow address:{next_inst['address']}"
- )
-
- def _on_child_enter(self, widget, event):
- """Maintain hover state when entering child widgets"""
- self.is_hovered = True
- if self.hide_id:
- GLib.source_remove(self.hide_id)
- self.hide_id = None
- return False # Continue event propagation
-
- def toggle_dock(self, show):
- """Show or hide the dock immediately"""
- if show:
- if self.is_hidden:
- self.is_hidden = False
- self.dock_full.add_style_class("show-dock")
- self.dock_full.remove_style_class("hide-dock")
- if self.hide_id:
- GLib.source_remove(self.hide_id)
- self.hide_id = None
- else:
- if not self.is_hidden:
- self.is_hidden = True
- self.dock_full.add_style_class("hide-dock")
- self.dock_full.remove_style_class("show-dock")
-
- def delay_hide(self):
- """Schedule hiding after short delay"""
- if self.hide_id:
- GLib.source_remove(self.hide_id)
- self.hide_id = GLib.timeout_add(1000, self.hide_dock)
-
- def hide_dock(self):
- """Finalize hiding procedure"""
- self.toggle_dock(show=False)
- self.hide_id = None
- return False
-
- def check_hide(self, *args):
- """Determine if dock should auto-hide"""
- clients = self.get_clients()
- current_ws = self.get_workspace()
- ws_clients = [w for w in clients if w["workspace"]["id"] == current_ws]
-
- if not ws_clients:
- self.toggle_dock(show=True)
- elif any(not w.get("floating") and not w.get("fullscreen") for w in ws_clients):
- self.delay_hide()
- else:
- self.toggle_dock(show=True)
-
- def update_dock(self, *args):
- """Refresh dock contents and clear drag lock."""
- self.update_app_map() # Update app map before creating buttons
- arranger_handler = getattr(self, "_arranger_handler", None)
- if arranger_handler:
- remove_handler(arranger_handler)
- clients = self.get_clients()
-
- # Create a mapping of window class to instances
- running_windows = {}
- for c in clients:
- # Try multiple identification methods in order of reliability
- window_id = None
-
- # Try initialClass first (most reliable)
- if class_name := c.get("initialClass", "").lower():
- window_id = class_name
-
- # Try class second (fallback)
- elif class_name := c.get("class", "").lower():
- window_id = class_name
-
- # Use title as last resort if both class identifiers are missing
- elif title := c.get("title", "").lower():
- # Extract app name from title (common format: "App Name - Document")
- possible_name = title.split(" - ")[0].strip()
- if (
- possible_name and len(possible_name) > 1
- ): # Avoid single letter app names
- window_id = possible_name
- else:
- window_id = title # Use full title if we can't extract a good name
-
- # Use generic identifier as absolute fallback
- if not window_id:
- window_id = "unknown-app"
-
- # Log window for debugging purposes
- logging.debug(
- f"Window detected: {window_id} (from {c.get('initialClass', '')}/{c.get('class', '')}/{c.get('title', '')})"
- )
-
- # Add to running windows - store with both original and normalized keys
- running_windows.setdefault(window_id, []).append(c)
-
- # Also store with normalized key for more flexible matching
- normalized_id = self._normalize_window_class(window_id)
- if normalized_id != window_id:
- running_windows.setdefault(normalized_id, []).extend(
- running_windows[window_id]
- )
-
- # Map pinned apps to their running instances
- pinned_buttons = []
- used_window_classes = set() # Track which window classes we've already assigned
-
- for app_data in self.pinned:
- app = self.find_app(app_data)
-
- # Try to find running instances for this pinned app
- instances = []
- matched_class = None
-
- # Extract all possible identifiers from app_data for matching
- possible_identifiers = []
-
- if isinstance(app_data, dict):
- for key in [
- "window_class",
- "executable",
- "command_line",
- "name",
- "display_name",
- ]:
- if key in app_data and app_data[key]:
- possible_identifiers.append(app_data[key].lower())
- elif isinstance(app_data, str):
- possible_identifiers.append(app_data.lower())
-
- # Add identifiers from DesktopApp if available
- if app:
- if app.window_class:
- possible_identifiers.append(app.window_class.lower())
- if app.executable:
- possible_identifiers.append(app.executable.split("/")[-1].lower())
- if app.command_line:
- cmd_parts = app.command_line.split()
- if cmd_parts:
- possible_identifiers.append(cmd_parts[0].split("/")[-1].lower())
- if app.name:
- possible_identifiers.append(app.name.lower())
- if app.display_name:
- possible_identifiers.append(app.display_name.lower())
-
- # Remove duplicates
- possible_identifiers = list(set(possible_identifiers))
-
- # Try each identifier for matching
- for identifier in possible_identifiers:
- # Try direct match
- if identifier in running_windows:
- instances = running_windows[identifier]
- matched_class = identifier
- break
-
- # Try normalized version
- normalized = self._normalize_window_class(identifier)
- if normalized in running_windows:
- instances = running_windows[normalized]
- matched_class = normalized
- break
-
- # Try substring matching for window classes (less reliable but helpful)
- for window_class in running_windows:
- # For substring matching, ensure the identifier is at least 3 chars
- # to avoid too broad matches
- if len(identifier) >= 3 and identifier in window_class:
- instances = running_windows[window_class]
- matched_class = window_class
- logging.debug(
- f"Substring match: {identifier} in {window_class}"
- )
- break
-
- if matched_class:
- break
-
- # Mark the matched class as used
- if matched_class:
- used_window_classes.add(matched_class)
- # Also mark the normalized version as used
- used_window_classes.add(self._normalize_window_class(matched_class))
- logging.debug(
- f"Matched pinned app {app_data} to running instances via {matched_class}"
- )
-
- # Create button for this pinned app with any found instances
- pinned_buttons.append(self.create_button(app_data, instances))
-
- # For any remaining window classes that aren't assigned to pinned apps
- open_buttons = []
- for class_name, instances in running_windows.items():
- if class_name not in used_window_classes:
- # Enhanced app identification for running windows
- app = None
-
- # Try multiple methods to find the correct app
- # 1. Direct lookup by class name
- app = self.app_identifiers.get(class_name)
-
- # 2. Try with normalized class name
- if not app:
- norm_class = self._normalize_window_class(class_name)
- app = self.app_identifiers.get(norm_class)
-
- # 3. Try with our more robust find_app method
- if not app:
- app = self.find_app_by_key(class_name)
-
- # 4. Try using window title which often contains app name
- if not app and instances and instances[0].get("title"):
- title = instances[0].get("title", "")
- # Extract potential app name from title (common format: "App Name - Document")
- possible_name = title.split(" - ")[0].strip()
- if len(possible_name) > 2: # Avoid very short names
- app = self.find_app_by_key(possible_name)
-
- # Create comprehensive app data if app was found
- if app:
- app_data = {
- "name": app.name,
- "display_name": app.display_name,
- "window_class": app.window_class,
- "executable": app.executable,
- "command_line": app.command_line,
- }
- identifier = app_data
- else:
- # Fallback to just class name
- identifier = class_name
-
- open_buttons.append(self.create_button(identifier, instances))
-
- # Assemble dock layout
- children = pinned_buttons
- # Only add separator if both pinned and open buttons exist
- if pinned_buttons and open_buttons:
- children += [
- Box(
- orientation="v"
- if not data.VERTICAL
- and not data.VERTICAL_RIGHT_ALIGN
- and not data.BOTTOM_BAR
- else "h",
- v_expand=not data.VERTICAL
- and not data.VERTICAL_RIGHT_ALIGN
- and not data.BOTTOM_BAR,
- h_expand=data.VERTICAL
- and data.VERTICAL_RIGHT_ALIGN
- and data.BOTTOM_BAR,
- name="dock-separator",
- )
- ]
- children += open_buttons
-
- self.view.children = children
- idle_add(self._update_size)
- self._drag_in_progress = False # Clear the drag lock
-
- def _update_size(self):
- """Update window size based on content"""
- width, _ = self.view.get_preferred_width()
- self.set_size_request(width, -1)
- return False
-
- def get_clients(self):
- """Get current client list"""
- try:
- return json.loads(self.conn.send_command("j/clients").reply.decode())
- except json.JSONDecodeError:
- return []
-
- def get_focused(self):
- """Get focused window address"""
- try:
- return json.loads(
- self.conn.send_command("j/activewindow").reply.decode()
- ).get("address", "")
- except json.JSONDecodeError:
- return ""
-
- def get_workspace(self):
- """Get current workspace ID"""
- try:
- return json.loads(
- self.conn.send_command("j/activeworkspace").reply.decode()
- ).get("id", 0)
- except json.JSONDecodeError:
- return 0
-
- def check_occlusion_state(self):
- """Periodic occlusion check"""
- # Skip occlusion check if hovered or dragging an icon
- if self.is_hovered or self._drag_in_progress:
- self.dock_full.remove_style_class("occluded")
- return True
-
- # If always_occluded is enabled, keep the dock occluded
- if self.always_occluded:
- self.dock_full.add_style_class("occluded")
- return True
-
- occlusion_region = (
- ("bottom", OCCLUSION)
- if not data.VERTICAL
- and not data.VERTICAL_RIGHT_ALIGN
- and not data.BOTTOM_BAR
- else ("left", OCCLUSION)
- if data.VERTICAL_RIGHT_ALIGN
- else ("right", OCCLUSION)
- )
- if check_occlusion(occlusion_region) or not self.view.get_children():
- self.dock_full.add_style_class("occluded")
- else:
- self.dock_full.remove_style_class("occluded")
- return True
-
- def _find_drag_target(self, widget):
- """Find valid drag target in viewport"""
- children = self.view.get_children()
- while widget is not None and widget not in children:
- widget = widget.get_parent() if hasattr(widget, "get_parent") else None
- return widget
-
- def on_drag_data_get(self, widget, drag_context, data, info, time):
- """Handle drag start"""
- target = self._find_drag_target(
- widget.get_parent() if isinstance(widget, Box) else widget
- )
- if target is not None:
- index = self.view.get_children().index(target)
- data.set_text(str(index), -1)
-
- def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
- """Handle drop event"""
- target = self._find_drag_target(
- widget.get_parent() if isinstance(widget, Box) else widget
- )
- if target is None:
- return
- try:
- source_index = int(data.get_text())
- except (TypeError, ValueError):
- return
-
- children = self.view.get_children()
- try:
- target_index = children.index(target)
- except ValueError:
- return
-
- if source_index != target_index:
- # Check for separator to detect cross-section drag
- separator_index = -1
- for i, child in enumerate(children):
- if child.get_name() == "dock-separator":
- separator_index = i
- break
-
- # Detect if we're dragging across the separator (between pinned/unpinned)
- cross_section_drag = separator_index != -1 and (
- (source_index < separator_index and target_index > separator_index)
- or (source_index > separator_index and target_index < separator_index)
- )
-
- # Move the item in the children list
- child = children.pop(source_index)
- children.insert(target_index, child)
- self.view.children = children # Update view immediately
-
- # Update pinned apps configuration
- self.update_pinned_apps(skip_update=not cross_section_drag)
-
- # Force immediate update if we dragged across sections to update separator visibility
- if cross_section_drag:
- GLib.idle_add(self.update_dock)
-
- def on_drag_end(self, widget, drag_context):
- """Handles drag end, for unpinning and closing apps."""
- # If a drag is already in progress, ignore duplicate calls.
- if not self._drag_in_progress:
- return
-
- def process_drag_end():
- """Inner function to handle drag end, safe to call with idle_add."""
- if self.get_mapped(): # Check if the window is mapped
- # Get window geometry *before* any modifications
- display = Gdk.Display.get_default()
- _, x, y, _ = display.get_pointer() # Get pointer relative to the screen
- window = self.get_window()
- if window: # Check if we got a window
- win_x, win_y, width, height = window.get_geometry()
- if not (
- win_x <= x <= win_x + width and win_y <= y <= win_y + height
- ):
- # Drag ended outside the dock - get app data from the button
- app_id = widget.app_identifier
- instances = widget.instances
-
- # Find this app in pinned list
- app_index = -1
- for i, pinned_app in enumerate(self.pinned):
- # Match by identifier
- if pinned_app == app_id:
- app_index = i
- break
- # For dict format, match by name
- elif isinstance(pinned_app, dict) and isinstance(
- app_id, dict
- ):
- if pinned_app.get("name") == app_id.get("name"):
- app_index = i
- break
-
- # If it's a pinned app, remove from pinned
- if app_index >= 0:
- self.pinned.pop(app_index)
- self.config["pinned_apps"] = self.pinned
- self.update_pinned_apps_file()
- self.update_dock() # Update dock after file save
- elif instances: # Close if running and unpinned
- # Close running app (if not pinned)
- address = instances[0].get("address")
- if address:
- exec_shell_command(
- f"hyprctl dispatch closewindow address:{address}"
- )
- self.update_dock() # Update dock after closing window
- self._drag_in_progress = False # Clear the drag lock
-
- GLib.idle_add(process_drag_end) # Deferred execution with idle_add
-
- def check_config_change(self):
- """Check if the config file has been modified."""
- new_config = data.read_config()
- if new_config.get("pinned_apps", []) != self.config.get("pinned_apps", []):
- self.config = new_config
- self.pinned = self.config.get("pinned_apps", [])
- self.update_app_map() # Update app_map when config changes
- self.update_dock()
- return True # Continue the timeout
-
- def update_pinned_apps_file(self):
- """Writes the updated pinned apps list to modus.json"""
- config_path = get_relative_path("../config/assets/modus.json")
- try:
- with open(config_path, "w") as file:
- json.dump(self.config, file, indent=4)
- return True
- except Exception as e:
- logging.error(f"Failed to write dock config: {e}")
- return False
-
- def update_pinned_apps(self, skip_update=False):
- """Update pinned apps configuration by collecting all items before the separator"""
- pinned_children = []
- for child in self.view.get_children():
- if child.get_name() == "dock-separator":
- break # Stop at separator
-
- # Use stored app identifier from the button
- if hasattr(child, "app_identifier"):
- # If the button has a desktop_app, use it to create comprehensive data
- if hasattr(child, "desktop_app") and child.desktop_app:
- app = child.desktop_app
- app_data = {
- "name": app.name,
- "display_name": app.display_name,
- "window_class": app.window_class,
- "executable": app.executable,
- "command_line": app.command_line,
- }
- pinned_children.append(app_data)
- else:
- # Keep existing app_identifier (might be dict or string)
- pinned_children.append(child.app_identifier)
-
- # Directly update both config and local pinned list
- self.config["pinned_apps"] = pinned_children
- self.pinned = pinned_children # Update local state immediately
-
- # Write to file first to ensure persistence
- file_updated = self.update_pinned_apps_file()
-
- # Only update the dock if needed and file was successfully written
- if file_updated and not skip_update:
- self.update_dock() # Refresh dock only after file is saved
-
- # Static method to update all dock instances
- @staticmethod
- def notify_config_change():
- """Notify all dock instances to reload their configuration immediately."""
- for dock in Dock._instances:
- # Use idle_add to avoid potential issues with direct calls
- GLib.idle_add(dock.check_config_change_immediate)
-
- def check_config_change_immediate(self):
- """Immediately check for configuration changes and update if needed."""
- new_config = data.read_config()
-
- # Check if always_occluded setting has changed
- previous_always_occluded = self.always_occluded
- self.always_occluded = data.DOCK_ALWAYS_OCCLUDED
-
- # If always_occluded changed, update the dock appearance
- if previous_always_occluded != self.always_occluded:
- if self.always_occluded and not self.is_hovered:
- self.dock_full.add_style_class("occluded")
- elif not self.always_occluded and not self.is_hovered:
- # Perform a normal occlusion check
- occlusion_region = (
- ("bottom", OCCLUSION)
- if not data.VERTICAL and not data.BOTTOM_BAR
- else ("left", OCCLUSION)
- if data.VERTICAL_RIGHT_ALIGN
- else ("right", OCCLUSION)
- )
- if check_occlusion(occlusion_region) or not self.view.get_children():
- self.dock_full.add_style_class("occluded")
- else:
- self.dock_full.remove_style_class("occluded")
-
- if new_config.get("pinned_apps", []) != self.config.get("pinned_apps", []):
- self.config = new_config
- self.pinned = self.config.get("pinned_apps", [])
- self.update_app_map()
- self.update_dock()
- return False # Don't repeat
-
- # Add a static method to handle dock visibility changes
- @staticmethod
- def update_visibility(visible):
- """Update visibility for all dock instances."""
- for dock in Dock._instances:
- dock.set_visible(visible)
diff --git a/modules/launcher/components/__init__.py b/modules/launcher/components/__init__.py
deleted file mode 100644
index da2ef07..0000000
--- a/modules/launcher/components/__init__.py
+++ /dev/null
@@ -1,16 +0,0 @@
-from .applauncher import *
-from .bluetooth import *
-from .cliphist import *
-from .dashboard import *
-from .emoji import *
-from .power import *
-from .sh import *
-from .wallpapers import *
-from .wifi import *
-from .calendar import *
-from .dashboard import *
-from .tools import *
-from .kanban import *
-from .tmux import *
-from .window_switcher import *
-from .notification_center import *
diff --git a/modules/launcher/components/applauncher.py b/modules/launcher/components/applauncher.py
deleted file mode 100644
index 83380de..0000000
--- a/modules/launcher/components/applauncher.py
+++ /dev/null
@@ -1,630 +0,0 @@
-import operator
-from collections.abc import Iterator
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.widgets.entry import Entry
-from fabric.widgets.image import Image
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from fabric.utils import (
- DesktopApp,
- get_desktop_applications,
- idle_add,
- remove_handler,
- exec_shell_command_async,
-)
-from fabric.utils.helpers import get_relative_path
-from gi.repository import GLib, Gdk
-import utils.icons as icons
-import config.data as data
-import json
-import os
-import re
-import math
-import numpy as np
-import subprocess
-from modules.dock import Dock # Import the Dock class
-
-
-class AppLauncher(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="app-launcher",
- visible=False,
- all_visible=False,
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
- self.selected_index = -1 # Track the selected item index
-
- self._arranger_handler: int = 0
- self._all_apps = get_desktop_applications()
- self.config = data.read_config()
-
- # Calculator history initialization
- self.calc_history_path = f"{data.CACHE_DIR}/calc.json"
- if os.path.exists(self.calc_history_path):
- with open(self.calc_history_path, "r") as f:
- self.calc_history = json.load(f)
- else:
- self.calc_history = []
-
- self.viewport = Box(name="viewport", spacing=4, orientation="v")
- self.search_entry = Entry(
- name="search-entry",
- placeholder="Search Applications...",
- h_expand=True,
- notify_text=self.notify_text, # Use the method instead of lambda
- on_activate=lambda entry, *_: self.on_search_entry_activate(
- entry.get_text()
- ),
- on_key_press_event=self.on_search_entry_key_press, # Handle key presses
- )
- self.search_entry.props.xalign = 0.5
- self.scrolled_window = ScrolledWindow(
- name="scrolled-window",
- spacing=10,
- min_content_size=(450, 105),
- max_content_size=(450, 105),
- child=self.viewport,
- )
-
- self.header_box = Box(
- name="header_box",
- spacing=10,
- orientation="h",
- children=[
- Button(
- name="config-button",
- child=Label(name="config-label", markup=icons.config),
- on_clicked=lambda *_: (
- exec_shell_command_async(
- f"python {get_relative_path('../../../config/config.py')}"
- ),
- self.close_launcher(),
- ),
- ),
- self.search_entry,
- ],
- )
-
- self.launcher_box = Box(
- name="launcher-box",
- spacing=10,
- h_expand=True,
- orientation="v",
- children=[
- self.header_box,
- self.scrolled_window,
- ],
- )
-
- self.resize_viewport()
-
- self.add(self.launcher_box)
- self.show_all()
-
- def close_launcher(self):
- self.viewport.children = []
- self.selected_index = -1 # Reset selection
- self.launcher.close()
-
- def open_launcher(self):
- self._all_apps = get_desktop_applications()
- self.arrange_viewport()
-
- def ensure_initialized(self):
- """Make sure the launcher is initialized with apps list before opening"""
- if not hasattr(self, "_initialized"):
- # Force pre-loading apps
- self._all_apps = get_desktop_applications()
- self._initialized = True
- return True # Was initialized for first time
- return False # Was already initialized
-
- def arrange_viewport(self, query: str = ""):
- if query.startswith("="):
- # In calculator mode, update history view once (not per keystroke)
- self.show_scrolled_window()
- self.launcher.dashboard.hide()
- self.update_calculator_viewport()
- return
- remove_handler(self._arranger_handler) if self._arranger_handler else None
- self.viewport.children = []
- self.selected_index = -1 # Clear selection when viewport changes
-
- if query.startswith("/"):
- self.launcher.dashboard.hide()
- return
-
- if query.strip():
- self.launcher.dashboard.hide()
- self.show_scrolled_window()
- else:
- self.launcher.dashboard.show()
- self.hide_scrolled_window()
- return False
-
- filtered_apps_iter = iter(
- sorted(
- [
- app
- for app in self._all_apps
- if query.casefold()
- in (
- (app.display_name or "")
- + (" " + app.name + " ")
- + (app.generic_name or "")
- ).casefold()
- ],
- key=lambda app: (app.display_name or "").casefold(),
- )
- )
- should_resize = operator.length_hint(filtered_apps_iter) == len(self._all_apps)
-
- self._arranger_handler = idle_add(
- lambda apps_iter: self.add_next_application(apps_iter)
- or self.handle_arrange_complete(should_resize, query),
- filtered_apps_iter,
- pin=True,
- )
-
- def handle_arrange_complete(self, should_resize, query):
- if should_resize:
- self.resize_viewport()
- # Only auto-select first item if query exists
- if query.strip() != "" and self.viewport.get_children():
- self.update_selection(0)
- return False
-
- def add_next_application(self, apps_iter: Iterator[DesktopApp]):
- if not (app := next(apps_iter, None)):
- return False
- self.viewport.add(self.bake_application_slot(app))
- return True
-
- def show_scrolled_window(self):
- if hasattr(self, "scrolled_window") and self.scrolled_window:
- self.scrolled_window.show_all()
-
- def hide_scrolled_window(self):
- if hasattr(self, "scrolled_window") and self.scrolled_window:
- self.scrolled_window.hide()
-
- def resize_viewport(self):
- self.scrolled_window.set_min_content_width(
- self.viewport.get_allocation().width # type: ignore
- )
- return False
-
- def bake_application_slot(self, app: DesktopApp, **kwargs) -> Button:
- button = Button(
- name="slot-button",
- child=Box(
- name="slot-box",
- orientation="h",
- spacing=10,
- children=[
- Image(
- name="app-icon",
- pixbuf=app.get_icon_pixbuf(size=24),
- h_align="start",
- ),
- Label(
- name="app-label",
- label=app.display_name or "Unknown",
- ellipsization="end",
- v_align="center",
- h_align="center",
- ),
- Label(
- name="app-desc",
- label=app.description or "",
- ellipsization="end",
- v_align="center",
- h_align="start",
- h_expand=True,
- ),
- ],
- ),
- tooltip_text=app.description,
- on_clicked=lambda *_: (app.launch(), self.close_launcher()),
- **kwargs,
- )
- return button
-
- def update_selection(self, new_index: int):
- # Unselect current
- if self.selected_index != -1 and self.selected_index < len(
- self.viewport.get_children()
- ):
- current_button = self.viewport.get_children()[self.selected_index]
- current_button.get_style_context().remove_class("selected")
- # Select new
- if new_index != -1 and new_index < len(self.viewport.get_children()):
- new_button = self.viewport.get_children()[new_index]
- new_button.get_style_context().add_class("selected")
- self.selected_index = new_index
- self.scroll_to_selected(new_button)
- else:
- self.selected_index = -1
-
- def scroll_to_selected(self, button):
- def scroll():
- adj = self.scrolled_window.get_vadjustment()
- alloc = button.get_allocation()
- if alloc.height == 0:
- return False # Retry if allocation isn't ready
-
- y = alloc.y
- height = alloc.height
- page_size = adj.get_page_size()
- current_value = adj.get_value()
-
- # Calculate visible boundaries
- visible_top = current_value
- visible_bottom = current_value + page_size
-
- if y < visible_top:
- # Item above viewport - align to top
- adj.set_value(y)
- elif y + height > visible_bottom:
- # Item below viewport - align to bottom
- new_value = y + height - page_size
- adj.set_value(new_value)
- # No action if already fully visible
- return False
-
- GLib.idle_add(scroll)
-
- def on_search_entry_activate(self, text):
- # This method is now only used for non-Enter key activations
- commands = self.config.get("commands", {})
-
- # Check if text is a mathematical expression
- if text and (text[0].isdigit()):
- self.evaluate_calculator_expression(text)
- return
-
- if text in commands:
- command = commands[text]
- eval(command, {"launcher": self.launcher})
- else:
- children = self.viewport.get_children()
- if children:
- # Only activate if we have selection or non-empty query
- if text.strip() == "" and self.selected_index == -1:
- return # Prevent accidental activation when empty
- selected_index = self.selected_index if self.selected_index != -1 else 0
- if 0 <= selected_index < len(children):
- children[selected_index].clicked()
-
- def on_search_entry_key_press(self, widget, event):
- text = widget.get_text()
-
- # Check if we're in calculator mode
- if text.startswith("="):
- if event.keyval == Gdk.KEY_Down:
- self.move_selection(1)
- return True
- elif event.keyval == Gdk.KEY_Up:
- self.move_selection(-1)
- return True
- elif event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
- # In calculator mode, if a history item is highlighted:
- if self.selected_index != -1 and self.selected_index < len(
- self.calc_history
- ):
- if event.state & Gdk.ModifierType.SHIFT_MASK:
- # Shift+Enter deletes the selected calculator history item
- self.delete_selected_calc_history()
- else:
- # Normal Enter copies the result
- selected_text = self.calc_history[self.selected_index]
- self.copy_text_to_clipboard(selected_text)
- # Clear selection so new expressions are evaluated on further Return presses
- self.selected_index = -1
- else:
- # Force reset selection index
- self.selected_index = -1
- # No item selected, evaluate the expression
- self.evaluate_calculator_expression(text)
- return True
- elif event.keyval == Gdk.KEY_Escape:
- self.close_launcher()
- return True
- return False
- else:
- # Normal app mode behavior
- if event.keyval == Gdk.KEY_Down:
- self.move_selection(1)
- return True
- elif event.keyval == Gdk.KEY_Up:
- self.move_selection(-1)
- return True
- elif event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter) and (
- event.state & Gdk.ModifierType.SHIFT_MASK
- ):
- # Only add to dock when in app mode (not calculator mode)
- self.add_selected_app_to_dock()
- return True
- elif event.keyval == Gdk.KEY_Escape:
- self.close_launcher()
- return True
- return False
-
- def notify_text(self, entry, *_):
- """Handle text changes in the search entry"""
- text = entry.get_text()
- if text.startswith("="):
- # In calculator mode, show scrolled window and update viewport
- self.show_scrolled_window()
- self.launcher.dashboard.hide()
- self.update_calculator_viewport()
- # Always reset selection when typing a new expression
- self.selected_index = -1
- else:
- self.arrange_viewport(text)
-
- def add_selected_app_to_dock(self):
- """Adds the currently selected application to the modus.json file with comprehensive metadata."""
- children = self.viewport.get_children()
- if (
- not children
- or self.selected_index == -1
- or self.selected_index >= len(children)
- ):
- return # No app selected
-
- selected_button = children[self.selected_index]
- # Extract the app's display name from the label to find the DesktopApp object
- selected_app = next(
- (
- app
- for app in self._all_apps
- if app.display_name
- == selected_button.get_child().get_children()[1].props.label
- ),
- None,
- )
- if not selected_app:
- return
-
- # Create comprehensive app data dictionary - Include all available properties
- # Filter out None values to keep the JSON clean
- app_data = {
- k: v
- for k, v in {
- "name": selected_app.name,
- "display_name": selected_app.display_name,
- "window_class": selected_app.window_class,
- "executable": selected_app.executable,
- "command_line": selected_app.command_line,
- "icon_name": selected_app.icon_name,
- }.items()
- if v is not None
- }
-
- config_path = get_relative_path("../../../config/assets/modus.json")
- try:
- with open(config_path, "r") as file:
- data = json.load(file)
- except (FileNotFoundError, json.JSONDecodeError):
- data = {"pinned_apps": []}
-
- # Check if app is already pinned (by name)
- already_pinned = False
- for pinned_app in data.get("pinned_apps", []):
- if (
- isinstance(pinned_app, dict)
- and pinned_app.get("name") == app_data["name"]
- ):
- already_pinned = True
- # Update existing entry with latest app data
- pinned_app.update(app_data)
- break
- elif isinstance(pinned_app, str) and pinned_app == app_data["name"]:
- already_pinned = True
- # Replace string format with new comprehensive format
- data["pinned_apps"].remove(pinned_app)
- data["pinned_apps"].append(app_data)
- break
-
- # Add to pinned apps if not already there
- if not already_pinned:
- data.setdefault("pinned_apps", []).append(app_data)
-
- # Write the updated config
- with open(config_path, "w") as file:
- json.dump(data, file, indent=4)
-
- # Notify dock instances of the configuration change immediately
- Dock.notify_config_change()
-
- def move_selection(self, delta: int):
- children = self.viewport.get_children()
- if not children:
- return
- # Allow starting selection from nothing when empty
- if self.selected_index == -1 and delta == 1:
- new_index = 0
- else:
- new_index = self.selected_index + delta
- new_index = max(0, min(new_index, len(children) - 1))
- self.update_selection(new_index)
-
- def save_calc_history(self):
- with open(self.calc_history_path, "w") as f:
- json.dump(self.calc_history, f)
-
- def evaluate_calculator_expression(self, text: str):
- # Add debug print
- print(f"Evaluating calculator expression: {text}")
-
- # Remove the '=' prefix and extra spaces
- expr = text.lstrip("=").strip()
- if not expr:
- return
-
- # Common replacements to make expressions more natural
- replacements = {
- "^": "**", # Power operator
- "ร": "*", # Multiplication
- "รท": "/", # Division
- "ฯ": "np.pi", # Pi constant
- "pi": "np.pi", # Pi constant (text)
- "e": "np.e", # Euler's number
- "sin(": "np.sin(", # Sine
- "cos(": "np.cos(", # Cosine
- "tan(": "np.tan(", # Tangent
- "log(": "np.log10(", # Log base 10
- "ln(": "np.log(", # Natural log
- "sqrt(": "np.sqrt(", # Square root
- "abs(": "np.abs(", # Absolute value
- "exp(": "np.exp(", # Exponential
- }
-
- # Apply all replacements
- for old, new in replacements.items():
- expr = expr.replace(old, new)
-
- # Replace factorial: e.g. 5! -> np.factorial(5)
- expr = re.sub(r"(\d+)!", r"np.factorial(\1)", expr)
-
- # Replace brackets: allow [] and {} as ()
- for old, new in [("[", "("), ("]", ")"), ("{", "("), ("}", ")")]:
- expr = expr.replace(old, new)
-
- # Define the safe execution environment
- safe_dict = {
- "np": np,
- "math": math,
- "arange": np.arange,
- "linspace": np.linspace,
- "array": np.array,
- }
-
- try:
- # Evaluate the expression in the safe environment
- result = eval(expr, {"__builtins__": None}, safe_dict)
-
- # Format the result based on its type
- if isinstance(result, np.ndarray):
- if result.size > 10: # Truncate large arrays
- result_str = f"Array of shape {result.shape}"
- else:
- result_str = str(result)
- elif isinstance(result, (int, float, np.number)):
- # Format numbers nicely - integers as integers, floats with limited precision
- if isinstance(result, (int, np.integer)) or result.is_integer():
- result_str = str(int(result))
- else:
- result_str = f"{float(result):.10g}" # Remove trailing zeros
- else:
- result_str = str(result)
-
- except Exception as e:
- result_str = f"Error: {str(e)}"
-
- # Prepend to history (newest first)
- self.calc_history.insert(0, f"{text} => {result_str}")
- self.save_calc_history()
- self.update_calculator_viewport()
-
- def update_calculator_viewport(self):
- self.viewport.children = []
- for item in self.calc_history:
- btn = self.create_calc_history_button(item)
- self.viewport.add(btn)
- # Don't reset selection index here automatically
- # Ensure selection state stays valid
- if self.selected_index >= len(self.calc_history):
- self.selected_index = -1
-
- def create_calc_history_button(self, text: str) -> Button:
- # Parse the result to create a more readable display
- if "=>" in text:
- parts = text.split("=>")
- expression = parts[0].strip()
- result = parts[1].strip()
-
- # For very long results, truncate for display but keep full in tooltip
- display_text = text
- if len(result) > 50: # Truncate long results
- display_text = f"{expression} => {result[:47]}..."
-
- btn = Button(
- name="slot-button", # reuse existing CSS styling
- child=Box(
- name="calc-slot-box",
- orientation="h",
- spacing=10,
- children=[
- Label(
- name="calc-label",
- label=display_text,
- ellipsization="end",
- v_align="center",
- h_align="center",
- ),
- ],
- ),
- tooltip_text=text,
- on_clicked=lambda *_: self.copy_text_to_clipboard(text),
- )
- else:
- # Fallback for non-calculation entries
- btn = Button(
- name="slot-button",
- child=Box(
- name="calc-slot-box",
- orientation="h",
- spacing=10,
- children=[
- Label(
- name="calc-label",
- label=text,
- ellipsization="end",
- v_align="center",
- h_align="center",
- ),
- ],
- ),
- tooltip_text=text,
- on_clicked=lambda *_: self.copy_text_to_clipboard(text),
- )
- return btn
-
- def copy_text_to_clipboard(self, text: str):
- # Split the text on "=>" and copy only the result part if available
- parts = text.split("=>", 1)
- copy_text = parts[1].strip() if len(parts) > 1 else text
- try:
- subprocess.run(["wl-copy"], input=copy_text.encode(), check=True)
- except subprocess.CalledProcessError as e:
- print(f"Clipboard copy failed: {e}")
-
- def delete_selected_calc_history(self):
- if self.selected_index != -1 and self.selected_index < len(self.calc_history):
- # Store the current index before deletion
- current_index = self.selected_index
-
- # Delete the item
- del self.calc_history[current_index]
- self.save_calc_history()
-
- # Determine the new selection index
- # If we deleted the first item, stay at index 0
- # Otherwise, move to the previous item
- new_index = 0 if current_index == 0 else current_index - 1
-
- # Reset selection before updating viewport
- self.selected_index = -1
-
- # Update the viewport
- self.update_calculator_viewport()
-
- # If we still have items, select the determined index
- if len(self.calc_history) > 0:
- self.update_selection(min(new_index, len(self.calc_history) - 1))
diff --git a/modules/launcher/components/bluetooth.py b/modules/launcher/components/bluetooth.py
deleted file mode 100644
index c3f6930..0000000
--- a/modules/launcher/components/bluetooth.py
+++ /dev/null
@@ -1,170 +0,0 @@
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.image import Image
-from fabric.widgets.button import Button
-from fabric.widgets.centerbox import CenterBox
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from fabric.bluetooth import BluetoothClient, BluetoothDevice
-import utils.icons as icons
-
-
-class BluetoothDeviceSlot(CenterBox):
- def __init__(self, device: BluetoothDevice, **kwargs):
- super().__init__(name="bluetooth-device", **kwargs)
- self.device = device
- self.device.connect("changed", self.on_changed)
- self.device.connect(
- "notify::closed", lambda *_: self.device.closed and self.destroy()
- )
-
- self.connection_label = Label(
- name="bluetooth-connection", markup=icons.bluetooth_disconnected
- )
- self.connect_button = Button(
- name="bluetooth-connect",
- label="Connect",
- on_clicked=lambda *_: self.device.set_connecting(not self.device.connected),
- )
-
- self.start_children = [
- Box(
- spacing=8,
- children=[
- Image(icon_name=device.icon_name + "-symbolic", size=32),
- Label(label=device.name),
- self.connection_label,
- ],
- )
- ]
- self.end_children = self.connect_button
-
- self.device.emit("changed") # to update display status
-
- def on_changed(self, *_):
- self.connection_label.set_markup(
- icons.bluetooth_connected
- if self.device.connected
- else icons.bluetooth_disconnected
- )
- if self.device.connecting:
- self.connect_button.set_label(
- "Connecting..." if not self.device.connecting else "Disconnecting..."
- )
- else:
- self.connect_button.set_label(
- "Connect" if not self.device.connected else "Disconnect"
- )
- return
-
-
-class BluetoothConnections(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="bluetooth",
- spacing=4,
- orientation="vertical",
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
-
- self.buttons = self.launcher.dashboard.bluetooth_button
- self.bt_status_text = self.buttons.bluetooth_status_text
- self.bt_status_button = self.buttons.bluetooth_status_button
- self.bt_icon = self.buttons.bluetooth_icon
- self.bt_label = self.buttons.bluetooth_label
- self.bt_menu_button = self.buttons.bluetooth_menu_button
- self.bt_menu_label = self.buttons.bluetooth_menu_label
-
- self.client = BluetoothClient(on_device_added=self.on_device_added)
- self.scan_label = Label(name="bluetooth-scan-label", markup=icons.radar)
- self.scan_button = Button(
- name="bluetooth-scan",
- child=self.scan_label,
- tooltip_text="Scan for Bluetooth devices",
- on_clicked=lambda *_: self.client.toggle_scan(),
- )
- self.back_button = Button(
- name="bluetooth-back",
- child=Label(name="bluetooth-back-label", markup=icons.chevron_left),
- on_clicked=lambda *_: self.launcher.open("launcher"),
- )
-
- self.client.connect("notify::enabled", lambda *_: self.status_label())
- self.client.connect("notify::scanning", lambda *_: self.update_scan_label())
-
- self.paired_box = Box(spacing=2, orientation="vertical")
- self.available_box = Box(spacing=2, orientation="vertical")
-
- # Create a single content container with the required structure:
- # [Paired label] [Paired devices list] [Available label] [Available devices list]
- content_box = Box(spacing=4, orientation="vertical")
- content_box.add(self.paired_box)
- content_box.add(Label(name="bluetooth-section", label="Available"))
- content_box.add(self.available_box)
-
- self.children = [
- CenterBox(
- name="bluetooth-header",
- start_children=self.back_button,
- center_children=Label(name="bluetooth-text", label="Bluetooth Devices"),
- end_children=self.scan_button,
- ),
- ScrolledWindow(
- name="bluetooth-devices",
- min_content_size=(-1, -1),
- child=content_box,
- v_expand=True,
- ),
- ]
-
- # Trigger initial notifications to update status without delay.
- self.client.notify("scanning")
- self.client.notify("enabled")
-
- def status_label(self):
- print(self.client.enabled)
- if self.client.enabled:
- self.bt_status_text.set_label("Enabled")
- for i in [
- self.bt_status_button,
- self.bt_status_text,
- self.bt_icon,
- self.bt_label,
- self.bt_menu_button,
- self.bt_menu_label,
- ]:
- i.remove_style_class("disabled")
- self.bt_icon.set_markup(icons.bluetooth)
- else:
- self.bt_status_text.set_label("Disabled")
- for i in [
- self.bt_status_button,
- self.bt_status_text,
- self.bt_icon,
- self.bt_label,
- self.bt_menu_button,
- self.bt_menu_label,
- ]:
- i.add_style_class("disabled")
- self.bt_icon.set_markup(icons.bluetooth_off)
-
- def on_device_added(self, client: BluetoothClient, address: str):
- if not (device := client.get_device(address)):
- return
- slot = BluetoothDeviceSlot(device)
-
- if device.paired:
- return self.paired_box.add(slot)
- return self.available_box.add(slot)
-
- def update_scan_label(self):
- if self.client.scanning:
- self.scan_label.add_style_class("scanning")
- self.scan_button.add_style_class("scanning")
- self.scan_button.set_tooltip_text("Stop scanning for Bluetooth devices")
- else:
- self.scan_label.remove_style_class("scanning")
- self.scan_button.remove_style_class("scanning")
- self.scan_button.set_tooltip_text("Scan for Bluetooth devices")
-
diff --git a/modules/launcher/components/calendar.py b/modules/launcher/components/calendar.py
deleted file mode 100644
index 263e572..0000000
--- a/modules/launcher/components/calendar.py
+++ /dev/null
@@ -1,217 +0,0 @@
-import calendar
-from datetime import datetime
-import gi
-import utils.icons as icons
-from fabric.widgets.label import Label
-from fabric.widgets.box import Box
-from fabric.widgets.centerbox import CenterBox
-
-gi.require_version("Gtk", "3.0")
-from gi.repository import Gtk, GLib
-
-
-class Calendar(Gtk.Box):
- def __init__(self):
- super().__init__(
- orientation=Gtk.Orientation.VERTICAL, spacing=8, name="calendar"
- )
-
- self.current_year = datetime.now().year
- self.current_month = datetime.now().month
- self.current_day = datetime.now().day
- # Store previous month/year to determine transition direction.
- self.previous_key = (self.current_year, self.current_month)
-
- # Cache threshold in number of months away from the current view.
- self.cache_threshold = 1 # Allow current month +/- 1 month
-
- # Dictionary to store built views for each month.
- self.month_views = {}
-
- self.prev_month_button = Gtk.Button(
- name="prev-month-button",
- child=Label(name="month-button-label", markup=icons.chevron_left),
- )
- self.prev_month_button.connect("clicked", self.on_prev_month_clicked)
-
- self.month_label = Gtk.Label(name="month-label")
-
- self.next_month_button = Gtk.Button(
- name="next-month-button",
- child=Label(name="month-button-label", markup=icons.chevron_right),
- )
- self.next_month_button.connect("clicked", self.on_next_month_clicked)
-
- self.header = CenterBox(
- spacing=4,
- name="header",
- start_children=[self.prev_month_button],
- center_children=[
- self.month_label,
- ],
- end_children=[self.next_month_button],
- )
-
- self.add(self.header)
-
- self.weekday_row = Gtk.Box(spacing=4, name="weekday-row")
- self.pack_start(self.weekday_row, False, False, 0)
-
- # Create a stack to hold month days views.
- self.stack = Gtk.Stack(name="calendar-stack")
- self.stack.set_transition_duration(250)
- self.pack_start(self.stack, True, True, 0)
-
- self.update_header()
- self.update_calendar()
- GLib.timeout_add_seconds(60, self.check_date_change)
-
- def update_header(self):
- # Update header month label and weekday row.
- self.month_label.set_text(
- datetime(self.current_year, self.current_month, 1)
- .strftime("%B %Y")
- .capitalize()
- )
- # Clear existing children from weekday_row.
- for child in self.weekday_row.get_children():
- self.weekday_row.remove(child)
- days = self.get_weekday_initials()
- for day in days:
- label = Gtk.Label(label=day.upper(), name="weekday-label")
- self.weekday_row.pack_start(label, True, True, 0)
- self.weekday_row.show_all()
-
- def update_calendar(self):
- new_key = (self.current_year, self.current_month)
- # Set the transition type based on whether we're moving to a later or earlier month.
- if new_key > self.previous_key:
- self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_LEFT)
- elif new_key < self.previous_key:
- self.stack.set_transition_type(Gtk.StackTransitionType.SLIDE_RIGHT)
- # Update previous key for next comparison.
- self.previous_key = new_key
-
- # Create the month view if it doesn't exist.
- if new_key not in self.month_views:
- month_view = self.create_month_view(self.current_year, self.current_month)
- self.month_views[new_key] = month_view
- self.stack.add_titled(
- month_view,
- f"{self.current_year}_{self.current_month}",
- f"{self.current_year}_{self.current_month}",
- )
- # Switch the visible child in the stack.
- self.stack.set_visible_child_name(f"{self.current_year}_{self.current_month}")
- self.update_header()
- self.stack.show_all()
-
- # Purge any cached month views that are too far away.
- self.prune_cache()
-
- def prune_cache(self):
- # Compute a numerical value for a (year, month) key.
- def month_index(key):
- year, month = key
- return year * 12 + (month - 1)
-
- current_index = month_index((self.current_year, self.current_month))
- keys_to_remove = []
- for key in self.month_views:
- if abs(month_index(key) - current_index) > self.cache_threshold:
- keys_to_remove.append(key)
- for key in keys_to_remove:
- widget = self.month_views.pop(key)
- self.stack.remove(widget)
-
- def create_month_view(self, year, month):
- grid = Gtk.Grid(
- column_homogeneous=True, row_homogeneous=False, name="calendar-grid"
- )
-
- cal = calendar.Calendar(firstweekday=6)
- month_days = cal.monthdayscalendar(year, month)
-
- prev_month = month - 1 if month > 1 else 12
- next_month = month + 1 if month < 12 else 1
- prev_year = year if month > 1 else year - 1
- next_year = year if month < 12 else year + 1
-
- prev_month_days_count = calendar.monthrange(prev_year, prev_month)[1]
- next_month_day = 1
-
- for row, week in enumerate(month_days):
- for col, day in enumerate(week):
- day_box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, name="day-box")
- top_spacer = Gtk.Box(hexpand=True, vexpand=True)
- middle_box = Gtk.Box(hexpand=True, vexpand=True)
- bottom_spacer = Gtk.Box(hexpand=True, vexpand=True)
-
- if day == 0:
- if row == 0:
- day_num = prev_month_days_count - week[:col].count(0)
- label = Gtk.Label(label=str(day_num), name="prev-month-day")
- else:
- label = Gtk.Label(
- label=str(next_month_day), name="next-month-day"
- )
- next_month_day += 1
- else:
- label = Gtk.Label(label=str(day), name="day-label")
- if (
- day == self.current_day
- and month == datetime.now().month
- and year == datetime.now().year
- ):
- label.get_style_context().add_class("current-day")
-
- middle_box.pack_start(
- Gtk.Box(hexpand=True, vexpand=True), True, True, 0
- )
- middle_box.pack_start(label, False, False, 0)
- middle_box.pack_start(
- Gtk.Box(hexpand=True, vexpand=True), True, True, 0
- )
-
- day_box.pack_start(top_spacer, True, True, 0)
- day_box.pack_start(middle_box, True, True, 0)
- day_box.pack_start(bottom_spacer, True, True, 0)
-
- grid.attach(day_box, col, row, 1, 1)
-
- grid.show_all()
- return grid
-
- def get_weekday_initials(self):
- # Returns localized weekday initials.
- return [datetime(2023, 1, i + 1).strftime("%a")[:1] for i in range(7)]
-
- def on_prev_month_clicked(self, widget):
- if self.current_month == 1:
- self.current_month = 12
- self.current_year -= 1
- else:
- self.current_month -= 1
- self.update_calendar()
-
- def on_next_month_clicked(self, widget):
- if self.current_month == 12:
- self.current_month = 1
- self.current_year += 1
- else:
- self.current_month += 1
- self.update_calendar()
-
- def check_date_change(self):
- now = datetime.now()
- if (
- now.day != self.current_day
- or now.month != self.current_month
- or now.year != self.current_year
- ):
- self.current_day = now.day
- self.current_month = now.month
- self.current_year = now.year
- self.update_calendar()
- return True
-
diff --git a/modules/launcher/components/cliphist.py b/modules/launcher/components/cliphist.py
deleted file mode 100644
index 5747747..0000000
--- a/modules/launcher/components/cliphist.py
+++ /dev/null
@@ -1,504 +0,0 @@
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.widgets.entry import Entry
-from fabric.widgets.image import Image
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from fabric.utils import idle_add, remove_handler
-from fabric.utils.helpers import get_relative_path
-from gi.repository import GLib, Gdk, GdkPixbuf
-import subprocess
-import os
-import re
-import sys
-import tempfile
-
-import utils.icons as icons
-
-
-class ClipHistory(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="clip-history",
- visible=False,
- all_visible=False,
- **kwargs,
- )
-
- # Create a temporary directory for image icons
- self.tmp_dir = tempfile.mkdtemp(prefix="cliphist-")
- self.image_cache = {} # Cache for image previews
-
- self.launcher = kwargs["launcher"]
- self.selected_index = -1 # Track the selected item index
- self._arranger_handler = 0
- self.clipboard_items = []
- self._loading = False
- self._pending_updates = False
-
- self.viewport = Box(name="viewport", spacing=4, orientation="v")
- self.search_entry = Entry(
- name="search-entry",
- placeholder="Search Clipboard History...",
- h_expand=True,
- notify_text=self.filter_items,
- on_activate=lambda entry, *_: self.use_selected_item(),
- on_key_press_event=self.on_search_entry_key_press,
- )
- self.search_entry.props.xalign = 0.5
-
- self.scrolled_window = ScrolledWindow(
- name="scrolled-window",
- spacing=10,
- min_content_size=(450, 105),
- max_content_size=(450, 105),
- child=self.viewport,
- )
-
- self.header_box = Box(
- name="header_box",
- spacing=10,
- orientation="h",
- children=[
- Button(
- name="clear-button",
- child=Label(name="clear-label", markup=icons.trash),
- on_clicked=lambda *_: self.clear_history(),
- ),
- self.search_entry,
- ],
- )
-
- self.history_box = Box(
- name="launcher-box",
- spacing=10,
- h_expand=True,
- orientation="v",
- children=[
- self.header_box,
- self.scrolled_window,
- ],
- )
-
- self.add(self.history_box)
- self.show_all()
-
- def close(self):
- """Close the clipboard history panel"""
- self.viewport.children = []
- self.selected_index = -1 # Reset selection
- self.launcher.close()
-
- def open(self):
- """Open the clipboard history panel and load items"""
- self.viewport.set_visible(True)
- if self._loading:
- return
- self._loading = True
- self.search_entry.set_text("") # Clear search
- self.search_entry.grab_focus()
- # Start loading in background using GLib.idle_add
- GLib.idle_add(self._load_clipboard_items_thread)
-
- def _load_clipboard_items_thread(self):
- """Worker for loading clipboard items, now runs in main loop via idle_add"""
- try:
- result = subprocess.run(
- ["cliphist", "list"], capture_output=True, text=True, check=True
- )
- lines = result.stdout.strip().split("\n")
- new_items = []
- for line in lines:
- if not line or " 1 else "0"
- content = parts[1] if len(parts) > 1 else item
-
- # Truncate content for display
- display_text = content.strip()
- if len(display_text) > 100:
- display_text = display_text[:97] + "..."
-
- # Check if this is an image by examining the content
- is_image = self.is_image_data(content)
-
- if is_image:
- # For images, create item with image preview
- button = Button(
- name="slot-button",
- child=Box(
- name="slot-box",
- orientation="h",
- spacing=10,
- children=[
- Image(name="clip-icon", h_align="start"), # Placeholder
- Label(
- name="clip-label",
- label="[Image]",
- ellipsization="end",
- v_align="center",
- h_align="start",
- h_expand=True,
- ),
- ],
- ),
- tooltip_text="Image in clipboard",
- on_clicked=lambda *_, id=item_id: self.paste_item(id),
- )
- # Load image preview in background
- self._load_image_preview_async(item_id, button)
- else:
- # For text, create regular item
- button = self.create_text_item_button(item_id, display_text)
-
- # Add key press event handler for Enter key
- button.connect(
- "key-press-event",
- lambda widget, event, id=item_id: self.on_item_key_press(widget, event, id),
- )
-
- # Make sure button can receive focus and key events
- button.set_can_focus(True)
- button.add_events(Gdk.EventMask.KEY_PRESS_MASK)
-
- return button
-
- def _load_image_preview_async(self, item_id, button):
- """Load image preview using GLib.idle_add instead of threads"""
-
- def load_image():
- try:
- if item_id in self.image_cache:
- pixbuf = self.image_cache[item_id]
- else:
- result = subprocess.run(
- ["cliphist", "decode", item_id], capture_output=True, check=True
- )
- loader = GdkPixbuf.PixbufLoader()
- loader.write(result.stdout)
- loader.close()
- pixbuf = loader.get_pixbuf()
- width, height = pixbuf.get_width(), pixbuf.get_height()
- max_size = 72
- if width > height:
- new_width = max_size
- new_height = int(height * (max_size / width))
- else:
- new_height = max_size
- new_width = int(width * (max_size / height))
- pixbuf = pixbuf.scale_simple(
- new_width, new_height, GdkPixbuf.InterpType.BILINEAR
- )
- self.image_cache[item_id] = pixbuf
- self._update_image_button(button, pixbuf)
- except Exception as e:
- print(f"Error loading image preview: {e}", file=sys.stderr)
- return False
-
- GLib.idle_add(load_image)
-
- def _update_image_button(self, button, pixbuf):
- """Update the button with the loaded image preview"""
- box = button.get_child()
- if box and len(box.get_children()) > 0:
- image_widget = box.get_children()[0]
- if isinstance(image_widget, Image):
- image_widget.set_from_pixbuf(pixbuf)
-
- def create_text_item_button(self, item_id, display_text):
- """Create a button for a text clipboard item"""
- return Button(
- name="slot-button",
- child=Box(
- name="slot-box",
- orientation="h",
- spacing=10,
- children=[
- Label(
- name="clip-icon",
- markup=icons.clip_text,
- h_align="start",
- ),
- Label(
- name="clip-label",
- label=display_text,
- ellipsization="end",
- v_align="center",
- h_align="start",
- h_expand=True,
- ),
- ],
- ),
- tooltip_text=display_text,
- on_clicked=lambda *_: self.paste_item(item_id),
- )
-
- def is_image_data(self, content):
- """Determine if clipboard content is likely an image"""
- # Check for common image data patterns
- return (
- content.startswith("data:image/")
- or content.startswith("\x89PNG")
- or content.startswith("GIF8")
- or content.startswith("\xff\xd8\xff") # JPEG
- or re.match(r"^\s*
visible_bottom:
- # Item below viewport - align to bottom
- new_value = y + height - page_size
- adj.set_value(new_value)
- return False
-
- GLib.idle_add(scroll)
-
- def use_selected_item(self):
- """Use (paste) the selected clipboard item"""
- children = self.viewport.get_children()
- if (
- not children
- or self.selected_index == -1
- or self.selected_index >= len(self.clipboard_items)
- ):
- return
-
- # Get the item ID from the first part before the tab
- item_line = self.clipboard_items[self.selected_index]
- item_id = item_line.split("\t", 1)[0]
- self.paste_item(item_id)
-
- def delete_selected_item(self):
- """Delete the selected clipboard item"""
- children = self.viewport.get_children()
- if not children or self.selected_index == -1:
- return
-
- # Get the item ID from the first part before the tab
- item_line = self.clipboard_items[self.selected_index]
- item_id = item_line.split("\t", 1)[0]
- self.delete_item(item_id)
-
- def on_item_key_press(self, widget, event, item_id):
- """Handle key press events on clipboard items"""
- if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
- # Copy item to clipboard and close
- self.paste_item(item_id)
- return True
- return False
-
- def __del__(self):
- """Clean up temporary files on destruction"""
- try:
- if hasattr(self, "tmp_dir") and os.path.exists(self.tmp_dir):
- import shutil
-
- shutil.rmtree(self.tmp_dir)
- self.image_cache.clear()
- except Exception as e:
- print(f"Error cleaning up temporary files: {e}", file=sys.stderr)
diff --git a/modules/launcher/components/dashboard.py b/modules/launcher/components/dashboard.py
deleted file mode 100644
index 49b8ad8..0000000
--- a/modules/launcher/components/dashboard.py
+++ /dev/null
@@ -1,605 +0,0 @@
-import subprocess
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.utils.helpers import exec_shell_command_async
-import gi
-from gi.repository import Gtk, Gdk, GLib
-from services import network_client
-from services.powerprofile import PowerProfiles
-from fabric import Fabricator
-
-gi.require_version("Gtk", "3.0")
-import utils.icons as icons
-
-
-def add_hover_cursor(widget):
- # Add enter/leave events to change the cursor
- widget.add_events(Gdk.EventMask.ENTER_NOTIFY_MASK | Gdk.EventMask.LEAVE_NOTIFY_MASK)
- widget.connect(
- "enter-notify-event",
- lambda w, e: w.get_window().set_cursor(
- Gdk.Cursor.new_from_name(w.get_display(), "pointer")
- )
- if w.get_window()
- else None,
- )
- widget.connect(
- "leave-notify-event",
- lambda w, e: w.get_window().set_cursor(None) if w.get_window() else None,
- )
-
-
-class NetworkButton(Box):
- def __init__(self):
- self.network_client = network_client
- self._animation_timeout_id = None
- self._animation_step = 0
- self._animation_direction = 1
-
- self.network_icon = Label(
- name="network-icon",
- markup=None,
- )
- self.network_label = Label(
- name="network-label",
- label="Wi-Fi",
- justification="left",
- )
- self.network_label_box = Box(children=[self.network_label, Box(h_expand=True)])
- self.network_ssid = Label(
- name="network-ssid",
- justification="left",
- )
- self.network_ssid_box = Box(children=[self.network_ssid, Box(h_expand=True)])
- self.network_text = Box(
- name="network-text",
- orientation="v",
- h_align="start",
- v_align="center",
- children=[self.network_label_box, self.network_ssid_box],
- )
- self.network_status_box = Box(
- h_align="start",
- v_align="center",
- spacing=10,
- children=[self.network_icon, self.network_text],
- )
- self.network_status_button = Button(
- name="network-status-button",
- h_expand=True,
- child=self.network_status_box,
- on_clicked=lambda *_: self.network_client.wifi_device.toggle_wifi()
- if self.network_client.wifi_device
- else None,
- )
- add_hover_cursor(self.network_status_button) # <-- Added hover
-
- self.network_menu_label = Label(
- name="network-menu-label",
- markup=icons.chevron_right,
- )
- self.network_menu_button = Button(
- name="network-menu-button",
- child=self.network_menu_label,
- )
- add_hover_cursor(self.network_menu_button) # <-- Added hover
-
- super().__init__(
- name="network-button",
- orientation="h",
- h_align="fill",
- v_align="fill",
- h_expand=True,
- v_expand=True,
- spacing=0,
- children=[self.network_status_button, self.network_menu_button],
- )
-
- self.widgets = [
- self,
- self.network_icon,
- self.network_label,
- self.network_ssid,
- self.network_status_button,
- self.network_menu_button,
- self.network_menu_label,
- ]
-
- # Connect to wifi device signals when ready
- self.network_client.connect("device-ready", self._on_wifi_ready)
-
- # Check initial state using idle_add to defer until GTK loop is running
- GLib.idle_add(self._initial_update)
-
- def _initial_update(self):
- self.update_state()
- return False # Run only once
-
- def _on_wifi_ready(self, *args):
- if self.network_client.wifi_device:
- self.network_client.wifi_device.connect(
- "notify::enabled", self.update_state
- )
- self.network_client.wifi_device.connect("notify::ssid", self.update_state)
- self.update_state()
-
- def _animate_searching(self):
- """Animate wifi icon when searching for networks"""
- wifi_icons = [
- icons.wifi_0,
- icons.wifi_1,
- icons.wifi_2,
- icons.wifi_3,
- icons.wifi_2,
- icons.wifi_1,
- ]
-
- # Si el widget no existe o el WiFi estรก desactivado, detener la animaciรณn
- wifi = self.network_client.wifi_device
- if not self.network_icon or not wifi or not wifi.enabled:
- self._stop_animation()
- return False
-
- # Si estamos conectados, detener la animaciรณn
- if wifi.state == "activated" and wifi.ssid != "Disconnected":
- self._stop_animation()
- return False
-
- GLib.idle_add(self.network_icon.set_markup, wifi_icons[self._animation_step])
-
- # Reiniciar al principio cuando llegamos al final
- self._animation_step = (self._animation_step + 1) % len(wifi_icons)
-
- return True # Mantener la animaciรณn activa
-
- def _start_animation(self):
- if self._animation_timeout_id is None:
- self._animation_step = 0
- self._animation_direction = 1
- # Ejecuta la animaciรณn cada 500ms sin usar idle_add
- self._animation_timeout_id = GLib.timeout_add(500, self._animate_searching)
-
- def _stop_animation(self):
- if self._animation_timeout_id is not None:
- GLib.source_remove(self._animation_timeout_id)
- self._animation_timeout_id = None
-
- def update_state(self, *args):
- """Update the button state based on network status"""
- wifi = self.network_client.wifi_device
- ethernet = self.network_client.ethernet_device
-
- # Update enabled/disabled state
- if wifi and not wifi.enabled:
- self._stop_animation()
- self.network_icon.set_markup(icons.wifi_off)
- for widget in self.widgets:
- widget.add_style_class("disabled")
- self.network_ssid.set_label("Disabled")
- return
-
- # Remove disabled class if we got here
- for widget in self.widgets:
- widget.remove_style_class("disabled")
-
- # Update text and animation based on state
- if wifi and wifi.enabled:
- if wifi.state == "activated" and wifi.ssid != "Disconnected":
- self._stop_animation()
- self.network_ssid.set_label(wifi.ssid)
- # Update icon based on signal strength
- if wifi.strength > 0:
- strength = wifi.strength
- if strength < 25:
- self.network_icon.set_markup(icons.wifi_0)
- elif strength < 50:
- self.network_icon.set_markup(icons.wifi_1)
- elif strength < 75:
- self.network_icon.set_markup(icons.wifi_2)
- else:
- self.network_icon.set_markup(icons.wifi_3)
- else:
- self.network_ssid.set_label("Enabled")
- self._start_animation()
-
- # Handle primary device check safely
- try:
- primary_device = self.network_client.primary_device
- except AttributeError:
- primary_device = "wireless" # Default to wireless if error occurs
-
- # Handle wired connection case
- if primary_device == "wired":
- self._stop_animation()
- if ethernet and ethernet.internet == "activated":
- self.network_icon.set_markup(icons.world)
- else:
- self.network_icon.set_markup(icons.world_off)
- else:
- if not wifi:
- self._stop_animation()
- self.network_icon.set_markup(icons.wifi_off)
- elif (
- wifi.state == "activated"
- and wifi.ssid != "Disconnected"
- and wifi.strength > 0
- ):
- self._stop_animation()
- strength = wifi.strength
- if strength < 25:
- self.network_icon.set_markup(icons.wifi_0)
- elif strength < 50:
- self.network_icon.set_markup(icons.wifi_1)
- elif strength < 75:
- self.network_icon.set_markup(icons.wifi_2)
- else:
- self.network_icon.set_markup(icons.wifi_3)
- else:
- self._start_animation()
-
-
-class BluetoothButton(Box):
- def __init__(self, launcher):
- super().__init__(
- name="bluetooth-button",
- orientation="h",
- h_align="fill",
- v_align="fill",
- h_expand=True,
- v_expand=True,
- )
- self.launcher = launcher
-
- self.bluetooth_icon = Label(
- name="bluetooth-icon",
- markup=icons.bluetooth,
- )
- self.bluetooth_label = Label(
- name="bluetooth-label",
- label="Bluetooth",
- justification="left",
- )
- self.bluetooth_label_box = Box(
- children=[self.bluetooth_label, Box(h_expand=True)]
- )
- self.bluetooth_status_text = Label(
- name="bluetooth-status",
- label="Disabled",
- justification="left",
- )
- self.bluetooth_status_box = Box(
- children=[self.bluetooth_status_text, Box(h_expand=True)]
- )
- self.bluetooth_text = Box(
- orientation="v",
- h_align="start",
- v_align="center",
- children=[self.bluetooth_label_box, self.bluetooth_status_box],
- )
- self.bluetooth_status_container = Box(
- h_align="start",
- v_align="center",
- spacing=10,
- children=[self.bluetooth_icon, self.bluetooth_text],
- )
- self.bluetooth_status_button = Button(
- name="bluetooth-status-button",
- h_expand=True,
- child=self.bluetooth_status_container,
- on_clicked=lambda *_: self.launcher.bluetooth.client.toggle_power(),
- )
- add_hover_cursor(self.bluetooth_status_button) # <-- Added hover
- self.bluetooth_menu_label = Label(
- name="bluetooth-menu-label",
- markup=icons.chevron_right,
- )
- self.bluetooth_menu_button = Button(
- name="bluetooth-menu-button",
- on_clicked=lambda *_: self.launcher.open("bluetooth"),
- child=self.bluetooth_menu_label,
- )
- add_hover_cursor(self.bluetooth_menu_button) # <-- Added hover
-
- self.add(self.bluetooth_status_button)
- self.add(self.bluetooth_menu_button)
-
-
-class NightModeButton(Button):
- def __init__(self):
- self.night_mode_icon = Label(
- name="night-mode-icon",
- markup=icons.night,
- )
- self.night_mode_label = Label(
- name="night-mode-label",
- label="Night Mode",
- justification="left",
- )
- self.night_mode_label_box = Box(
- children=[self.night_mode_label, Box(h_expand=True)]
- )
- self.night_mode_status = Label(
- name="night-mode-status",
- label="Enabled",
- justification="left",
- )
- self.night_mode_status_box = Box(
- children=[self.night_mode_status, Box(h_expand=True)]
- )
- self.night_mode_text = Box(
- name="night-mode-text",
- orientation="v",
- h_align="start",
- v_align="center",
- children=[self.night_mode_label_box, self.night_mode_status_box],
- )
- self.night_mode_box = Box(
- h_align="start",
- v_align="center",
- spacing=10,
- children=[self.night_mode_icon, self.night_mode_text],
- )
-
- super().__init__(
- name="night-mode-button",
- h_expand=True,
- child=self.night_mode_box,
- on_clicked=self.toggle_hyprsunset,
- )
- add_hover_cursor(self) # <-- Added hover
-
- self.widgets = [
- self,
- self.night_mode_label,
- self.night_mode_status,
- self.night_mode_icon,
- ]
- # self.check_hyprsunset()
- # **Monitor external changes every 3 seconds**
- self.hyprsunset_watcher = Fabricator(
- self.is_hyprsunset_running,
- interval=3000,
- stream=False,
- default_value=False,
- )
- self.hyprsunset_watcher.changed.connect(self.update_night_mode_status)
- self.update_night_mode_status(None, self.is_hyprsunset_running())
-
- def toggle_hyprsunset(self, *args):
- """
- Toggle the 'hyprsunset' process:
- - If running, kill it and mark as 'Disabled'.
- - If not running, start it and mark as 'Enabled'.
- """
- try:
- subprocess.check_output(["pgrep", "hyprsunset"])
- exec_shell_command_async("pkill hyprsunset")
- self.night_mode_status.set_label("Disabled")
- for widget in self.widgets:
- widget.add_style_class("disabled")
- except subprocess.CalledProcessError:
- exec_shell_command_async("hyprsunset -t 3500")
- self.night_mode_status.set_label("Enabled")
- for widget in self.widgets:
- widget.remove_style_class("disabled")
-
- def is_hyprsunset_running(self, *args):
- """Returns True if hyprsunset is running."""
- try:
- subprocess.check_output(["pgrep", "hyprsunset"])
- return True
- except subprocess.CalledProcessError:
- return False
-
- def update_night_mode_status(self, _, is_running):
- """Update UI based on hyprsunset status."""
- if is_running:
- self.night_mode_status.set_label("Enabled")
- for widget in self.widgets:
- widget.remove_style_class("disabled")
- else:
- self.night_mode_status.set_label("Disabled")
- for widget in self.widgets:
- widget.add_style_class("disabled")
-
-
-class CaffeineButton(Button):
- def __init__(self):
- self.caffeine_icon = Label(
- name="caffeine-icon",
- markup=icons.coffee,
- )
- self.caffeine_label = Label(
- name="caffeine-label",
- label="Caffeine",
- justification="left",
- )
- self.caffeine_label_box = Box(
- children=[self.caffeine_label, Box(h_expand=True)]
- )
- self.caffeine_status = Label(
- name="caffeine-status",
- label="Enabled",
- justification="left",
- )
- self.caffeine_status_box = Box(
- children=[self.caffeine_status, Box(h_expand=True)]
- )
- self.caffeine_text = Box(
- name="caffeine-text",
- orientation="v",
- h_align="start",
- v_align="center",
- children=[self.caffeine_label_box, self.caffeine_status_box],
- )
- self.caffeine_box = Box(
- h_align="start",
- v_align="center",
- spacing=10,
- children=[self.caffeine_icon, self.caffeine_text],
- )
- super().__init__(
- name="caffeine-button",
- h_expand=True,
- child=self.caffeine_box,
- on_clicked=self.toggle_wlinhibit,
- )
- add_hover_cursor(self) # <-- Added hover
-
- self.widgets = [
- self,
- self.caffeine_label,
- self.caffeine_status,
- self.caffeine_icon,
- ]
- # **Monitor external changes every 3 seconds**
- self.wlinhibit_watcher = Fabricator(
- self.is_wlinhibit_running,
- interval=3000,
- stream=False,
- default_value=False,
- )
- self.wlinhibit_watcher.changed.connect(self.update_wlinhibit_status)
- self.update_wlinhibit_status(None, self.is_wlinhibit_running())
-
- def toggle_wlinhibit(self, *args):
- """Toggle the 'wlinhibit' process."""
- if self.is_wlinhibit_running():
- exec_shell_command_async("pkill wlinhibit")
- else:
- exec_shell_command_async("wlinhibit")
-
- def is_wlinhibit_running(self, *args):
- """Returns True if wlinhibit is running. Accepts extra arguments to avoid Fabricator errors."""
- try:
- subprocess.check_output(["pgrep", "wlinhibit"])
- return True
- except subprocess.CalledProcessError:
- return False
-
- def update_wlinhibit_status(self, _, is_running):
- """Update UI based on wlinhibit status."""
- if is_running:
- self.caffeine_status.set_label("Enabled")
- for widget in self.widgets:
- widget.remove_style_class("disabled")
- else:
- self.caffeine_status.set_label("Disabled")
- for widget in self.widgets:
- widget.add_style_class("disabled")
-
-
-class PowerProfileButton(Button):
- def __init__(self):
- self.power_profile_service = PowerProfiles.get_default()
-
- self.power_profile_icon = Label(
- name="power-profile-icon",
- markup=icons.power_balanced, # Default icon
- )
- self.power_profile_label = Label(
- name="power-profile-label",
- label="Power Profile",
- justification="left",
- )
- self.power_profile_label_box = Box(
- children=[self.power_profile_label, Box(h_expand=True)]
- )
- self.power_profile_status = Label(
- name="power-profile-status",
- label="Balanced",
- justification="left",
- )
- self.power_profile_status_box = Box(
- children=[self.power_profile_status, Box(h_expand=True)]
- )
- self.power_profile_text = Box(
- name="power-profile-text",
- orientation="v",
- h_align="start",
- v_align="center",
- children=[self.power_profile_label_box, self.power_profile_status_box],
- )
- self.power_profile_box = Box(
- h_align="start",
- v_align="center",
- spacing=10,
- children=[self.power_profile_icon, self.power_profile_text],
- )
-
- super().__init__(
- name="power-profile-button",
- h_expand=True,
- child=self.power_profile_box,
- on_clicked=self.toggle_power_profile,
- )
- add_hover_cursor(self)
-
- self.widgets = [
- self,
- self.power_profile_label,
- self.power_profile_status,
- self.power_profile_icon,
- ]
-
- # Connect to power profile service signals
- self.power_profile_service.connect("profile", self.update_power_profile_status)
-
- # Initial update
- self.update_power_profile_status(None, self.power_profile_service.get_current_profile())
-
- def toggle_power_profile(self, *args):
- current_profile = self.power_profile_service.get_current_profile()
- profiles = ["power-saver", "balanced", "performance"]
- current_index = profiles.index(current_profile)
- next_profile = profiles[(current_index + 1) % len(profiles)]
- self.power_profile_service.set_power_profile(next_profile)
-
- def update_power_profile_status(self, _, profile):
- # Map profiles to their display names and icons
- profile_map = {
- "power-saver": {"name": "Power Saver", "icon": icons.power_saving},
- "balanced": {"name": "Balanced", "icon": icons.power_balanced},
- "performance": {"name": "Performance", "icon": icons.power_performance},
- }
-
- profile_info = profile_map.get(profile, profile_map["balanced"])
- self.power_profile_status.set_label(profile_info["name"])
- self.power_profile_icon.set_markup(profile_info["icon"])
-
- if profile == "balanced":
- for widget in self.widgets:
- widget.add_style_class("disabled")
- else:
- for widget in self.widgets:
- widget.remove_style_class("disabled")
-
-
-
-class Dashboard(Gtk.Grid):
- def __init__(self, **kwargs):
- super().__init__(name="buttons-grid")
- self.set_row_homogeneous(True)
- self.set_column_homogeneous(True)
- self.set_row_spacing(8)
- self.set_column_spacing(8)
- self.set_vexpand(True) # Prevent vertical expansion
-
- self.launcher = kwargs["launcher"]
-
- # Instantiate each button
- self.network_button = NetworkButton()
- self.bluetooth_button = BluetoothButton(self.launcher)
- self.night_mode_button = NightModeButton()
- self.caffeine_button = CaffeineButton()
- self.power_profile_button = PowerProfileButton()
-
- # Attach buttons into the grid (two rows, three columns)
- self.attach(self.network_button, 0, 0, 1, 1)
- self.attach(self.bluetooth_button, 1, 0, 1, 1)
- self.attach(self.power_profile_button, 2, 0, 1, 1)
- self.attach(self.night_mode_button, 0, 1, 1, 1)
- self.attach(self.caffeine_button, 1, 1, 1, 1)
-
- self.show_all()
diff --git a/modules/launcher/components/emoji.py b/modules/launcher/components/emoji.py
deleted file mode 100644
index b69355e..0000000
--- a/modules/launcher/components/emoji.py
+++ /dev/null
@@ -1,330 +0,0 @@
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.widgets.entry import Entry
-from fabric.widgets.stack import Stack
-from fabric.utils import remove_handler, get_relative_path
-from gi.repository import Gdk
-import utils.icons as icons
-import os
-import subprocess
-import ijson
-
-
-class EmojiPicker(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="emoji",
- visible=False,
- all_visible=False,
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
- self.selected_index = -1
- self.emojis_per_page = 9 * 3
- self.current_page_index = 0
- self.filtered_emojis = []
- self.total_pages = 0
-
- self._arranger_handler: int = 0
- self._all_emojis = self._load_emoji_data()
-
- self.stack = Stack(
- name="viewport",
- spacing=4,
- orientation="v",
- transition_type="slide-up-down",
- transition_duration=200,
- )
- self.search_entry = Entry(
- name="search-entry",
- placeholder="Search Emojis...",
- h_expand=True,
- notify_text=lambda entry, *_: self.arrange_viewport(entry.get_text()),
- on_activate=lambda entry, *_: self.on_search_entry_activate(
- entry.get_text()
- ),
- on_key_press_event=self.on_search_entry_key_press,
- )
- self.search_entry.props.xalign = 0.5
- self.header_box = Box(
- name="header_box",
- spacing=10,
- orientation="h",
- children=[
- self.search_entry,
- # Button(
- # name="close-button",
- # child=Label(name="close-label", markup=icons.cancel),
- # tooltip_text="Exit",
- # on_clicked=lambda *_: self.close_picker(),
- # ),
- ],
- )
-
- self.picker_box = Box(
- name="picker-box",
- spacing=10,
- h_expand=True,
- orientation="v",
- children=[
- self.header_box,
- self.stack,
- ],
- )
-
- self.resize_viewport()
-
- self.add(self.picker_box)
- self.show_all()
-
- def _load_emoji_data(self):
- emoji_data = {}
- emoji_file_path = get_relative_path("../../../config/assets/emoji.json")
- if not os.path.exists(emoji_file_path):
- print(f"Emoji JSON file not found at: {emoji_file_path}")
- return {}
-
- with open(emoji_file_path, "r") as f:
- for emoji_char, emoji_info in ijson.kvitems(f, ""):
- emoji_data[emoji_char] = emoji_info
- return emoji_data
-
- def close_picker(self):
- self.stack.children = []
- self.selected_index = -1
- self.launcher.close()
-
- def open_picker(self):
- self.search_entry.set_text("")
- self.current_page_index = 0
- self.arrange_viewport()
- self.search_entry.grab_focus()
-
- def arrange_viewport(self, query: str = ""):
- remove_handler(self._arranger_handler) if self._arranger_handler else None
- self.stack.children = []
- self.selected_index = -1
- self.current_page_index = 0
-
- self.filtered_emojis = [
- (emoji_char, emoji_info)
- for emoji_char, emoji_info in self._all_emojis.items()
- if query.casefold()
- in (
- emoji_info.get("name", "") + " " + emoji_info.get("group", "")
- ).casefold()
- ]
- self.total_pages = (
- (len(self.filtered_emojis) + self.emojis_per_page - 1)
- // self.emojis_per_page
- if self.filtered_emojis
- else 0
- )
-
- self.load_page(self.current_page_index)
-
- should_resize = not query
-
- if should_resize:
- self.resize_viewport()
- if query.strip() != "" and self.get_all_emoji_buttons():
- self.update_selection(0)
-
- def load_page(self, page_index):
- self.update_selection(-1)
- page_box = Box(name=f"page-box-{page_index}", orientation="v", spacing=4)
- start_index = page_index * self.emojis_per_page
- end_index = min(
- (page_index + 1) * self.emojis_per_page, len(self.filtered_emojis)
- )
- page_emojis = self.filtered_emojis[start_index:end_index]
-
- grid_box = Box(name="emoji-grid-box", orientation="v", spacing=2)
-
- row_box = None
- for i, (emoji_char, emoji_info) in enumerate(page_emojis):
- if i % 9 == 0:
- row_box = Box(name="emoji-row-box", orientation="h", spacing=2)
- grid_box.add(row_box)
- if row_box is not None:
- row_box.add(self.bake_emoji_slot(emoji_char, emoji_info))
- page_box.add(grid_box)
- self.stack.add_named(page_box, f"page-{page_index}")
- self.stack.set_visible_child_name(f"page-{page_index}")
- page_box.show_all()
-
- buttons = self.get_all_emoji_buttons()
- if buttons and self.selected_index != -1:
- page_relative_index = self.selected_index % self.emojis_per_page
- if page_relative_index < len(buttons):
- self.update_selection(page_relative_index)
- else:
- self.update_selection(len(buttons) - 1)
-
- def resize_viewport(self):
- return False
-
- def bake_emoji_slot(self, emoji_char: str, emoji_info: dict, **kwargs) -> Button:
- button = Button(
- name="emoji-slot-button",
- child=Box(
- name="emoji-slot-box",
- orientation="horizontal",
- halign="center",
- valign="center",
- children=[
- Label(
- name="emoji-char-label",
- label=emoji_char,
- use_markup=True,
- v_align="center",
- h_align="center",
- css_name="emoji-char-label",
- ),
- ],
- ),
- tooltip_text=emoji_info.get("name", "Unknown"),
- on_clicked=lambda *_: (
- self.copy_emoji_to_clipboard(emoji_char),
- self.close_picker(),
- ),
- **kwargs,
- )
- return button
-
- def update_selection(self, new_index: int):
- buttons = self.get_all_emoji_buttons()
- if not buttons:
- self.selected_index = -1
- return
-
- if self.selected_index != -1 and self.selected_index < len(buttons):
- current_button = buttons[self.selected_index]
- current_button.get_style_context().remove_class("selected")
- if not buttons or current_button not in buttons:
- self.selected_index = -1
-
- if 0 <= new_index < len(buttons):
- new_button = buttons[new_index]
- new_button.get_style_context().add_class("selected")
- self.selected_index = new_index
- else:
- self.selected_index = -1
-
- def get_all_emoji_buttons(self):
- buttons = []
- current_page = self.stack.get_visible_child()
- if current_page and current_page.get_children():
- if current_page.get_children()[0].get_children():
- for row_box in current_page.get_children()[0].get_children():
- buttons.extend(row_box.get_children())
- return buttons
-
- def on_search_entry_activate(self, text):
- buttons = self.get_all_emoji_buttons()
- if buttons:
- if self.selected_index != -1:
- buttons[self.selected_index].clicked()
- elif buttons and text.strip() != "":
- buttons[0].clicked()
-
- def on_search_entry_key_press(self, widget, event):
- if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right):
- self.move_selection_2d(event.keyval)
- return True
- elif event.keyval == Gdk.KEY_Escape:
- self.close_picker()
- return True
- return False
-
- def move_selection_2d(self, keyval):
- buttons = self.get_all_emoji_buttons()
- total_items_current_page = len(buttons)
- if total_items_current_page == 0:
- return
-
- rows = 3
- columns = 9
-
- if self.selected_index == -1:
- if keyval in (Gdk.KEY_Down, Gdk.KEY_Right):
- new_index = 0
- elif keyval in (Gdk.KEY_Up, Gdk.KEY_Left):
- new_index = total_items_current_page - 1
- else:
- return
- else:
- current_index_page = self.selected_index
- row = current_index_page // columns
- col = current_index_page % columns
-
- if keyval == Gdk.KEY_Right:
- new_col = (col + 1) % columns
- new_row = row
- if new_col == 0:
- new_row = row + 1
- elif keyval == Gdk.KEY_Left:
- new_col = (col - 1) % columns
- new_row = row
- if new_col == (columns - 1):
- new_row = row - 1
- elif keyval == Gdk.KEY_Down:
- new_row = row + 1
- new_col = col
- elif keyval == Gdk.KEY_Up:
- new_row = row - 1
- new_col = col
- else:
- return
-
- if new_row >= rows:
- if self.current_page_index < self.total_pages - 1:
- current_col = col # Keep track of current column
- self.current_page_index += 1
- self.load_page(self.current_page_index)
- new_index = current_col # Try to keep the same column
- if (
- new_index >= total_items_current_page
- ): # if column is out of bound, select last
- new_index = total_items_current_page - 1
- self.selected_index = -1
- self.update_selection(new_index)
- return
- else:
- new_index = total_items_current_page - 1
- elif new_row < 0:
- if self.current_page_index > 0:
- current_col = col # Keep track of current column
- self.current_page_index -= 1
- self.load_page(self.current_page_index)
- new_index = (
- rows - 1
- ) * columns + current_col # Select last row, same column
- if (
- new_index >= total_items_current_page
- ): # if column is out of bound, select last
- new_index = total_items_current_page - 1
- self.selected_index = -1
- self.update_selection(new_index)
- return
- else:
- new_index = 0
- else:
- new_index = new_row * columns + new_col
- if new_index >= total_items_current_page:
- new_index = total_items_current_page - 1
-
- if new_index < 0:
- new_index = 0
- elif new_index >= total_items_current_page:
- new_index = total_items_current_page - 1
-
- self.update_selection(new_index)
-
- def copy_emoji_to_clipboard(self, emoji_char: str):
- try:
- subprocess.run(["wl-copy"], input=emoji_char.encode("utf-8"), check=True)
- except subprocess.CalledProcessError as e:
- print(f"Clipboard copy failed: {e}")
diff --git a/modules/launcher/components/kanban.py b/modules/launcher/components/kanban.py
deleted file mode 100644
index 8c2bf8a..0000000
--- a/modules/launcher/components/kanban.py
+++ /dev/null
@@ -1,365 +0,0 @@
-import gi
-import json
-import os
-from pathlib import Path
-import cairo # For rendering the drag preview
-
-from fabric.widgets.label import Label
-from fabric.widgets.box import Box
-from fabric.widgets.centerbox import CenterBox
-import utils.icons as icons
-
-gi.require_version("Gtk", "3.0")
-from gi.repository import Gtk, Gdk, GObject, GLib
-
-
-def createSurfaceFromWidget(widget: Gtk.Widget) -> cairo.ImageSurface:
- alloc = widget.get_allocation()
- surface = cairo.ImageSurface(cairo.Format.ARGB32, alloc.width, alloc.height)
- cr = cairo.Context(surface)
- # Use a transparent background.
- cr.set_source_rgba(0, 0, 0, 0)
- cr.rectangle(0, 0, alloc.width, alloc.height)
- cr.fill()
- widget.draw(cr)
- return surface
-
-
-class InlineEditor(Gtk.Box):
- __gsignals__ = {
- "confirmed": (GObject.SignalFlags.RUN_LAST, None, (str,)),
- "canceled": (GObject.SignalFlags.RUN_LAST, None, ()),
- }
-
- def __init__(self, initial_text=""):
- super().__init__(name="inline-editor", spacing=4)
- # Replace Gtk.Entry with a Gtk.TextView for multiline editing.
- self.text_view = Gtk.TextView()
- self.text_view.set_wrap_mode(Gtk.WrapMode.WORD)
- buffer = self.text_view.get_buffer()
- buffer.set_text(initial_text)
- # Connect key press events to handle Return and SHIFT+Return.
- self.text_view.connect("key-press-event", self.on_key_press)
-
- confirm_btn = Gtk.Button(
- name="kanban-btn", child=Label(name="kanban-btn-label", markup=icons.accept)
- )
- confirm_btn.connect("clicked", self.on_confirm)
- confirm_btn.get_style_context().add_class("flat")
-
- cancel_btn = Gtk.Button(
- name="kanban-btn", child=Label(name="kanban-btn-neg", markup=icons.cancel)
- )
- cancel_btn.connect("clicked", self.on_cancel)
- cancel_btn.get_style_context().add_class("flat")
-
- # Pack the TextView inside a ScrolledWindow for better appearance.
- sw = Gtk.ScrolledWindow()
- sw.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
- sw.set_min_content_height(50)
- sw.add(self.text_view)
-
- self.button_box = Box(children=[confirm_btn, cancel_btn], spacing=4)
- self.center_box = CenterBox(center_children=[self.button_box], orientation="v")
-
- self.pack_start(sw, True, True, 0)
- self.pack_start(self.center_box, False, False, 0)
- self.show_all()
-
- def on_confirm(self, widget):
- buffer = self.text_view.get_buffer()
- start, end = buffer.get_bounds()
- text = buffer.get_text(start, end, True).strip()
- if text:
- self.emit("confirmed", text)
- else:
- self.emit("canceled")
-
- def on_cancel(self, widget):
- self.emit("canceled")
-
- def on_key_press(self, widget, event):
- # Check for Escape to cancel.
- if event.keyval == Gdk.KEY_Escape:
- self.emit("canceled")
- return True
-
- # If Return is pressed...
- if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
- state = event.get_state()
- if state & Gdk.ModifierType.SHIFT_MASK:
- # SHIFT+Return: insert a newline.
- buffer = self.text_view.get_buffer()
- cursor_iter = buffer.get_iter_at_mark(buffer.get_insert())
- buffer.insert(cursor_iter, "\n")
- return True # Prevent further handling.
- else:
- # Plain Return: confirm the edit.
- self.on_confirm(widget)
- return True
- return False
-
-
-class KanbanNote(Gtk.EventBox):
- __gsignals__ = {
- "changed": (GObject.SignalFlags.RUN_LAST, None, ()),
- }
-
- def __init__(self, text):
- super().__init__()
- self.text = text
- # Variables to store the click offset for drag preview.
- self.setup_ui()
- self.setup_dnd()
- self.connect("button-press-event", self.on_button_press)
-
- def setup_ui(self):
- self.box = Gtk.Box(name="kanban-note", spacing=4)
- self.label = Gtk.Label(label=self.text)
- self.label.set_line_wrap(True)
- # Wrap long lines.
- self.label.set_line_wrap_mode(Gtk.WrapMode.WORD)
-
- self.delete_btn = Gtk.Button(
- name="kanban-btn", child=Label(name="kanban-btn-neg", markup=icons.trash)
- )
- self.delete_btn.connect("clicked", self.on_delete_clicked)
-
- self.center_btn = CenterBox(orientation="v", start_children=[self.delete_btn])
-
- self.box.pack_start(self.label, True, True, 0)
- self.box.pack_start(self.center_btn, False, False, 0)
- self.add(self.box)
- self.show_all()
-
- def setup_dnd(self):
- self.drag_source_set(
- Gdk.ModifierType.BUTTON1_MASK,
- [Gtk.TargetEntry.new("UTF8_STRING", Gtk.TargetFlags.SAME_APP, 0)],
- Gdk.DragAction.MOVE,
- )
- self.connect("drag-data-get", self.on_drag_data_get)
- self.connect("drag-data-delete", self.on_drag_data_delete)
- # Set up drag-begin to display a preview of the card.
- self.connect("drag-begin", self.on_drag_begin)
-
- def on_button_press(self, widget, event):
- if event.type != Gdk.EventType._2BUTTON_PRESS:
- return True
- self.start_edit()
- return False
-
- def on_drag_begin(self, widget, context):
- surface = createSurfaceFromWidget(self)
- Gtk.drag_set_icon_surface(context, surface)
-
- def on_drag_data_get(self, widget, drag_context, data, info, time):
- data.set_text(self.label.get_text(), -1)
-
- def on_drag_data_delete(self, widget, drag_context):
- self.get_parent().destroy()
-
- def on_delete_clicked(self, button):
- self.get_parent().destroy()
-
- def start_edit(self):
- row = self.get_parent()
- editor = InlineEditor(self.label.get_text())
-
- def on_confirmed(editor, text):
- self.label.set_text(text)
- row.remove(editor)
- row.add(self)
- row.show_all()
- self.emit("changed")
-
- def on_canceled(editor):
- row.remove(editor)
- row.add(self)
- row.show_all()
-
- editor.connect("confirmed", on_confirmed)
- editor.connect("canceled", on_canceled)
-
- row.remove(self)
- row.add(editor)
- row.show_all()
- # Usamos un retardo de 50 milisegundos:
- GLib.timeout_add(50, lambda: (editor.text_view.grab_focus(), False))
-
-
-class KanbanColumn(Gtk.Frame):
- __gsignals__ = {
- "changed": (GObject.SignalFlags.RUN_LAST, None, ()),
- }
-
- def __init__(self, title):
- super().__init__(name="kanban-column")
- self.title = title
- self.setup_ui()
- self.setup_dnd()
- self.set_hexpand(True)
- self.set_vexpand(True)
-
- def setup_ui(self):
- self.box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL, spacing=4)
- self.listbox = Gtk.ListBox()
- self.listbox.set_selection_mode(Gtk.SelectionMode.NONE)
-
- self.add_btn = Gtk.Button(
- name="kanban-btn-add",
- child=Label(name="kanban-btn-label", markup=icons.add),
- )
- header = CenterBox(
- name="kanban-header",
- center_children=[Label(name="column-header", label=self.title)],
- end_children=[self.add_btn],
- )
- self.box.pack_start(header, False, False, 0)
-
- self.add_btn.connect("clicked", self.on_add_clicked)
-
- scrolled = Gtk.ScrolledWindow(name="kanban-scroll")
- scrolled.set_policy(Gtk.PolicyType.NEVER, Gtk.PolicyType.AUTOMATIC)
- scrolled.add(self.listbox)
- scrolled.set_vexpand(True)
-
- self.box.pack_start(scrolled, True, True, 0)
- self.box.pack_start(self.add_btn, False, False, 0)
- self.add(self.box)
- self.show_all()
-
- def setup_dnd(self):
- self.listbox.drag_dest_set(
- Gtk.DestDefaults.ALL,
- [Gtk.TargetEntry.new("UTF8_STRING", Gtk.TargetFlags.SAME_APP, 0)],
- Gdk.DragAction.MOVE,
- )
-
- self.listbox.connect("drag-data-received", self.on_drag_data_received)
- self.listbox.connect("drag-motion", self.on_drag_motion)
- self.listbox.connect("drag-leave", self.on_drag_leave)
-
- def on_add_clicked(self, button):
- editor = InlineEditor()
- row = Gtk.ListBoxRow(name="kanban-row")
- row.add(editor)
- self.listbox.add(row)
- self.listbox.show_all()
- editor.text_view.grab_focus()
-
- def on_confirmed(editor, text):
- note = KanbanNote(text)
- note.connect("changed", lambda x: self.emit("changed"))
- row.remove(editor)
- row.add(note)
- self.listbox.show_all()
- self.emit("changed") # Emit on add
-
- def on_canceled(editor):
- row.destroy()
-
- editor.connect("confirmed", on_confirmed)
- editor.connect("canceled", on_canceled)
-
- def add_note(self, text, suppress_signal=False):
- note = KanbanNote(text)
- note.connect("changed", lambda x: self.emit("changed"))
- row = Gtk.ListBoxRow(name="kanban-row")
- row.add(note)
- row.connect("destroy", lambda x: self.emit("changed"))
- self.listbox.add(row)
- self.listbox.show_all()
- if not suppress_signal:
- self.emit("changed")
-
- def get_notes(self):
- return [
- row.get_children()[0].label.get_text()
- for row in self.listbox.get_children()
- if isinstance(row.get_children()[0], KanbanNote)
- ]
-
- def clear_notes(self, suppress_signal=False):
- for row in self.listbox.get_children():
- row.destroy()
- if not suppress_signal:
- self.emit("changed")
-
- def on_drag_data_received(self, widget, drag_context, x, y, data, info, time):
- text = data.get_text()
- if text:
- row = self.listbox.get_row_at_y(y)
- new_note = KanbanNote(text)
- new_note.connect("changed", lambda x: self.emit("changed"))
- new_row = Gtk.ListBoxRow(name="kanban-row")
- new_row.add(new_note)
- new_row.connect("destroy", lambda x: self.emit("changed"))
-
- if row:
- self.listbox.insert(new_row, row.get_index())
- else:
- self.listbox.add(new_row)
-
- self.listbox.show_all()
- drag_context.finish(True, False, time)
- self.emit("changed") # Emit on move
-
- def on_drag_motion(self, widget, drag_context, x, y, time):
- Gdk.drag_status(drag_context, Gdk.DragAction.MOVE, time)
- return True
-
- def on_drag_leave(self, widget, drag_context, time):
- widget.get_parent().get_parent().drag_unhighlight()
-
-
-class Kanban(Gtk.Box):
- STATE_FILE = Path(os.path.expanduser("~/.local/state/modus/kanban.json"))
-
- def __init__(self):
- super().__init__(name="kanban")
-
- self.grid = Gtk.Grid(column_spacing=4, column_homogeneous=True)
- self.grid.set_vexpand(True)
- self.add(self.grid)
-
- self.columns = [
- KanbanColumn("To Do"),
- KanbanColumn("In Progress"),
- KanbanColumn("Done"),
- ]
-
- for i, column in enumerate(self.columns):
- self.grid.attach(column, i, 0, 1, 1)
- column.connect("changed", lambda x: self.save_state())
-
- self.load_state()
- self.show_all()
-
- def save_state(self):
- state = {
- "columns": [
- {"title": col.title, "notes": col.get_notes()} for col in self.columns
- ]
- }
- try:
- with open(self.STATE_FILE, "w") as f:
- json.dump(state, f, indent=2)
- except Exception as e:
- print(f"Error saving state: {e}")
-
- def load_state(self):
- try:
- with open(self.STATE_FILE, "r") as f:
- state = json.load(f)
- for col_data in state["columns"]:
- for column in self.columns:
- if column.title == col_data["title"]:
- column.clear_notes(suppress_signal=True)
- for note_text in col_data["notes"]:
- column.add_note(note_text, suppress_signal=True)
- break
- except FileNotFoundError:
- pass
- except Exception as e:
- print(f"Error loading state: {e}")
diff --git a/modules/launcher/components/notification_center.py b/modules/launcher/components/notification_center.py
deleted file mode 100644
index 86ade48..0000000
--- a/modules/launcher/components/notification_center.py
+++ /dev/null
@@ -1,409 +0,0 @@
-from fabric.notifications import Notification
-from services import notification_service
-from fabric.widgets.box import Box
-from fabric.widgets.button import Button
-from fabric.widgets.label import Label
-from fabric.widgets.image import Image
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from fabric.widgets.revealer import Revealer
-from loguru import logger
-import utils.icons as icons
-from gi.repository import GdkPixbuf, GLib
-from utils import CustomImage
-import os
-from gi.repository import Gtk
-from fabric.widgets.centerbox import CenterBox
-
-
-class NotificationCenter(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="notification-center",
- visible=False,
- all_visible=False,
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
- self.notification_service = notification_service
-
- # Main container for notifications
- self.notifications = Box(
- v_expand=True,
- h_expand=True,
- orientation="v",
- spacing=8,
- )
- self.header_switch = Gtk.Switch(name="matugen-switcher")
- self.header_switch.set_vexpand(False)
- self.header_switch.set_valign(Gtk.Align.CENTER)
- self.header_switch.set_active(self.notification_service.dont_disturb)
- self.do_not_disturb_enabled = self.notification_service.dont_disturb
- self.header_switch.connect("notify::active", self.on_do_not_disturb_changed)
- self.dnd_label = Label(name="dnd-label", markup=icons.notifications_off)
- # Header with clear all button
- header = CenterBox(
- h_expand=True,
- start_children=[self.header_switch, self.dnd_label],
- center_children=[
- Label(
- label="Notifications",
- name="nhh",
- )
- ],
- end_children=[
- Button(
- name="nhh-button",
- child=Label(name="nhh-button-label", markup=icons.trash),
- on_clicked=lambda *args: self.clear_all_notifications(),
- )
- ],
- )
-
- self.scrolledwindow = ScrolledWindow(
- name="scrolled-window",
- child=self.notifications,
- min_content_size=(400, 300),
- max_content_size=(400, 300),
- v_expand=True,
- h_expand=True,
- )
-
- # Main container
- main_container = Box(
- v_expand=True,
- h_expand=True,
- orientation="v",
- children=[header, self.scrolledwindow],
- )
-
- self.add(main_container)
-
- # Connect to notification service signals
- self.notification_service.connect(
- "notification-added", self.on_new_notification
- )
- self.notification_service.connect("clear_all", self.on_clear_all)
-
- # Load existing notifications
- self.load_notifications()
-
- def load_notifications(self):
- """Load existing notifications from the service."""
- notifications = self.notification_service.get_deserialized()
- if not notifications:
- # Create a container box to better center the message
- container = Box(
- name="no-notifications-container",
- orientation="v",
- h_align="center",
- v_align="center",
- h_expand=True,
- v_expand=True,
- )
-
- # Show a message if no notifications
- label = Label(
- name="no-notifications-label",
- markup=icons.notifications_clear,
- v_align="fill",
- h_align="fill",
- v_expand=True,
- h_expand=True,
- justification="center",
- )
-
- container.add(label)
- self.notifications.add(container)
- return
-
- for notification in notifications:
- self.add_notification(notification)
-
- def toggle_notification_body(self, revealer, chevron_label):
- """Toggle the visibility of notification body."""
- is_revealed = revealer.get_reveal_child()
- revealer.set_reveal_child(not is_revealed)
- # Update chevron icon
- chevron_label.set_markup(
- icons.chevron_up if not is_revealed else icons.chevron_down
- )
-
- def add_notification(self, notification: Notification):
- """Add a notification to the center."""
- # Remove empty state container if it exists
- for child in self.notifications.get_children():
- if child.get_name() == "no-notifications-container":
- self.notifications.remove(child)
- break
-
- # Get timestamp from notification object
- timestamp = getattr(notification, 'timestamp', GLib.get_real_time() / 1000000)
- time_str = GLib.DateTime.new_from_unix_local(timestamp).format("%I:%M %p")
-
- # Create the notification header (summary) box
- header_box = Box(
- name="notification-header",
- # h_expand=True,
- spacing=8,
- children=[
- Box(
- orientation="h",
- h_expand=True,
- spacing=8,
- style="min-width: 0;",
- children=[
- # Summary
- Label(
- name="notification-app-name",
- markup=notification.app_name + " | ",
- h_align="start",
- ellipsization="end",
- ),
- Label(
- markup=notification.summary.replace("\n", " "),
- h_align="start",
- ellipsization="end",
- ),
- Label(
- name="notification-time",
- markup=f" | {time_str}",
- h_align="end",
- style="color: var(--outline);",
- ),
- ],
- ),
- # Add chevron button if there's content to show
- *(
- [
- Button(
- name="chevron-button",
- child=Label(
- name="chevron-label", markup=icons.chevron_down
- ),
- )
- ]
- if notification.body or notification.image_pixbuf
- else []
- ),
- Button(
- name="notif-close-button",
- child=Label(name="notif-close-label", markup=icons.cancel),
- on_clicked=lambda *args: self.remove_notification(notification),
- ),
- ],
- )
-
- # Create content box that will be revealed
- # Create content box in vertical layout for body + actions
- content_box = Box(
- name="notification-content",
- spacing=8,
- orientation="v",
- )
-
- # Create horizontal box for image and body
- body_box = Box(
- name="notification-body-box",
- spacing=8,
- orientation="h",
- )
-
- # Add image if available
- if notification.image_pixbuf:
- image_box = Box(
- name="notification-image",
- children=[
- CustomImage(
- pixbuf=notification.image_pixbuf.scale_simple(
- 48, 48, GdkPixbuf.InterpType.BILINEAR
- )
- if notification.image_pixbuf
- else self.get_pixbuf(notification.app_icon, 48, 48)
- ),
- ],
- )
- body_box.add(image_box)
-
- # Add body text if available
- if notification.body:
- body_box.add(
- Label(
- markup=notification.body.replace("\n", " "),
- name="notification-body",
- h_align="start",
- ellipsization="end",
- )
- )
-
- # Add body box to content
- content_box.add(body_box)
-
- # Add action buttons if available
- if notification.actions:
- action_box = Box(
- name="notification-action-buttons",
- spacing=4,
- h_expand=True,
- )
- for i, action in enumerate(notification.actions):
- style_class = (
- "start-action"
- if i == 0
- else "end-action"
- if i == len(notification.actions) - 1
- else "middle-action"
- )
- button = Button(
- name="action-button",
- h_expand=True,
- child=Label(name="button-label", label=action.label),
- on_clicked=lambda btn, act=action, notif=notification: [
- act.invoke(),
- self.notification_service.remove_notification(notif.id),
- self.remove_notification(notif)
- ],
- )
- button.add_style_class(style_class)
- action_box.add(button)
- content_box.add(action_box)
-
- # Create revealer for content
- body_revealer = None
- if notification.body or notification.image_pixbuf:
- body_revealer = Revealer(
- name="body-revealer",
- transition_type="slide-down",
- reveal_child=False,
- child=content_box,
- )
-
- # Create main notification box
- notification_box = Box(
- name="notification-box",
- orientation="v",
- h_expand=True,
- style="margin: 4px; padding: 16px; border-radius: 20px; background-color: var(--surface-container-low); min-width: 200px;",
- children=[header_box],
- )
-
- # Add body revealer if exists
- if body_revealer:
- notification_box.add(body_revealer)
- # Get the chevron button from header_box children
- chevron_button = [
- c for c in header_box.get_children() if c.get_name() == "chevron-button"
- ][0]
- chevron_label = chevron_button.get_child()
- chevron_button.connect(
- "clicked",
- lambda *args: self.toggle_notification_body(
- body_revealer, chevron_label
- ),
- )
-
- self.notifications.add(notification_box)
-
- def on_do_not_disturb_changed(self, switch, pspec):
- """Handle Do Not Disturb toggle."""
- self.do_not_disturb_enabled = switch.get_active()
- self.notification_service.dont_disturb = self.do_not_disturb_enabled
-
- def on_new_notification(self, fabric_notif, id):
- """Handle new notification event."""
- notification = fabric_notif.get_notification_from_id(id)
- self.add_notification(notification)
-
- def remove_notification(self, notification: Notification):
- """Remove a notification from the center."""
- self.notification_service.remove_notification(notification.id)
- # Find and remove the notification box
- for child in self.notifications.get_children():
- if child.get_name() == "notification-box":
- # Get the header box
- header_box = child.get_children()[0]
- # Get the text container box
- text_box = header_box.get_children()[0]
- # Get the summary label (second child of text box)
- summary_label = text_box.get_children()[1]
- if summary_label.get_text() == notification.summary:
- self.notifications.remove(child)
- break
-
- def clear_all_notifications(self, *args):
- """Clear all notifications."""
- self.notification_service.clear_all_notifications()
- # Remove all child widgets
- for child in self.notifications.get_children():
- self.notifications.remove(child)
-
- # Add empty state
- container = Box(
- name="no-notifications-container",
- orientation="v",
- h_align="center",
- v_align="center",
- h_expand=True,
- v_expand=True,
- )
-
- label = Label(
- name="no-notifications-label",
- markup=icons.notifications_clear,
- v_align="fill",
- h_align="fill",
- v_expand=True,
- h_expand=True,
- justification="center",
- )
-
- container.add(label)
- self.notifications.add(container)
-
- def on_clear_all(self, *args):
- """Handle clear all event from service."""
- # Remove all child widgets
- for child in self.notifications.get_children():
- self.notifications.remove(child)
-
- # Add empty state
- container = Box(
- name="no-notifications-container",
- orientation="v",
- h_align="center",
- v_align="center",
- h_expand=True,
- v_expand=True,
- )
-
- label = Label(
- name="no-notifications-label",
- markup=icons.notifications_clear,
- v_align="fill",
- h_align="fill",
- v_expand=True,
- h_expand=True,
- justification="center",
- )
-
- container.add(label)
- self.notifications.add(container)
-
- def get_pixbuf(self, icon_path, width, height):
- if icon_path.startswith("file://"):
- icon_path = icon_path[7:]
-
- if not os.path.exists(icon_path):
- logger.warning(f"Icon path does not exist: {icon_path}")
- return None
-
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon_path)
- return pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR)
- except Exception as e:
- logger.error(f"Failed to load or scale icon: {e}")
- return None
-
- def open_center(self):
- """Open the notification center."""
- self.show_all()
diff --git a/modules/launcher/components/power.py b/modules/launcher/components/power.py
deleted file mode 100644
index b441e25..0000000
--- a/modules/launcher/components/power.py
+++ /dev/null
@@ -1,88 +0,0 @@
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.utils.helpers import exec_shell_command_async
-import utils.icons as icons
-
-
-class PowerMenu(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="power-menu",
- orientation="h",
- spacing=10,
- v_align="center",
- h_align="center",
- v_expand=True,
- h_expand=True,
- visible=True,
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
-
- self.btn_lock = Button(
- name="power-menu-button",
- child=Label(name="button-label", markup=icons.lock),
- on_clicked=self.lock,
- )
-
- self.btn_suspend = Button(
- name="power-menu-button",
- child=Label(name="button-label", markup=icons.suspend),
- on_clicked=self.suspend,
- )
-
- self.btn_logout = Button(
- name="power-menu-button",
- child=Label(name="button-label", markup=icons.logout),
- on_clicked=self.logout,
- )
-
- self.btn_reboot = Button(
- name="power-menu-button",
- child=Label(name="button-label", markup=icons.reboot),
- on_clicked=self.reboot,
- )
-
- self.btn_shutdown = Button(
- name="power-menu-button",
- child=Label(name="button-label", markup=icons.shutdown),
- on_clicked=self.poweroff,
- )
-
- self.buttons = [
- self.btn_lock,
- self.btn_suspend,
- self.btn_logout,
- self.btn_reboot,
- self.btn_shutdown,
- ]
-
- for button in self.buttons:
- self.add(button)
-
- self.show_all()
-
- def close_menu(self):
- self.launcher.close()
-
- def lock(self, *args):
- exec_shell_command_async("hyprlock --immediate")
- self.close_menu()
-
- def suspend(self, *args):
- exec_shell_command_async("systemctl suspend")
- self.close_menu()
-
- def logout(self, *args):
- exec_shell_command_async("hyprctl dispatch exit")
- self.close_menu()
-
- def reboot(self, *args):
- exec_shell_command_async("systemctl reboot")
- self.close_menu()
-
- def poweroff(self, *args):
- exec_shell_command_async("systemctl poweroff")
- self.close_menu()
diff --git a/modules/launcher/components/sh.py b/modules/launcher/components/sh.py
deleted file mode 100644
index 6fef79a..0000000
--- a/modules/launcher/components/sh.py
+++ /dev/null
@@ -1,139 +0,0 @@
-import os
-import subprocess
-from typing import List
-from fabric.widgets.box import Box
-from fabric.widgets.button import Button
-from fabric.widgets.entry import Entry
-from fabric.widgets.label import Label
-from fabric.widgets.scrolledwindow import ScrolledWindow
-import config.data as data
-
-BINS = f"{data.CACHE_DIR}/binaries"
-
-
-class Sh(Box):
- def __init__(self, **kwargs):
- super().__init__(
- all_visible=False,
- visible=False,
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
- self._arranger_handler: int = 0
- self.shell_command_manager = ShellCommandManager(self)
- self.viewport = None
-
- self.search_entry = Entry(
- name="search-entry",
- placeholder="Search Shell Commands...",
- h_expand=True,
- notify_text=lambda entry, *_: self.handle_search_input(entry.get_text()),
- on_activate=lambda entry, *_: self.handle_search_input(entry.get_text()),
- )
- self.search_entry.props.xalign = 0.5
-
- self.header_box = Box(
- spacing=10,
- orientation="h",
- children=[self.search_entry],
- )
-
- self.launcher_box = Box(
- spacing=10,
- orientation="v",
- h_expand=True,
- children=[self.header_box],
- )
-
- self.add(self.launcher_box)
-
- def open_sh(self):
- if not self.viewport:
- self.viewport = Box(name="viewport", spacing=10, orientation="v")
- self.scrolled_window = ScrolledWindow(
- name="scrolled-window",
- spacing=10,
- h_scrollbar_policy="never",
- v_scrollbar_policy="never",
- # min_content_size=(-1, -1),
- child=self.viewport,
- )
- self.launcher_box.add(self.scrolled_window)
-
- self.viewport.children = []
- self.shell_command_manager.show_shell_commands(self.viewport, "")
-
- self.viewport.show()
- self.search_entry.grab_focus()
-
- def handle_search_input(self, text: str):
- self.shell_command_manager.show_shell_commands(self.viewport, text)
-
-
-class ShellCommandManager:
- def __init__(self, launcher):
- self.launcher = launcher
- self.binaries = set()
- self.reload_binaries()
-
- def reload_binaries(self):
- paths = os.getenv("PATH", "").split(":")
- self.binaries = set()
-
- for path in paths:
- self.binaries.update(self.list_binaries_in_path(path))
-
- with open(BINS, "w") as f:
- f.write("\n".join(self.binaries))
-
- def list_binaries_in_path(self, path):
- if not os.path.exists(path):
- return []
- try:
- return [
- entry
- for entry in os.listdir(path)
- if os.access(os.path.join(path, entry), os.X_OK)
- ]
- except OSError:
- return []
-
- def show_shell_commands(self, viewport, search_query: str):
- if not search_query:
- return
-
- viewport.children = []
-
- results = self.query_binaries(search_query)
- self.display_results(viewport, results)
-
- def query_binaries(self, filter_str: str) -> List[str]:
- filter_str = filter_str.lower()
- filtered_bins = (cmd for cmd in self.binaries if filter_str in cmd)
- return list(dict.fromkeys(filtered_bins))[:8]
-
- def display_results(self, viewport, results: List[str]):
- for command in results:
- button = Button(
- child=Label(
- label=command,
- v_align="center",
- h_align="start",
- ),
- on_clicked=lambda _, cmd=command: self.execute_command(cmd),
- name="app-slot-button",
- )
- viewport.add(button)
-
- def close_menu(self):
- self.launcher.close()
-
- def execute_command(self, cmd: str):
- try:
- output = subprocess.check_output(cmd, shell=True, text=True)
- print(output)
- except subprocess.CalledProcessError as e:
- print(e.output)
- finally:
- self.close_menu()
diff --git a/modules/launcher/components/tmux.py b/modules/launcher/components/tmux.py
deleted file mode 100644
index 583e477..0000000
--- a/modules/launcher/components/tmux.py
+++ /dev/null
@@ -1,540 +0,0 @@
-import subprocess
-import os
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.widgets.entry import Entry
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from fabric.utils import idle_add, remove_handler, exec_shell_command_async
-from gi.repository import GLib, Gdk, Gtk
-import utils.icons as icons
-import config.data as data
-
-class TmuxManager(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="tmux-manager",
- visible=False,
- all_visible=False,
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
- self.selected_index = -1 # Track the selected item index
-
- self._arranger_handler: int = 0
-
- self.viewport = Box(name="viewport", spacing=4, orientation="v")
- self.session_name_entry = Entry(
- name="session-name-entry",
- placeholder="Create Tmux Session...",
- h_expand=True,
- on_activate=lambda entry, *_: self.create_session(entry.get_text()),
- on_key_press_event=self.on_entry_key_press,
- )
- self.session_name_entry.props.xalign = 0.5
- self.scrolled_window = ScrolledWindow(
- name="scrolled-window",
- spacing=10,
- min_content_size=(450, 105),
- max_content_size=(450, 105),
- child=self.viewport,
- )
-
- self.header_box = Box(
- name="header_box",
- spacing=10,
- orientation="h",
- children=[
- Button(
- name="new-session-button",
- child=Label(name="new-session-label", markup=icons.add),
- tooltip_text="Create New Session",
- on_clicked=lambda *_: self.create_session(self.session_name_entry.get_text()),
- ),
- self.session_name_entry,
- ],
- )
-
- self.tmux_box = Box(
- name="tmux-box",
- spacing=10,
- h_expand=True,
- orientation="v",
- children=[
- self.header_box,
- self.scrolled_window,
- ],
- )
-
- self.add(self.tmux_box)
- self.show_all()
-
- def close_manager(self):
- """Close the tmux manager"""
- self.viewport.children = []
- self.selected_index = -1 # Reset selection
- self.launcher.close()
-
- def open_manager(self):
- """Open the tmux manager and refresh sessions"""
- self.viewport.set_visible(True)
- self.refresh_sessions()
- self.session_name_entry.set_text("")
- self.session_name_entry.grab_focus()
-
- def refresh_sessions(self):
- """Get tmux sessions and populate the viewport"""
- remove_handler(self._arranger_handler) if self._arranger_handler else None
- self.viewport.children = []
- self.selected_index = -1 # Clear selection when viewport changes
-
- # Get tmux sessions
- sessions = self.get_tmux_sessions()
- if not sessions:
- # Create a container box to better center the message
- container = Box(
- name="no-tmux-container",
- orientation="v",
- h_align="center",
- v_align="center",
- h_expand=True,
- v_expand=True
- )
-
- # Show a message if no sessions
- label = Label(
- name="no-tmux",
- markup=icons.terminal,
- h_align="center",
- v_align="center",
- )
-
- container.add(label)
-
- self.viewport.add(container)
- return
-
- # Add session slots to viewport
- for session in sessions:
- self.viewport.add(self.create_session_slot(session))
-
- def get_tmux_sessions(self):
- """Get list of tmux sessions"""
- try:
- result = subprocess.run(
- ["tmux", "list-sessions", "-F", "#{session_name}"],
- capture_output=True,
- text=True
- )
- if result.returncode == 0:
- return [s.strip() for s in result.stdout.strip().split('\n') if s.strip()]
- return []
- except Exception as e:
- print(f"Error getting tmux sessions: {e}")
- return []
-
- def create_session_slot(self, session_name):
- """Create a button for a tmux session"""
- # Create an entry for inline editing (initially hidden)
- name_entry = Entry(
- name="session-name-entry",
- text=session_name,
- visible=False,
- on_activate=lambda entry, *_: self.finish_rename(button, session_name, entry),
- on_key_press_event=self.on_rename_key_press,
- )
-
- # Create the label showing the session name
- name_label = Label(
- name="app-label",
- label=session_name,
- ellipsization="end",
- v_align="center",
- h_align="center",
- )
-
- # Session slot content box
- slot_box = Box(
- name="slot-box",
- orientation="h",
- spacing=10,
- children=[
- Label(
- name="tmux-icon",
- markup=icons.terminal, # Use existing terminal icon
- ),
- name_label,
- name_entry,
- ],
- )
-
- button = Button(
- name="slot-button", # reuse existing CSS styling
- child=slot_box,
- tooltip_text=f"Attach to session: {session_name}",
- on_clicked=lambda *_: self.attach_to_session(session_name),
- can_focus=True, # Ensure the button can receive focus
- )
-
- # Add double-click handler to start renaming
- button.connect("button-press-event", self.on_session_click, session_name, name_label, name_entry)
-
- # Add key press handler for 'r' to rename
- button.connect("key-press-event", self.on_slot_key_press, session_name, name_label, name_entry)
-
- # Store reference to entry and label in button for later access
- button.name_entry = name_entry
- button.name_label = name_label
- button.session_name = session_name
-
- return button
-
- def on_session_click(self, button, event, session_name, label, entry):
- """Handle clicks on session buttons"""
- # Handle double-click to rename
- if event.type == Gdk.EventType.DOUBLE_BUTTON_PRESS and event.button == 1:
- self.start_rename(button, session_name, label, entry)
- return True
- # Handle right click for context menu
- elif event.button == 3:
- menu = Gtk.Menu()
-
- # Rename option
- rename_item = Gtk.MenuItem(label="Rename")
- rename_item.connect("activate", lambda _: self.start_rename(button, session_name, label, entry))
- menu.append(rename_item)
-
- # Kill option
- kill_item = Gtk.MenuItem(label="Kill Session")
- kill_item.connect("activate", lambda _: self.kill_session(session_name))
- menu.append(kill_item)
-
- menu.show_all()
- menu.popup_at_pointer(event)
- return True
-
- return False
-
- def start_rename(self, button, session_name, label, entry):
- """Start inline renaming of a session"""
- # Hide label, show entry
- label.set_visible(False)
- entry.set_visible(True)
-
- # Focus entry and select all text
- entry.grab_focus()
- entry.select_region(0, -1)
-
- # Mark button as being edited
- button.get_style_context().add_class("editing")
-
- def finish_rename(self, button, old_name, entry):
- """Finish renaming a session"""
- new_name = entry.get_text().strip()
-
- # Only rename if the name changed and isn't empty
- if new_name and new_name != old_name:
- self.rename_session(old_name, new_name)
-
- # Reset UI state
- self.cancel_rename(button)
-
- def cancel_rename(self, button):
- """Cancel renaming operation"""
- # Restore original view
- button.name_entry.set_visible(False)
- button.name_label.set_visible(True)
-
- # Remove editing style
- button.get_style_context().remove_class("editing")
-
- # Return focus to session name entry
- self.session_name_entry.grab_focus()
-
- def on_rename_key_press(self, entry, event):
- """Handle key presses in the rename entry"""
- if event.keyval == Gdk.KEY_Escape:
- # Find the parent button
- parent = entry.get_parent()
- while parent and not isinstance(parent, Button):
- parent = parent.get_parent()
-
- if parent:
- self.cancel_rename(parent)
- return True
-
- return False
-
- def on_session_right_click(self, button, event, session_name):
- """Handle right-click on a session button to show context menu"""
- if event.button == 3: # Right click
- menu = Gtk.Menu()
-
- # Rename option
- rename_item = Gtk.MenuItem(label="Rename")
- rename_item.connect("activate", lambda _: self.start_rename(
- button,
- session_name,
- button.name_label,
- button.name_entry
- ))
- menu.append(rename_item)
-
- # Kill option
- kill_item = Gtk.MenuItem(label="Kill Session")
- kill_item.connect("activate", lambda _: self.kill_session(session_name))
- menu.append(kill_item)
-
- menu.show_all()
- menu.popup_at_pointer(event)
- return True
-
- return False
-
- def on_entry_key_press(self, widget, event):
- """Handle key press events in the entry"""
- if event.keyval == Gdk.KEY_Escape:
- self.close_manager()
- return True
-
- # Custom navigation with UP/DOWN keys removed
- return False
-
- def scroll_to_selected(self, button):
- """Scroll to ensure the selected button is visible"""
- def scroll():
- adj = self.scrolled_window.get_vadjustment()
- alloc = button.get_allocation()
- if alloc.height == 0:
- return False # Retry if allocation isn't ready
-
- y = alloc.y
- height = alloc.height
- page_size = adj.get_page_size()
- current_value = adj.get_value()
-
- # Calculate visible boundaries
- visible_top = current_value
- visible_bottom = current_value + page_size
-
- if y < visible_top:
- # Item above viewport - align to top
- adj.set_value(y)
- elif y + height > visible_bottom:
- # Item below viewport - align to bottom
- new_value = y + height - page_size
- adj.set_value(new_value)
- # No action if already fully visible
- return False
- GLib.idle_add(scroll)
-
- def create_session(self, session_name):
- """Create a new tmux session"""
- if not session_name:
- # Get existing session names
- existing_sessions = self.get_tmux_sessions()
-
- # Find the next available number
- counter = 0
- while str(counter) in existing_sessions:
- counter += 1
-
- session_name = str(counter)
-
- try:
- # Clean the session name (replace spaces with underscores)
- clean_name = session_name.strip().replace(" ", "_")
-
- # Create session
- subprocess.run(
- ["tmux", "new-session", "-d", "-s", clean_name],
- check=True
- )
-
- # Refresh the session list
- self.refresh_sessions()
-
- # Clear entry
- self.session_name_entry.set_text("")
-
- # Launch a terminal and attach to this session
- terminal_cmd = self.get_terminal_command(f"tmux attach-session -t {clean_name}")
- exec_shell_command_async(terminal_cmd)
-
- # Close manager
- self.close_manager()
-
- except Exception as e:
- print(f"Error creating tmux session: {e}")
-
- def attach_to_session(self, session_name):
- """Attach to an existing tmux session"""
- try:
- # Launch a terminal and attach to this session
- terminal_cmd = self.get_terminal_command(f"tmux attach-session -t {session_name}")
- exec_shell_command_async(terminal_cmd)
- self.close_manager()
- except Exception as e:
- print(f"Error attaching to tmux session: {e}")
-
- def get_terminal_command(self, cmd):
- """Get terminal command based on configured terminal or available terminals"""
- # First try to use the configured terminal command
- if hasattr(data, 'TERMINAL_COMMAND') and data.TERMINAL_COMMAND:
- parts = data.TERMINAL_COMMAND.split()
- terminal = parts[0]
-
- try:
- # Check if the configured terminal is available
- subprocess.run(["which", terminal], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return f"{data.TERMINAL_COMMAND} {cmd}"
- except subprocess.CalledProcessError:
- # If configured terminal is not available, fall back to defaults
- pass
-
- # Fallback to checking available terminals
- terminals = [
- ("kitty", f"kitty -e {cmd}"),
- ("alacritty", f"alacritty -e {cmd}"),
- ("foot", f"foot {cmd}"),
- ("gnome-terminal", f"gnome-terminal -- {cmd}"),
- ("konsole", f"konsole -e {cmd}"),
- ("xfce4-terminal", f"xfce4-terminal -e '{cmd}'"),
- ]
-
- for term, term_cmd in terminals:
- try:
- # Check if terminal is available
- subprocess.run(["which", term], check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
- return term_cmd
- except subprocess.CalledProcessError:
- continue
-
- # Default fallback
- return f"kitty -e {cmd}"
-
- def rename_session_dialog(self, old_name):
- """Show dialog to rename a session"""
- dialog = Gtk.Dialog(
- title="Rename Session",
- transient_for=None,
- flags=0
- )
-
- dialog.add_buttons(
- Gtk.STOCK_CANCEL, Gtk.ResponseType.CANCEL,
- Gtk.STOCK_OK, Gtk.ResponseType.OK
- )
-
- content_area = dialog.get_content_area()
- entry = Gtk.Entry()
- entry.set_text(old_name)
- entry.set_activates_default(True)
- content_area.add(entry)
-
- dialog.set_default_response(Gtk.ResponseType.OK)
- dialog.show_all()
-
- response = dialog.run()
- if response == Gtk.ResponseType.OK:
- new_name = entry.get_text()
- if new_name and new_name != old_name:
- self.rename_session(old_name, new_name)
-
- dialog.destroy()
-
- def rename_session(self, old_name, new_name):
- """Rename a tmux session"""
- try:
- # Clean the session name (replace spaces with underscores)
- clean_name = new_name.strip().replace(" ", "_")
-
- # Rename session
- subprocess.run(
- ["tmux", "rename-session", "-t", old_name, clean_name],
- check=True
- )
-
- # Refresh the session list
- self.refresh_sessions()
-
- except Exception as e:
- print(f"Error renaming tmux session: {e}")
-
- def kill_session(self, session_name):
- """Kill a tmux session"""
- try:
- # Kill session
- subprocess.run(
- ["tmux", "kill-session", "-t", session_name],
- check=True
- )
-
- # Refresh the session list
- self.refresh_sessions()
-
- # Close the launcher after killing session
- self.close_manager()
-
- except Exception as e:
- print(f"Error killing tmux session: {e}")
-
- # Add new method to handle key presses on session slots
- def on_slot_key_press(self, button, event, session_name, label, entry):
- """Handle key presses on session buttons"""
- # Print debugging info
- print(f"Key pressed: {event.keyval}, State: {event.state}")
-
- # Check if 'r' key was pressed for renaming
- if event.keyval == Gdk.KEY_r:
- self.start_rename(button, session_name, label, entry)
- return True
- # Check for 'K' (capital K) which indicates Shift is pressed
- elif event.keyval == Gdk.KEY_K:
- print("Shift+K detected - killing session without confirmation")
- self.kill_session(session_name)
- return True
- # Check for lowercase 'k'
- elif event.keyval == Gdk.KEY_k:
- print("Regular k detected - showing confirmation")
- self.show_kill_confirmation_menu(button, session_name)
- return True
- # Check if Delete key was pressed for killing session
- elif event.keyval == Gdk.KEY_Delete:
- self.show_kill_confirmation_menu(button, session_name)
- return True
- return False
-
- def show_kill_confirmation_menu(self, button, session_name):
- """Show a confirmation menu for killing a session"""
- menu = Gtk.Menu()
-
- # Confirmation message as a disabled menu item
- msg_item = Gtk.MenuItem(label=f"Kill session '{session_name}'?")
- msg_item.set_sensitive(False)
- menu.append(msg_item)
-
- # Separator
- menu.append(Gtk.SeparatorMenuItem())
-
- # Confirm option
- confirm_item = Gtk.MenuItem(label="Confirm")
- confirm_item.connect("activate", lambda _: self.kill_session(session_name))
- menu.append(confirm_item)
-
- # Cancel option
- cancel_item = Gtk.MenuItem(label="Cancel")
- # Close launcher on cancel
- cancel_item.connect("activate", lambda _: self.close_manager())
- menu.append(cancel_item)
-
- menu.show_all()
-
- # Show the menu positioned at the button
- menu.popup_at_widget(
- button,
- Gdk.Gravity.SOUTH_WEST,
- Gdk.Gravity.NORTH_WEST,
- None
- )
\ No newline at end of file
diff --git a/modules/launcher/components/tools.py b/modules/launcher/components/tools.py
deleted file mode 100644
index d04c39d..0000000
--- a/modules/launcher/components/tools.py
+++ /dev/null
@@ -1,370 +0,0 @@
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.utils.helpers import exec_shell_command_async, get_relative_path
-import utils.icons as icons
-from gi.repository import Gdk, GLib
-import os
-import subprocess
-import threading
-
-
-CAPTURE_SCRIPT = get_relative_path("../../../config/scripts/capture.sh")
-GAMEMODE_SCRIPT = get_relative_path("../../../config/scripts/gamemode.sh")
-
-
-class Toolbox(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="toolbox",
- orientation="h",
- spacing=4,
- v_align="center",
- h_align="center",
- visible=True,
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
-
- self.btn_ssregion = Button(
- name="toolbox-button",
- child=Label(name="button-label", markup=icons.ssregion),
- on_clicked=self.ssregion,
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
- # Enable keyboard focus and connect events
- self.btn_ssregion.set_can_focus(True)
- self.btn_ssregion.connect("button-press-event", self.on_ssregion_click)
- self.btn_ssregion.connect("key-press-event", self.on_ssregion_key)
-
- self.btn_ssfull = Button(
- name="toolbox-button",
- child=Label(name="button-label", markup=icons.ssfull),
- on_clicked=self.ssfull,
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
- # Enable keyboard focus and connect events
- self.btn_ssfull.set_can_focus(True)
- self.btn_ssfull.connect("button-press-event", self.on_ssfull_click)
- self.btn_ssfull.connect("key-press-event", self.on_ssfull_key)
-
- self.btn_screenrecord = Button(
- name="toolbox-button",
- child=Label(name="button-label", markup=icons.screenrecord),
- on_clicked=self.screenrecord,
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
-
- self.btn_ocr = Button(
- name="toolbox-button",
- child=Label(name="button-label", markup=icons.ocr),
- on_clicked=self.ocr,
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
-
- self.btn_color = Button(
- name="toolbox-button",
- tooltip_text="Color Picker\nLeft Click: HEX\nMiddle Click: HSV\nRight Click: RGB\n\nKeyboard:\nEnter: HEX\nShift+Enter: RGB\nCtrl+Enter: HSV",
- child=Label(name="button-bar-label", markup=icons.colorpicker),
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
-
- self.btn_gamemode = Button(
- name="toolbox-button",
- child=Label(name="button-label", markup=icons.gamemode),
- on_clicked=self.gamemode,
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
-
- # Enable keyboard focus for the colorpicker button.
- self.btn_color.set_can_focus(True)
- # Connect both mouse and keyboard events.
- self.btn_color.connect("button-press-event", self.colorpicker)
- self.btn_color.connect("key_press_event", self.colorpicker_key)
-
- self.btn_emoji = Button(
- name="toolbox-button",
- child=Label(name="button-label", markup=icons.emoji),
- on_clicked=self.emoji,
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
-
- self.btn_screenshots_folder = Button(
- name="toolbox-button",
- child=Label(name="button-label", markup=icons.screenshots),
- on_clicked=self.open_screenshots_folder,
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
-
- self.btn_recordings_folder = Button(
- name="toolbox-button",
- child=Label(name="button-label", markup=icons.recordings),
- on_clicked=self.open_recordings_folder,
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- )
-
- self.buttons = [
- self.btn_ssregion,
- self.btn_ssfull,
- self.btn_screenshots_folder,
- Box(
- name="tool-sep",
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- ),
- self.btn_screenrecord,
- self.btn_recordings_folder,
- Box(
- name="tool-sep",
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- ),
- self.btn_ocr,
- self.btn_color,
- Box(
- name="tool-sep",
- h_expand=False,
- v_expand=False,
- h_align="center",
- v_align="center",
- ),
- self.btn_gamemode,
- self.btn_emoji,
- ]
-
- for button in self.buttons:
- self.add(button)
-
- self.show_all()
-
- # Start polling for process state every second.
- self.recorder_timer_id = GLib.timeout_add_seconds(
- 1, self.update_screenrecord_state
- )
-
- def close_menu(self):
- self.launcher.close()
-
- # Action methods
- def ssfull(self, *args):
- exec_shell_command_async(f"bash {CAPTURE_SCRIPT} screenshot p")
- self.close_menu()
-
- def on_ssfull_click(self, button, event):
- if event.type == Gdk.EventType.BUTTON_PRESS:
- if event.button == 1: # Left click
- self.ssfull()
- elif event.button == 3: # Right click
- exec_shell_command_async(
- f"bash {CAPTURE_SCRIPT} screenshot p mockup")
- self.close_menu()
- return True
- return False
-
- def on_ssfull_key(self, widget, event):
- if event.keyval in {Gdk.KEY_Return, Gdk.KEY_KP_Enter}:
- modifiers = event.get_state()
- if modifiers & Gdk.ModifierType.SHIFT_MASK:
- exec_shell_command_async(
- f"bash {CAPTURE_SCRIPT} screenshot p mockup")
- self.close_menu()
- else:
- self.ssfull()
- return True
- return False
-
- def ssregion(self, *args):
- exec_shell_command_async(f"bash {CAPTURE_SCRIPT} screenshot sf")
- self.close_menu()
-
- def on_ssregion_click(self, button, event):
- if event.type == Gdk.EventType.BUTTON_PRESS:
- if event.button == 1: # Left click
- self.ssregion()
- elif event.button == 3: # Right click
- exec_shell_command_async(
- f"bash {CAPTURE_SCRIPT} screenshot sf mockup")
- self.close_menu()
- return True
- return False
-
- def on_ssregion_key(self, widget, event):
- if event.keyval in {Gdk.KEY_Return, Gdk.KEY_KP_Enter}:
- modifiers = event.get_state()
- if modifiers & Gdk.ModifierType.SHIFT_MASK:
- exec_shell_command_async(
- f"bash {CAPTURE_SCRIPT} screenshot sf mockup")
- self.close_menu()
- else:
- self.ssregion()
- return True
- return False
-
- def screenrecord(self, *args):
- # Launch screenrecord script in detached mode so that it remains running independently of this program.
- exec_shell_command_async(
- f"bash -c 'nohup bash {CAPTURE_SCRIPT} record > /dev/null 2>&1 & disown'"
- )
- self.close_menu()
-
- def ocr(self, *args):
- exec_shell_command_async(f"bash {CAPTURE_SCRIPT} ocr sf")
- self.close_menu()
-
- def gamemode(self, *args):
- exec_shell_command_async(f"bash {GAMEMODE_SCRIPT}")
- self.gamemode_check()
- self.close_menu()
-
- def gamemode_check(self):
- def check():
- try:
- result = subprocess.run(
- f"bash {GAMEMODE_SCRIPT} check",
- shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- enabled = result.stdout == b"t\n"
- except Exception:
- enabled = False
-
- def update_ui():
- if enabled:
- self.btn_gamemode.get_child().set_markup(icons.gamemode_off)
- else:
- self.btn_gamemode.get_child().set_markup(icons.gamemode)
- return False
-
- GLib.idle_add(update_ui)
- return False
-
- GLib.idle_add(lambda: threading.Thread(target=check).start())
- return True
-
- def ssregion(self, *args):
- exec_shell_command_async(f"bash {CAPTURE_SCRIPT} screenshot sf")
- self.close_menu()
-
- def colorpicker(self, button, event):
- if event.type == Gdk.EventType.BUTTON_PRESS:
- cmd = {
- 1: "-hex", # Left click
- 2: "-hsv", # Middle click
- 3: "-rgb", # Right click
- }.get(event.button)
-
- if cmd:
- exec_shell_command_async(
- f"bash {
- get_relative_path(
- '../../../config/scripts/hyprpicker.sh')
- } {cmd}"
- )
- self.close_menu()
-
- def colorpicker_key(self, widget, event):
- if event.keyval in {Gdk.KEY_Return, Gdk.KEY_KP_Enter}:
- modifiers = event.get_state()
- cmd = "-hex" # Default
-
- match modifiers & (
- Gdk.ModifierType.SHIFT_MASK | Gdk.ModifierType.CONTROL_MASK
- ):
- case Gdk.ModifierType.SHIFT_MASK:
- cmd = "-rgb"
- case Gdk.ModifierType.CONTROL_MASK:
- cmd = "-hsv"
-
- exec_shell_command_async(
- f"bash {get_relative_path('../../../config/scripts/hyprpicker.sh')} {
- cmd
- }"
- )
- self.close_menu()
- return True
- return False
-
- def update_screenrecord_state(self):
- def check():
- try:
- result = subprocess.run(
- "pgrep -f gpu-screen-recorder",
- shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
- running = result.returncode == 0
- except Exception:
- running = False
-
- def update_ui():
- if running:
- self.btn_screenrecord.get_child().set_markup(icons.stop)
- self.btn_screenrecord.add_style_class("recording")
- else:
- self.btn_screenrecord.get_child().set_markup(icons.screenrecord)
- self.btn_screenrecord.remove_style_class("recording")
- return False
-
- GLib.idle_add(update_ui)
- return False
-
- GLib.idle_add(lambda: threading.Thread(target=check).start())
- return True
-
- def open_screenshots_folder(self, *args):
- screenshots_dir = os.path.join(
- os.environ.get("XDG_PICTURES_DIR",
- os.path.expanduser("~/Pictures")),
- "Screenshots",
- )
- # Create directory if it doesn't exist
- os.makedirs(screenshots_dir, exist_ok=True)
- exec_shell_command_async(f"xdg-open {screenshots_dir}")
- self.close_menu()
-
- def open_recordings_folder(self, *args):
- recordings_dir = os.path.join(
- os.environ.get("XDG_VIDEOS_DIR", os.path.expanduser("~/Videos")),
- "Recordings",
- )
- # Create directory if it doesn't exist
- os.makedirs(recordings_dir, exist_ok=True)
- exec_shell_command_async(f"xdg-open {recordings_dir}")
- self.close_menu()
-
- def emoji(self, *args):
- self.launcher.open("emoji")
diff --git a/modules/launcher/components/wallpapers.py b/modules/launcher/components/wallpapers.py
deleted file mode 100644
index 45c05b4..0000000
--- a/modules/launcher/components/wallpapers.py
+++ /dev/null
@@ -1,485 +0,0 @@
-import os
-import hashlib
-import shutil
-import colorsys
-from gi.repository import GdkPixbuf, Gtk, GLib, Gio, Gdk, Pango
-from fabric.widgets.box import Box
-from fabric.widgets.button import Button
-from fabric.widgets.centerbox import CenterBox
-from fabric.widgets.entry import Entry
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from fabric.widgets.label import Label
-from fabric.utils.helpers import exec_shell_command_async
-import utils.icons as icons
-import config.data as data
-import config.config
-from PIL import Image
-import concurrent.futures
-from concurrent.futures import ThreadPoolExecutor
-
-class WallpaperSelector(Box):
- CACHE_DIR = f"{data.CACHE_DIR}/thumbnails" # Changed from wallpapers to thumbs
-
- def __init__(self, **kwargs):
-
- super().__init__(name="wallpapers", spacing=4, orientation="v", h_expand=False, v_expand=False, **kwargs)
- os.makedirs(self.CACHE_DIR, exist_ok=True)
-
- # Process old wallpapers: use os.scandir for efficiency and only loop
- # over image files that actually need renaming (they're not already lowercase
- # and with hyphens instead of spaces)
- with os.scandir(data.WALLPAPERS_DIR) as entries:
- for entry in entries:
- if entry.is_file() and self._is_image(entry.name):
- # Check if the file needs renaming: file should be lowercase and have hyphens instead of spaces
- if entry.name != entry.name.lower() or " " in entry.name:
- new_name = entry.name.lower().replace(" ", "-")
- full_path = os.path.join(data.WALLPAPERS_DIR, entry.name)
- new_full_path = os.path.join(data.WALLPAPERS_DIR, new_name)
- try:
- os.rename(full_path, new_full_path)
- print(f"Renamed old wallpaper '{full_path}' to '{new_full_path}'")
- except Exception as e:
- print(f"Error renaming file {full_path}: {e}")
-
- # Refresh the file list after potential renaming
- self.files = sorted([f for f in os.listdir(data.WALLPAPERS_DIR) if self._is_image(f)])
- self.thumbnails = []
- self.thumbnail_queue = []
- self.executor = ThreadPoolExecutor(max_workers=4) # Shared executor
-
- # Variable to control the selection (similar to AppLauncher)
- self.selected_index = -1
-
- # Initialize UI components
- self.viewport = Gtk.IconView(name="wallpaper-icons")
- self.viewport.set_model(Gtk.ListStore(GdkPixbuf.Pixbuf, str))
- self.viewport.set_pixbuf_column(0)
- # Hide text column so only the image is shown
- self.viewport.set_text_column(-1)
- self.viewport.set_item_width(0)
- self.viewport.connect("item-activated", self.on_wallpaper_selected)
- # self.viewport.connect("selection-changed", self._on_selection_changed) # Removed connection
-
- self.scrolled_window = ScrolledWindow(
- name="scrolled-window",
- spacing=10,
- h_expand=True,
- v_expand=True,
- child=self.viewport,
- )
-
- self.search_entry = Entry(
- name="search-entry-walls",
- placeholder="Search Wallpapers...",
- h_expand=True,
- notify_text=lambda entry, *_: self.arrange_viewport(entry.get_text()),
- on_key_press_event=self.on_search_entry_key_press,
- )
- self.search_entry.props.xalign = 0.5
- self.search_entry.connect("focus-out-event", self.on_search_entry_focus_out)
-
- self.schemes = {
- "scheme-tonal-spot": "Tonal Spot",
- "scheme-content": "Content",
- "scheme-expressive": "Expressive",
- "scheme-fidelity": "Fidelity",
- "scheme-fruit-salad": "Fruit Salad",
- "scheme-monochrome": "Monochrome",
- "scheme-neutral": "Neutral",
- "scheme-rainbow": "Rainbow",
- }
-
- self.scheme_dropdown = Gtk.ComboBoxText()
- self.scheme_dropdown.set_name("scheme-dropdown")
- self.scheme_dropdown.set_tooltip_text("Select color scheme")
- for key, display_name in self.schemes.items():
- self.scheme_dropdown.append(key, display_name)
- self.scheme_dropdown.set_active_id("scheme-tonal-spot")
- self.scheme_dropdown.connect("changed", self.on_scheme_changed)
-
- # Load matugen state from the dedicated file
- self.matugen_enabled = True # Default to True
- try:
- with open(data.MATUGEN_STATE_FILE, 'r') as f:
- content = f.read().strip().lower()
- if content == "false":
- self.matugen_enabled = False
- elif content == "true":
- self.matugen_enabled = True
- # Any other content defaults to True
- except FileNotFoundError:
- # File doesn't exist, keep default True and create it on first toggle
- pass
- except Exception as e:
- print(f"Error reading matugen state file: {e}")
- # Keep default True on error
-
- # Create a switcher to enable/disable Matugen (enabled by default)
- self.matugen_switcher = Gtk.Switch(name="matugen-switcher")
- self.matugen_switcher.set_vexpand(False)
- self.matugen_switcher.set_hexpand(False)
- self.matugen_switcher.set_valign(Gtk.Align.CENTER)
- self.matugen_switcher.set_halign(Gtk.Align.CENTER)
- self.matugen_switcher.set_active(self.matugen_enabled)
- self.matugen_switcher.connect("notify::active", self.on_switch_toggled)
-
- self.mat_icon = Label(name="mat-label", markup=icons.palette)
-
- # Add the switcher to the header_box's start_children
- self.header_box = CenterBox(
- name="header-box",
- spacing=8,
- orientation="h",
- # Removed color button and label from here
- start_children=[self.matugen_switcher, self.mat_icon],
- center_children=[self.search_entry],
- end_children=[self.scheme_dropdown],
- )
-
- self.add(self.header_box)
-
- # Create the custom color selector components
- self.hue_slider = Gtk.Scale(
- orientation=Gtk.Orientation.HORIZONTAL, # Changed from VERTICAL
- adjustment=Gtk.Adjustment(value=0, lower=0, upper=360, step_increment=1, page_increment=10),
- draw_value=False, # Hide the default value text
- digits=0,
- # inverted=True, # Removed inverted for horizontal
- name="hue-slider", # For CSS styling
- )
-
- # Changed expand/align for horizontal orientation
- self.hue_slider.set_hexpand(True)
- self.hue_slider.set_halign(Gtk.Align.FILL)
- self.hue_slider.set_vexpand(False) # Ensure it doesn't expand vertically
- self.hue_slider.set_valign(Gtk.Align.CENTER) # Center vertically within its box
-
- self.apply_color_button = Button(name="apply-color-button", child=Label(name="apply-color-label", markup=icons.accept))
- self.apply_color_button.connect("clicked", self.on_apply_color_clicked)
- self.apply_color_button.set_vexpand(False) # Ensure button doesn't expand vertically
- self.apply_color_button.set_valign(Gtk.Align.CENTER) # Center button vertically
-
- self.custom_color_selector_box = Box(
- orientation="h", spacing=5, name="custom-color-selector-box", # Changed orientation to horizontal
- h_align="center" # Center the horizontal box
- )
- self.custom_color_selector_box.add(self.hue_slider)
- self.custom_color_selector_box.add(self.apply_color_button)
- self.custom_color_selector_box.set_halign(Gtk.Align.FILL)
-
- # Add the scrolled window (grid) and the custom color selector box directly
- # to the main WallpaperSelector box (which is already vertical)
- self.pack_start(self.scrolled_window, True, True, 0) # Add grid, expand
- self.pack_start(self.custom_color_selector_box, False, False, 0) # Add custom selector, don't expand
-
- # Removed the old main_content_box and its add
-
- self._start_thumbnail_thread()
- self.connect("map", self.on_map) # Connect the map signal
- # Set initial sensitivity based on loaded state
- # self.scheme_dropdown.set_sensitive(self.matugen_enabled) # Ensure sensitivity is set correctly on load
- self.setup_file_monitor() # Initialize file monitoring
- self.show_all()
- # Ensure the search entry gets focus when starting
- self.search_entry.grab_focus()
-
- def setup_file_monitor(self):
- gfile = Gio.File.new_for_path(data.WALLPAPERS_DIR)
- self.file_monitor = gfile.monitor_directory(Gio.FileMonitorFlags.NONE, None)
- self.file_monitor.connect("changed", self.on_directory_changed)
-
- def on_directory_changed(self, monitor, file, other_file, event_type):
- file_name = file.get_basename()
- if event_type == Gio.FileMonitorEvent.DELETED:
- if file_name in self.files:
- self.files.remove(file_name)
- cache_path = self._get_cache_path(file_name)
- if os.path.exists(cache_path):
- try:
- os.remove(cache_path)
- except Exception as e:
- print(f"Error deleting cache {cache_path}: {e}")
- self.thumbnails = [(p, n) for p, n in self.thumbnails if n != file_name]
- GLib.idle_add(self.arrange_viewport, self.search_entry.get_text())
- elif event_type == Gio.FileMonitorEvent.CREATED:
- if self._is_image(file_name):
- # Convert filename to lowercase and replace spaces with "-"
- new_name = file_name.lower().replace(" ", "-")
- full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
- new_full_path = os.path.join(data.WALLPAPERS_DIR, new_name)
- if new_name != file_name:
- try:
- os.rename(full_path, new_full_path)
- file_name = new_name
- print(f"Renamed file '{full_path}' to '{new_full_path}')")
- except Exception as e:
- print(f"Error renaming file {full_path}: {e}")
- if file_name not in self.files:
- self.files.append(file_name)
- self.files.sort()
- self.executor.submit(self._process_file, file_name)
- elif event_type == Gio.FileMonitorEvent.CHANGED:
- if self._is_image(file_name) and file_name in self.files:
- cache_path = self._get_cache_path(file_name)
- if os.path.exists(cache_path):
- try:
- os.remove(cache_path)
- except Exception as e:
- print(f"Error deleting cache for changed file {file_name}: {e}")
- self.executor.submit(self._process_file, file_name)
-
- def arrange_viewport(self, query: str = ""):
- model = self.viewport.get_model()
- model.clear()
- filtered_thumbnails = [
- (thumb, name)
- for thumb, name in self.thumbnails
- if query.casefold() in name.casefold()
- ]
- filtered_thumbnails.sort(key=lambda x: x[1].lower())
- for pixbuf, file_name in filtered_thumbnails:
- model.append([pixbuf, file_name])
- # If the search entry is empty, no icon is selected; otherwise, select the first one.
- if query.strip() == "":
- self.viewport.unselect_all()
- self.selected_index = -1
- elif len(model) > 0:
- self.update_selection(0)
-
- def on_wallpaper_selected(self, iconview, path):
- model = iconview.get_model()
- file_name = model[path][1]
- full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
- selected_scheme = self.scheme_dropdown.get_active_id()
- current_wall = os.path.expanduser(f"~/.current.wall")
- if os.path.isfile(current_wall):
- os.remove(current_wall)
- os.symlink(full_path, current_wall)
- if self.matugen_switcher.get_active():
- # Matugen is enabled: run the normal command.
- exec_shell_command_async(f'matugen image {full_path} -t {selected_scheme}')
- else:
- # Matugen is disabled: run the alternative swww command.
- exec_shell_command_async(
- f'swww img {full_path} -t outer --transition-duration 1.5 --transition-step 255 --transition-fps 60 -f Nearest'
- )
-
- def on_scheme_changed(self, combo):
- selected_scheme = combo.get_active_id()
- print(f"Color scheme selected: {selected_scheme}")
-
- def on_search_entry_key_press(self, widget, event):
- if event.state & Gdk.ModifierType.SHIFT_MASK:
- if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down):
- schemes_list = list(self.schemes.keys())
- current_id = self.scheme_dropdown.get_active_id()
- current_index = schemes_list.index(current_id) if current_id in schemes_list else 0
- new_index = (current_index - 1) % len(schemes_list) if event.keyval == Gdk.KEY_Up else (current_index + 1) % len(schemes_list)
- self.scheme_dropdown.set_active(new_index)
- return True
- elif event.keyval == Gdk.KEY_Right:
- self.scheme_dropdown.popup()
- return True
-
- if event.keyval in (Gdk.KEY_Up, Gdk.KEY_Down, Gdk.KEY_Left, Gdk.KEY_Right):
- self.move_selection_2d(event.keyval)
- return True
- elif event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
- if self.selected_index != -1:
- path = Gtk.TreePath.new_from_indices([self.selected_index])
- self.on_wallpaper_selected(self.viewport, path)
- return True
- return False
-
- # Removed _on_selection_changed method
-
- def move_selection_2d(self, keyval):
- model = self.viewport.get_model()
- total_items = len(model)
- if total_items == 0:
- return
-
- # --- Determine Column Count ---
- columns = self.viewport.get_columns()
-
- # If get_columns returns 0 or -1 (auto), try to estimate by checking item rows
- if columns <= 0 and total_items > 0:
- estimated_cols = 0
- try:
- # Check the row of the first item (should be 0)
- first_item_path = Gtk.TreePath.new_from_indices([0])
- base_row = self.viewport.get_item_row(first_item_path)
-
- # Find the index of the first item in the *next* row
- for i in range(1, total_items):
- path = Gtk.TreePath.new_from_indices([i])
- row = self.viewport.get_item_row(path)
- if row > base_row:
- estimated_cols = i # The number of items in the first row
- break
-
- # If loop finished without finding a new row, all items are in one row
- if estimated_cols == 0:
- estimated_cols = total_items
-
- columns = max(1, estimated_cols)
- except Exception:
- # Fallback if get_item_row fails (e.g., widget not realized)
- columns = 1
- elif columns <= 0 and total_items == 0:
- columns = 1 # Should not happen due to early return, but safe
-
- # Ensure columns is at least 1 after all checks
- columns = max(1, columns)
-
- # --- Navigation Logic ---
- current_index = self.selected_index
- new_index = current_index
-
- if current_index == -1:
- # If nothing is selected, select the first or last item based on direction
- if keyval in (Gdk.KEY_Down, Gdk.KEY_Right):
- new_index = 0
- elif keyval in (Gdk.KEY_Up, Gdk.KEY_Left):
- new_index = total_items - 1
- if total_items == 0: new_index = -1 # Handle edge case
-
- else:
- # Calculate potential new index based on key press
- if keyval == Gdk.KEY_Up:
- potential_new_index = current_index - columns
- # Only update if the new index is valid (>= 0)
- if potential_new_index >= 0:
- new_index = potential_new_index
- elif keyval == Gdk.KEY_Down:
- potential_new_index = current_index + columns
- # Only update if the new index is valid (< total_items)
- if potential_new_index < total_items:
- new_index = potential_new_index
- elif keyval == Gdk.KEY_Left:
- # Only update if not already in the first column (index % columns != 0)
- # and the index is greater than 0
- if current_index > 0 and current_index % columns != 0:
- new_index = current_index - 1
- elif keyval == Gdk.KEY_Right:
- # Only update if not in the last column ((index + 1) % columns != 0)
- # and not the very last item (index < total_items - 1)
- if current_index < total_items - 1 and (current_index + 1) % columns != 0:
- new_index = current_index + 1
-
- # Only update if the index actually changed and is valid
- if new_index != self.selected_index and 0 <= new_index < total_items:
- self.update_selection(new_index)
- elif total_items > 0 and self.selected_index == -1 and 0 <= new_index < total_items:
- # Handle selecting the first item when starting from -1
- self.update_selection(new_index)
-
- def update_selection(self, new_index: int):
- self.viewport.unselect_all()
- path = Gtk.TreePath.new_from_indices([new_index])
- self.viewport.select_path(path)
- self.viewport.scroll_to_path(path, False, 0.5, 0.5) # Ensure the selected icon is visible
- self.selected_index = new_index
-
- def _start_thumbnail_thread(self):
- thread = GLib.Thread.new("thumbnail-loader", self._preload_thumbnails, None)
-
- def _preload_thumbnails(self, _data):
- futures = [self.executor.submit(self._process_file, file_name) for file_name in self.files]
- concurrent.futures.wait(futures)
- GLib.idle_add(self._process_batch)
-
- def _process_file(self, file_name):
- full_path = os.path.join(data.WALLPAPERS_DIR, file_name)
- cache_path = self._get_cache_path(file_name)
- if not os.path.exists(cache_path):
- try:
- with Image.open(full_path) as img:
- width, height = img.size
- side = min(width, height)
- left = (img.width - side) // 2
- top = (height - side) // 2
- right = left + side
- bottom = top + side
- img_cropped = img.crop((left, top, right, bottom))
- img_cropped.thumbnail((96, 96), Image.Resampling.LANCZOS)
- img_cropped.save(cache_path, "PNG")
- except Exception as e:
- print(f"Error processing {file_name}: {e}")
- return
- self.thumbnail_queue.append((cache_path, file_name))
- GLib.idle_add(self._process_batch)
-
- def _process_batch(self):
- batch = self.thumbnail_queue[:10]
- del self.thumbnail_queue[:10]
- for cache_path, file_name in batch:
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(cache_path)
- self.thumbnails.append((pixbuf, file_name))
- self.viewport.get_model().append([pixbuf, file_name])
- except Exception as e:
- print(f"Error loading thumbnail {cache_path}: {e}")
- if self.thumbnail_queue:
- GLib.idle_add(self._process_batch)
- return False
-
- def _get_cache_path(self, file_name: str) -> str:
- file_hash = hashlib.md5(file_name.encode("utf-8")).hexdigest()
- return os.path.join(self.CACHE_DIR, f"{file_hash}.png")
-
- @staticmethod
- def _is_image(file_name: str) -> bool:
- return file_name.lower().endswith(('.png', '.jpg', '.jpeg', '.bmp', '.gif', '.webp'))
-
- def on_search_entry_focus_out(self, widget, event):
- if self.get_mapped():
- widget.grab_focus()
- return False
-
- def on_map(self, widget):
- """Handles the map signal to set initial visibility of the color selector."""
- # Set visibility based on the loaded state when the widget becomes visible
- self.custom_color_selector_box.set_visible(not self.matugen_enabled)
-
- def hsl_to_rgb_hex(self, h: float, s: float = 1.0, l: float = 0.5) -> str:
- """Converts HSL color value to RGB HEX string."""
- # colorsys uses HLS, not HSL, and expects values between 0.0 and 1.0
- hue = h / 360.0
- r, g, b = colorsys.hls_to_rgb(hue, l, s) # Note the order: H, L, S
- r_int, g_int, b_int = int(r * 255), int(g * 255), int(b * 255)
- return f"#{r_int:02X}{g_int:02X}{b_int:02X}"
-
- def rgba_to_hex(self, rgba: Gdk.RGBA) -> str:
- """Converts Gdk.RGBA to a HEX color string."""
- r = int(rgba.red * 255)
- g = int(rgba.green * 255)
- b = int(rgba.blue * 255)
- return f"#{r:02X}{g:02X}{b:02X}"
-
- def on_switch_toggled(self, switch, gparam):
- """Handles the toggling of the Matugen switch."""
- is_active = switch.get_active()
- self.matugen_enabled = is_active
- # self.scheme_dropdown.set_sensitive(is_active)
- self.custom_color_selector_box.set_visible(not is_active) # Toggle visibility
-
- # Save the state to the dedicated file
- try:
- with open(data.MATUGEN_STATE_FILE, 'w') as f:
- f.write(str(is_active))
- except Exception as e:
- print(f"Error writing matugen state file: {e}")
-
- def on_apply_color_clicked(self, button):
- """Applies the color selected by the hue slider via matugen."""
- hue_value = self.hue_slider.get_value() # Get value from 0-360
- hex_color = self.hsl_to_rgb_hex(hue_value) # Convert HSL(hue, 1.0, 0.5) to HEX
- print(f"Applying color from slider: H={hue_value}, HEX={hex_color}")
- selected_scheme = self.scheme_dropdown.get_active_id()
- # Run matugen with the chosen hex color and selected scheme
- exec_shell_command_async(f'matugen color hex "{hex_color}" -t {selected_scheme}')
- # Optionally save the chosen color to config if needed later
- # config.config.bind_vars["matugen_hex_color"] = hex_color
- # config.config.save_config() # Removed as save_config doesn't exist
diff --git a/modules/launcher/components/wifi.py b/modules/launcher/components/wifi.py
deleted file mode 100644
index 94e52d6..0000000
--- a/modules/launcher/components/wifi.py
+++ /dev/null
@@ -1,153 +0,0 @@
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.image import Image
-from fabric.widgets.button import Button
-from fabric.widgets.centerbox import CenterBox
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from services import network_client
-import utils.icons as icons
-
-
-class WifiNetworkSlot(CenterBox):
- def __init__(self, network, client: network_client, **kwargs):
- super().__init__(name="wifi-device", **kwargs)
- self.network = network
- self.client = client
- self.connecting = False # Tracks if we are connecting/disconnecting
-
- self.connection_icon = Label(name="wifi-connection", markup=icons.wifi_off)
- self.connect_button = Button(
- name="wifi-connect",
- label="Connect",
- on_clicked=self.on_connect_clicked,
- )
-
- self.start_children = [
- Box(
- spacing=8,
- children=[
- Image(icon_name=network.get("icon-name"), size=32),
- Label(label=network.get("ssid")),
- self.connection_icon,
- ],
- )
- ]
- self.end_children = self.connect_button
-
- self.visible = True
- self.show_all()
-
- # Listen to WiFi state changes
- self.client.wifi_device.connect("changed", lambda *_: self.update_status())
-
- self.update_status()
-
- def on_connect_clicked(self, *_):
- connected = self.network.get("ssid") == self.client.wifi_device.ssid
- if connected:
- self.connect_button.set_label("Disconnecting...")
- self.client.wifi_device.enabled = False # Disables WiFi to disconnect
- else:
- self.connect_button.set_label("Connecting...")
- self.client.connect_wifi_bssid(self.network.get("bssid"))
-
- self.connecting = True
- self.connect_button.sensitive = False
-
- def update_status(self):
- state = self.client.wifi_device.state
- connected = self.network.get("ssid") == self.client.wifi_device.ssid
-
- if self.connecting:
- if state in ["activating"]:
- self.connect_button.set_label("Connecting...")
- elif state in ["deactivating"]:
- self.connect_button.set_label("Disconnecting...")
- else:
- self.connecting = False
- self.connect_button.sensitive = True
-
- if not self.connecting:
- self.connection_icon.set_markup(icons.wifi if connected else icons.wifi_off)
- self.connect_button.set_label("Disconnect" if connected else "Connect")
-
-
-class WifiManager(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="wifi",
- spacing=8,
- orientation="vertical",
- **kwargs,
- )
-
- self.client = network_client
- self.scan_icon = Label(name="wifi-scan-icon", markup=icons.scan)
- self.toggle_icon = Label(name="wifi-toggle-icon", markup=icons.wifi_off)
-
- self.scan_button = Button(
- name="wifi-scan",
- child=self.scan_icon,
- tooltip_text="Scan",
- on_clicked=lambda *_: self.client.wifi_device
- and self.client.wifi_device.scan(),
- )
-
- self.toggle_button = Button(
- name="wifi-toggle",
- tooltip_text="Toggle WiFi",
- child=self.toggle_icon,
- on_clicked=lambda *_: self.client.wifi_device
- and self.client.wifi_device.toggle_wifi(),
- )
-
- self.available_box = Box(spacing=2, orientation="vertical")
-
- self.children = [
- CenterBox(
- name="wifi-header",
- start_children=self.scan_button,
- center_children=Label(name="wifi-text", label="WiFi Networks"),
- end_children=self.toggle_button,
- ),
- ScrolledWindow(
- name="wifi-available",
- v_expand=True,
- h_scrollbar_policy="never",
- min_content_size=(-1, -1),
- child=self.available_box,
- ),
- ]
-
- self.client.connect("device-ready", lambda *_: self.on_device_ready())
-
- def on_device_ready(self):
- if not self.client.wifi_device:
- return
-
- self.update_toggle_icon()
-
- self.client.wifi_device.connect(
- "notify::enabled",
- lambda *_: self.update_toggle_icon(),
- )
-
- self.build_wifi_options()
-
- def update_toggle_icon(self):
- self.toggle_icon.set_markup(
- icons.wifi if self.client.wifi_device.enabled else icons.wifi_off
- )
-
- def build_wifi_options(self):
- self.available_box.children = []
- if not self.client.wifi_device:
- return
-
- aps = self.client.wifi_device.access_points
- seen_ssids = set()
- for ap in aps:
- ssid = ap.get("ssid")
- if ap.get("ssid") != "Unknown" and ssid not in seen_ssids:
- seen_ssids.add(ssid)
- self.available_box.add(WifiNetworkSlot(ap, self.client))
diff --git a/modules/launcher/components/window_switcher.py b/modules/launcher/components/window_switcher.py
deleted file mode 100644
index c2ed906..0000000
--- a/modules/launcher/components/window_switcher.py
+++ /dev/null
@@ -1,312 +0,0 @@
-import subprocess
-import json
-from fabric.widgets.box import Box
-from fabric.widgets.label import Label
-from fabric.widgets.button import Button
-from fabric.widgets.entry import Entry
-from fabric.widgets.scrolledwindow import ScrolledWindow
-from fabric.widgets.image import Image
-from fabric.utils import idle_add, remove_handler, exec_shell_command_async
-from utils import IconResolver
-from gi.repository import GLib, Gdk, Gtk
-import utils.icons as icons
-import config.data as data
-
-class WindowSwitcher(Box):
- def __init__(self, **kwargs):
- super().__init__(
- name="window-switcher",
- visible=False,
- all_visible=False,
- **kwargs,
- )
-
- self.launcher = kwargs["launcher"]
- self.selected_index = -1 # Track the selected item index
- self.icon_resolver = IconResolver()
-
- self.viewport = Box(name="viewport", spacing=4, orientation="v")
- self.search_entry = Entry(
- name="search-entry",
- placeholder="Search Windows...",
- h_expand=True,
- notify_text=lambda entry, *_: self.handle_search(entry.get_text()),
- on_activate=lambda entry, *_: self.handle_search(entry.get_text()),
- on_key_press_event=self.on_entry_key_press,
- )
- self.search_entry.props.xalign = 0.5
- self.scrolled_window = ScrolledWindow(
- name="scrolled-window",
- spacing=10,
- min_content_size=(450, 305),
- max_content_size=(450, 305),
- child=self.viewport,
- )
-
- self.header_box = Box(
- name="header_box",
- spacing=10,
- orientation="h",
- children=[
- self.search_entry,
- ],
- )
-
- self.window_switcher_box = Box(
- name="window-switcher-box",
- spacing=10,
- h_expand=True,
- orientation="v",
- children=[
- self.header_box,
- self.scrolled_window,
- ],
- )
-
- self.add(self.window_switcher_box)
- self.show_all()
-
- def close_switcher(self):
- """Close the window switcher"""
- self.viewport.hide()
- self.selected_index = -1 # Reset selection
- self.launcher.close()
-
- def open_switcher(self):
- """Open the window switcher and refresh windows"""
- self.refresh_windows()
- self.search_entry.set_text("")
- self.search_entry.grab_focus()
- self.viewport.show()
-
- def refresh_windows(self):
- """Get Hyprland windows and populate the viewport"""
- remove_handler(self._arranger_handler) if hasattr(self, '_arranger_handler') else None
- self.viewport.children = []
- self.selected_index = -1 # Clear selection when viewport changes
-
- # Get Hyprland windows
- windows = self.get_hyprland_windows()
- if not windows:
- # Create a container box to better center the message
- container = Box(
- name="no-windows-container",
- orientation="v",
- h_align="center",
- v_align="center",
- h_expand=True,
- v_expand=True
- )
-
- # Show a message if no windows
- label = Label(
- name="no-windows",
- markup=icons.window,
- h_align="center",
- v_align="center",
- )
-
- container.add(label)
-
- self.viewport.add(container)
- return
-
- # Add window slots to viewport
- for window in windows:
- self.viewport.add(self.create_window_slot(window))
-
- def get_hyprland_windows(self):
- """Get list of windows from Hyprland"""
- try:
- # Get clients info from Hyprland
- result = subprocess.run(
- ["hyprctl", "clients", "-j"],
- capture_output=True,
- text=True,
- check=True
- )
- clients = json.loads(result.stdout)
-
- # Filter out special windows and sort by workspace
- windows = []
- for client in clients:
- if client.get("class") and not client.get("class").startswith("special"):
- windows.append(client)
-
- # Sort windows by workspace number
- windows.sort(key=lambda x: int(x.get("workspace", {}).get("id", 0)))
- return windows
-
- except Exception as e:
- print(f"Error getting Hyprland windows: {e}")
- return []
-
- def create_window_slot(self, window):
- """Create a button slot for a window"""
- # Get window title or class as fallback
- title = window.get("title", window.get("class", "Unknown"))
- workspace = window.get("workspace", {}).get("id", 0)
- window_class = window.get("class", "")
-
- # Get window icon using IconResolver
- icon = self.get_window_icon(window_class)
-
- # Create the button with window info
- button = Button(
- name="slot-button",
- child=Box(
- name="window-slot-box",
- orientation="h",
- spacing=10,
- children=[
- Image(
- name="window-icon",
- pixbuf=icon,
- h_align="start",
- ),
- Label(
- name="workspace-label",
- label=f"Workspace {workspace}",
- h_align="start",
- ),
- Label(
- name="window-title",
- label=title,
- ellipsization="end",
- v_align="center",
- h_align="center",
- ),
- ],
- ),
- on_clicked=lambda *_: self.focus_window(window.get("address")),
- on_key_press_event=lambda btn, event, addr=window.get("address"): self.on_slot_key_press(btn, event, addr),
- )
- return button
-
- def get_window_icon(self, window_class):
- """Get the appropriate icon for a window class using IconResolver"""
- try:
- # Try to get icon from IconResolver
- icon = self.icon_resolver.get_icon_pixbuf(window_class, size=24)
- if icon:
- return icon
- except Exception as e:
- print(f"Error resolving icon for {window_class}: {e}")
-
- # Fallback to default window icon if resolution fails
- return icons.window
-
- def focus_window(self, address):
- """Focus a window by its address"""
- try:
- subprocess.run(
- ["hyprctl", "dispatch", "focuswindow", f"address:{address}"],
- check=True
- )
- self.close_switcher()
- except Exception as e:
- print(f"Error focusing window: {e}")
-
- def handle_search(self, query):
- """Handle search input"""
- if not query:
- self.refresh_windows()
- return
-
- # Filter windows based on search query
- windows = self.get_hyprland_windows()
- filtered_windows = [
- window for window in windows
- if query.lower() in window.get("title", "").lower() or
- query.lower() in window.get("class", "").lower()
- ]
-
- # Update viewport with filtered results
- self.viewport.children = []
- self.selected_index = -1
-
- for window in filtered_windows:
- self.viewport.add(self.create_window_slot(window))
-
- def on_entry_key_press(self, widget, event):
- """Handle key presses on the search entry"""
- if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
- if self.selected_index != -1:
- children = self.viewport.get_children()
- if 0 <= self.selected_index < len(children):
- children[self.selected_index].clicked()
- return True
- elif event.keyval == Gdk.KEY_Down:
- self.move_selection(1)
- return True
- elif event.keyval == Gdk.KEY_Up:
- self.move_selection(-1)
- return True
- elif event.keyval == Gdk.KEY_Escape:
- self.close_switcher()
- return True
- return False
-
- def on_slot_key_press(self, button, event, address):
- """Handle key presses on window slots"""
- if event.keyval in (Gdk.KEY_Return, Gdk.KEY_KP_Enter):
- self.focus_window(address)
- return True
- elif event.keyval == Gdk.KEY_Escape:
- self.close_switcher()
- return True
- return False
-
- def move_selection(self, direction):
- """Move the selection up or down"""
- children = self.viewport.get_children()
- if not children:
- return
-
- new_index = self.selected_index + direction
- if 0 <= new_index < len(children):
- self.update_selection(new_index)
-
- def update_selection(self, new_index):
- """Update the selected item"""
- # Unselect current
- if self.selected_index != -1 and self.selected_index < len(self.viewport.get_children()):
- current_button = self.viewport.get_children()[self.selected_index]
- current_button.get_style_context().remove_class("selected")
-
- # Select new
- if new_index != -1 and new_index < len(self.viewport.get_children()):
- new_button = self.viewport.get_children()[new_index]
- new_button.get_style_context().add_class("selected")
- self.selected_index = new_index
- self.scroll_to_selected(new_button)
- else:
- self.selected_index = -1
-
- def scroll_to_selected(self, button):
- """Scroll to ensure the selected button is visible"""
- def scroll():
- adj = self.scrolled_window.get_vadjustment()
- alloc = button.get_allocation()
- if alloc.height == 0:
- return False # Retry if allocation isn't ready
-
- y = alloc.y
- height = alloc.height
- page_size = adj.get_page_size()
- current_value = adj.get_value()
-
- # Calculate visible boundaries
- visible_top = current_value
- visible_bottom = current_value + page_size
-
- if y < visible_top:
- # Item above viewport - align to top
- adj.set_value(y)
- elif y + height > visible_bottom:
- # Item below viewport - align to bottom
- new_value = y + height - page_size
- adj.set_value(new_value)
- # No action if already fully visible
- return False
- GLib.idle_add(scroll)
\ No newline at end of file
diff --git a/modules/launcher/launcher.py b/modules/launcher/launcher.py
deleted file mode 100644
index ed83c49..0000000
--- a/modules/launcher/launcher.py
+++ /dev/null
@@ -1,201 +0,0 @@
-from fabric.widgets.centerbox import CenterBox
-from fabric.widgets.stack import Stack
-from utils.wayland import WaylandWindow as Window
-from fabric.widgets.box import Box
-from modules.launcher.components import (
- AppLauncher,
- BluetoothConnections,
- EmojiPicker,
- PowerMenu,
- Sh,
- WallpaperSelector,
- WifiManager,
- Calendar,
- Dashboard,
- Toolbox,
- Kanban,
- TmuxManager,
- WindowSwitcher,
- NotificationCenter,
- ClipHistory,
-)
-
-
-class Launcher(Window):
- def __init__(self, **kwargs):
- super().__init__(
- layer="top",
- anchor="center",
- keyboard_mode="none",
- exclusivity="normal",
- visible=False,
- all_visible=False,
- **kwargs,
- )
-
- self.dashboard = Dashboard(launcher=self)
- self.wallpapers = WallpaperSelector(launcher=self)
- self.power = PowerMenu(launcher=self)
- self.emoji = EmojiPicker(launcher=self)
- self.bluetooth = BluetoothConnections(launcher=self)
- self.sh = Sh(launcher=self)
- self.wifi = WifiManager()
- self.calendar = Calendar()
- self.tools = Toolbox(launcher=self)
- self.kanban = Kanban()
- self.tmux = TmuxManager(launcher=self)
- self.window_switcher = WindowSwitcher(launcher=self)
- self.notification_center = NotificationCenter(launcher=self)
- self.cliphist = ClipHistory(launcher=self)
-
- # Wrap the dashboard in a Box container
- self.dashboard = Box(
- name="dashboard",
- orientation="h",
- spacing=10,
- children=[self.dashboard],
- )
- self.launcher = AppLauncher(launcher=self)
-
- self.stack = Stack(
- name="launcher-content",
- v_expand=True,
- h_expand=True,
- transition_type="crossfade",
- transition_duration=100,
- children=[
- self.launcher,
- self.wallpapers,
- self.power,
- self.emoji,
- self.cliphist,
- self.bluetooth,
- self.sh,
- self.wifi,
- self.calendar,
- self.tools,
- self.kanban,
- self.tmux,
- self.window_switcher,
- self.notification_center,
- ],
- )
-
- self.launcher_box = CenterBox(
- name="launcher",
- orientation="v",
- start_children=self.stack,
- end_children=self.dashboard,
- )
-
- self.add(self.launcher_box)
- self.show_all()
- self.hide()
- self.add_keybinding("Escape", lambda *_: self.close())
-
- def close(self):
- self.set_keyboard_mode("none")
- self.hide()
-
- for widget in [
- self.launcher,
- self.wallpapers,
- self.power,
- self.emoji,
- self.cliphist,
- self.bluetooth,
- self.sh,
- self.wifi,
- self.calendar,
- self.tools,
- self.kanban,
- self.tmux,
- self.window_switcher,
- self.notification_center,
- ]:
- if hasattr(widget, "viewport") and widget.viewport:
- widget.viewport.hide()
-
- for style in [
- "launcher",
- "wallpapers",
- "power",
- "emoji",
- "cliphist",
- "bluetooth",
- "sh",
- "wifi",
- "calendar",
- "tools",
- "kanban",
- "tmux",
- "window-switcher",
- "notification-center",
- ]:
- self.stack.remove_style_class(style)
-
- return True
-
- def open(self, widget):
- widgets = {
- "launcher": self.launcher,
- "wallpapers": self.wallpapers,
- "power": self.power,
- "emoji": self.emoji,
- "cliphist": self.cliphist,
- "bluetooth": self.bluetooth,
- "sh": self.sh,
- "wifi": self.wifi,
- "calendar": self.calendar,
- "tools": self.tools,
- "kanban": self.kanban,
- "tmux": self.tmux,
- "window-switcher": self.window_switcher,
- "notification-center": self.notification_center,
- }
- self.set_keyboard_mode("exclusive")
- self.show()
-
- for w in widgets.values():
- w.hide()
- self.dashboard.hide()
- for style in widgets.keys():
- self.stack.remove_style_class(style)
-
- if widget in widgets:
- self.stack.get_style_context().add_class(widget)
- self.stack.set_visible_child(widgets[widget])
- widgets[widget].show()
-
- if widget == "launcher":
- self.launcher.open_launcher()
- self.launcher.search_entry.set_text("")
- self.launcher.search_entry.grab_focus()
- self.dashboard.show()
-
- elif widget == "wallpapers":
- self.wallpapers.search_entry.set_text("")
- self.wallpapers.search_entry.grab_focus()
- self.wallpapers.viewport.show()
-
- elif widget == "tmux":
- self.tmux.open_manager()
- self.tmux.search_entry.set_text("")
- self.tmux.search_entry.grab_focus()
-
- elif widget == "emoji":
- self.emoji.open_picker()
- self.emoji.search_entry.set_text("")
- self.emoji.search_entry.grab_focus()
-
- elif widget == "cliphist":
- self.cliphist.open()
-
- elif widget == "sh":
- self.sh.open_sh()
-
- elif widget == "window-switcher":
- self.window_switcher.open_switcher()
-
- elif widget == "notification-center":
- self.notification_center.open_center()
diff --git a/modules/notification_popup.py b/modules/notification_popup.py
deleted file mode 100644
index 6d5bd7c..0000000
--- a/modules/notification_popup.py
+++ /dev/null
@@ -1,306 +0,0 @@
-import os
-from gi.repository import GdkPixbuf, GLib, Gdk
-from fabric.notifications import (
- Notification,
- NotificationAction,
- NotificationCloseReason,
-)
-from loguru import logger
-from fabric.widgets.box import Box
-from fabric.widgets.button import Button
-from fabric.widgets.image import Image
-from fabric.widgets.centerbox import CenterBox
-from fabric.widgets.label import Label
-from fabric.widgets.revealer import Revealer
-from utils.wayland import WaylandWindow as Window
-from utils import CustomImage
-import utils.icons as icons
-from services import notification_service
-
-
-class ActionButton(Button):
- def __init__(
- self, action: NotificationAction, index: int, total: int, notification_box
- ):
- super().__init__(
- name="action-button",
- h_expand=True,
- on_clicked=self.on_clicked,
- child=Label(name="button-label", label=action.label),
- )
- self.action = action
- self.notification_box = notification_box
- style_class = (
- "start-action"
- if index == 0
- else "end-action"
- if index == total - 1
- else "middle-action"
- )
- self.add_style_class(style_class)
- self.connect(
- "enter-notify-event", lambda *_: notification_box.hover_button(self)
- )
- self.connect(
- "leave-notify-event", lambda *_: notification_box.unhover_button(self)
- )
-
- def on_clicked(self, *_):
- self.action.invoke()
- self.action.parent.close("dismissed-by-user")
-
-
-class NotificationWidget(Box):
- def __init__(self, notification: Notification, timeout_ms=5000, **kwargs):
- super().__init__(
- name="notification-box",
- orientation="v",
- h_align="fill",
- h_expand=True,
- children=[
- self.create_content(notification),
- self.create_action_buttons(notification),
- ],
- )
- self.notification = notification
- self.timeout_ms = timeout_ms
- self._timeout_id = None
- self.start_timeout()
-
- def create_header(self, notification):
- app_icon = (
- Image(
- name="notification-icon",
- image_file=notification.app_icon[7:],
- size=24,
- )
- if "file://" in notification.app_icon
- else Image(
- name="notification-icon",
- icon_name="dialog-information-symbolic" or notification.app_icon,
- icon_size=24,
- )
- )
-
- return CenterBox(
- name="notification-title",
- start_children=[
- Box(
- spacing=4,
- children=[
- app_icon,
- Label(
- notification.app_name,
- name="notification-app-name",
- h_align="start",
- ),
- ],
- )
- ],
- end_children=[self.create_close_button()],
- )
-
- def create_content(self, notification):
- return Box(
- name="notification-content",
- spacing=8,
- children=[
- Box(
- name="notification-image",
- children=CustomImage(
- pixbuf=notification.image_pixbuf.scale_simple(
- 48, 48, GdkPixbuf.InterpType.BILINEAR
- )
- if notification.image_pixbuf
- else self.get_pixbuf(notification.app_icon, 48, 48)
- ),
- ),
- Box(
- name="notification-text",
- orientation="v",
- v_align="center",
- children=[
- Box(
- name="notification-summary-box",
- orientation="h",
- children=[
- Label(
- name="notification-summary",
- markup=notification.summary.replace("\n", " "),
- h_align="start",
- ellipsization="end",
- ),
- Label(
- name="notification-app-name",
- markup=" | " + notification.app_name,
- h_align="start",
- ellipsization="end",
- ),
- ],
- ),
- Label(
- markup=notification.body.replace("\n", " "),
- h_align="start",
- ellipsization="end",
- )
- if notification.body
- else Box(),
- ],
- ),
- Box(h_expand=True),
- Box(
- orientation="v",
- children=[
- self.create_close_button(),
- Box(v_expand=True),
- ],
- ),
- ],
- )
-
- def get_pixbuf(self, icon_path, width, height):
- if icon_path.startswith("file://"):
- icon_path = icon_path[7:]
-
- if not os.path.exists(icon_path):
- logger.warning(f"Icon path does not exist: {icon_path}")
- return None
-
- try:
- pixbuf = GdkPixbuf.Pixbuf.new_from_file(icon_path)
- return pixbuf.scale_simple(width, height, GdkPixbuf.InterpType.BILINEAR)
- except Exception as e:
- logger.error(f"Failed to load or scale icon: {e}")
- return None
-
- def create_action_buttons(self, notification):
- return Box(
- name="notification-action-buttons",
- spacing=4,
- h_expand=True,
- children=[
- ActionButton(action, i, len(notification.actions), self)
- for i, action in enumerate(notification.actions)
- ],
- )
-
- def create_close_button(self):
- close_button = Button(
- name="notif-close-button",
- child=Label(name="notif-close-label", markup=icons.cancel),
- on_clicked=lambda *_: self.notification.close("dismissed-by-user"),
- )
- close_button.connect(
- "enter-notify-event", lambda *_: self.hover_button(close_button)
- )
- close_button.connect(
- "leave-notify-event", lambda *_: self.unhover_button(close_button)
- )
- return close_button
-
- def start_timeout(self):
- self.stop_timeout()
- self._timeout_id = GLib.timeout_add(self.timeout_ms, self.close_notification)
-
- def stop_timeout(self):
- if self._timeout_id is not None:
- GLib.source_remove(self._timeout_id)
- self._timeout_id = None
-
- def close_notification(self):
- self.notification.close("expired")
- self.stop_timeout()
- return False
-
- def pause_timeout(self):
- self.stop_timeout()
-
- def resume_timeout(self):
- self.start_timeout()
-
- def destroy(self):
- self.stop_timeout()
- super().destroy()
-
- # @staticmethod
- def set_pointer_cursor(self, widget, cursor_name):
- window = widget.get_window()
- if window:
- cursor = Gdk.Cursor.new_from_name(widget.get_display(), cursor_name)
- window.set_cursor(cursor)
-
- def hover_button(self, button):
- self.pause_timeout()
- self.set_pointer_cursor(button, "hand2")
-
- def unhover_button(self, button):
- self.resume_timeout()
- self.set_pointer_cursor(button, "arrow")
-
-
-class NotificationRevealer(Revealer):
- def __init__(self, notification: Notification, **kwargs):
- self.notif_box = NotificationWidget(notification)
- self._notification = notification
- super().__init__(
- child=Box(
- children=[self.notif_box],
- ),
- transition_duration=250,
- transition_type="slide-down",
- )
-
- self.connect(
- "notify::child-revealed",
- lambda *_: self.destroy() if not self.get_child_revealed() else None,
- )
-
- self._notification.connect("closed", self.on_resolved)
-
- def on_resolved(
- self,
- notification: Notification,
- reason: NotificationCloseReason,
- ):
- self.set_reveal_child(False)
-
-
-class NotificationPopup(Window):
- def __init__(self):
- self._server = notification_service
- self.cache_notification_service = notification_service
- self.notifications = Box(
- v_expand=True,
- h_expand=True,
- style="margin: 1px 0px 1px 1px;",
- orientation="v",
- spacing=5,
- )
- self._server.connect("notification-added", self.on_new_notification)
-
- super().__init__(
- anchor="top right",
- child=self.notifications,
- layer="overlay",
- all_visible=True,
- visible=True,
- exclusive=False,
- )
-
- def on_new_notification(self, fabric_notif, id):
- notification: Notification = fabric_notif.get_notification_from_id(id)
- # Always cache the notification regardless of DND status
- self.cache_notification_service.cache_notification(notification)
-
- # Only show popup if DND is not enabled
- if not self.cache_notification_service.dont_disturb:
- new_box = NotificationRevealer(fabric_notif.get_notification_from_id(id))
- self.notifications.add(new_box)
- new_box.set_reveal_child(True)
- logger.info(f"[Notification] New notification from {notification.app_name}")
- else:
- logger.info(
- f"[Notification] DND enabled, notification from {
- notification.app_name
- } not shown"
- )
diff --git a/modules/osd.py b/modules/osd.py
deleted file mode 100644
index 4f4521f..0000000
--- a/modules/osd.py
+++ /dev/null
@@ -1,183 +0,0 @@
-import time
-from typing import ClassVar
-
-from fabric.audio import Audio
-from fabric.widgets.box import Box
-from fabric.widgets.scale import Scale, ScaleMark
-from utils.wayland import WaylandWindow as Window
-from fabric.utils import invoke_repeater
-from gi.repository import GObject
-from services.brightness import Brightness
-from fabric.widgets.label import Label
-from utils import Animator
-
-
-class AnimatedScale(Scale):
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
- self.animator = None # Lazily initialized
-
- def animate_value(self, value: float):
- if not self.animator:
- self.animator = Animator(
- bezier_curve=(0.34, 1.56, 0.64, 1.0),
- duration=0.8,
- min_value=self.min_value,
- max_value=self.max_value,
- tick_widget=self,
- notify_value=lambda p, *_: self.set_value(p.value),
- )
- self.animator.pause()
- self.animator.min_value = self.value
- self.animator.max_value = min(max(value, self.min_value), self.max_value)
- self.animator.play()
-
-
-class BaseOSDContainer(Box):
- def __init__(self, label_text, **kwargs):
- super().__init__(**kwargs, orientation="v", spacing=8, name="osd")
-
- self.header = Box(orientation="h")
- self.label = Label(
- label=label_text, name="osd-label", h_align="start", h_expand=True
- )
- self.value_label = Label(name="osd-label", h_align="end")
- self.header.add(self.label)
- self.header.add(self.value_label)
-
- self.scale = AnimatedScale(
- marks=(ScaleMark(value=i) for i in range(0, 100, 10)),
- value=70,
- min_value=0,
- max_value=100,
- increments=(1, 1),
- orientation="h",
- )
-
- self.add(self.header)
- self.add(self.scale)
-
- def update_value_label(self, value):
- self.value_label.set_label(str(round(value)))
-
-
-class BrightnessOSDContainer(BaseOSDContainer):
- def __init__(self, **kwargs):
- super().__init__(label_text="Brightness", **kwargs)
- self.brightness_service = Brightness()
- self.update_brightness()
-
- self.scale.connect("value-changed", lambda *_: self.update_brightness())
- self.brightness_service.connect("screen", self.on_brightness_changed)
-
- def update_brightness(self) -> None:
- current_brightness = self.brightness_service.screen_brightness
- if current_brightness != 0:
- normalized_brightness = self._normalize_brightness(current_brightness)
- self.scale.set_value(normalized_brightness)
- self.update_value_label(normalized_brightness)
-
- def on_brightness_changed(self, _sender: any, value: float, *_args) -> None:
- normalized_brightness = self._normalize_brightness(value)
- self.scale.animate_value(normalized_brightness)
- self.update_value_label(normalized_brightness)
-
- def _normalize_brightness(self, brightness: float) -> float:
- normalized = (brightness / self.brightness_service.max_screen) * 100
- return max(0, min(100, normalized)) # Ensure value stays within bounds
-
-
-class AudioOSDContainer(BaseOSDContainer):
- __gsignals__: ClassVar[dict] = {
- "volume-changed": (GObject.SIGNAL_RUN_FIRST, GObject.TYPE_NONE, ()),
- }
-
- def __init__(self, **kwargs):
- super().__init__(label_text="Volume", **kwargs)
- self.audio = Audio()
- self.sync_with_audio()
-
- self.scale.connect("value-changed", self.on_volume_changed)
- self.audio.connect("notify::speaker", self.on_audio_speaker_changed)
- self.audio.connect("speaker-changed", self.on_speaker_changed)
-
- def sync_with_audio(self):
- if self.audio.speaker:
- volume = round(self.audio.speaker.volume)
- self.scale.set_value(
- max(0, min(100, volume))
- ) # Ensure value stays within bounds
- self.update_value_label(volume)
-
- def on_volume_changed(self, *_):
- if self.audio.speaker:
- volume = self.scale.value
- if 0 <= volume <= 100:
- self.audio.speaker.set_volume(volume)
- self.update_value_label(volume)
- self.emit("volume-changed")
-
- def on_audio_speaker_changed(self, *_):
- if self.audio.speaker:
- self.audio.speaker.connect("changed", self.update_volume)
- self.update_volume()
-
- def on_speaker_changed(self, *_):
- self.update_volume()
-
- def update_volume(self, *_):
- if self.audio.speaker and not self.is_hovered():
- volume = round(self.audio.speaker.volume)
- self.scale.set_value(
- max(0, min(100, volume))
- ) # Ensure value stays within bounds
- self.update_value_label(volume)
-
-
-class OSD(Window):
- def __init__(self, **kwargs):
- self.audio_container = AudioOSDContainer()
- self.brightness_container = BrightnessOSDContainer()
-
- self.timeout = 1000
-
- self.controls_box = Box(
- orientation="h",
- spacing=20,
- children=[self.brightness_container, self.audio_container],
- )
-
- super().__init__(
- layer="overlay",
- anchor="top",
- child=self.controls_box,
- visible=False,
- pass_through=True,
- keyboard_mode="on-demand",
- **kwargs,
- )
-
- self.last_activity_time = time.time()
-
- self.audio_container.audio.connect("notify::speaker", self.show_controls)
- self.brightness_container.brightness_service.connect(
- "screen", self.show_controls
- )
- self.audio_container.connect("volume-changed", self.show_controls)
-
- invoke_repeater(100, self.check_inactivity, initial_call=True)
-
- def show_controls(self, *_):
- self.set_visible(True)
- self.reset_inactivity_timer()
-
- def start_hide_timer(self):
- self.set_visible(False)
-
- def reset_inactivity_timer(self):
- self.last_activity_time = time.time()
-
- def check_inactivity(self):
- if time.time() - self.last_activity_time >= (self.timeout / 1000):
- self.start_hide_timer()
- return True
diff --git a/services/__init__.py b/services/__init__.py
deleted file mode 100644
index cdba666..0000000
--- a/services/__init__.py
+++ /dev/null
@@ -1,10 +0,0 @@
-from fabric.audio import Audio
-from .brightness import Brightness
-from .network import NetworkClient
-from .notification import *
-from .powerprofile import *
-
-network_client = NetworkClient()
-brightness = Brightness()
-audio = Audio()
-notification_service = CustomNotifications()
diff --git a/services/brightness.py b/services/brightness.py
deleted file mode 100644
index 49e7040..0000000
--- a/services/brightness.py
+++ /dev/null
@@ -1,115 +0,0 @@
-import os
-import subprocess
-
-from fabric.core.service import Property, Service, Signal
-from fabric.utils import exec_shell_command, monitor_file
-from gi.repository import GLib
-from loguru import logger # Import loguru logger
-
-
-# Helper function to execute brightnessctl asynchronously
-def exec_brightnessctl_async(args: str):
- # Executes brightnessctl command asynchronously, ensuring no resource leaks.
- try:
- # Use subprocess.Popen to run the command without blocking
- process = subprocess.Popen(
- f"brightnessctl {args}",
- shell=True,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
-
- # Wait for the process to complete
- stdout, stderr = process.communicate()
-
- if process.returncode != 0:
- logger.error(f"Error executing brightnessctl: {stderr.decode().strip()}")
- else:
- logger.debug(
- f"brightnessctl output: {stdout.decode().strip()}"
- ) # Optional: Log the output
- except Exception as e:
- logger.exception(f"Exception in exec_brightnessctl_async: {e}")
-
-
-# Discover screen backlight device
-try:
- screen_device = str(exec_shell_command("ls -w1 /sys/class/backlight")).split("\n")[
- 0
- ]
-except IndexError:
- screen_device = None
-
-
-class NoBrightnessError(ImportError):
- # Raised when brightness-related dependencies are missing.
- def __init__(self, *args):
- super().__init__(
- "Brightness control not available or missing dependencies", *args
- )
-
-
-class Brightness(Service):
- # Service to manage screen brightness levels.
-
- @Signal
- def screen(self, value: int) -> None:
- """Signal emitted when screen brightness changes."""
- pass # Implement as needed for your application
-
- def __init__(self, **kwargs):
- super().__init__(**kwargs)
-
- if not screen_device:
- raise NoBrightnessError()
-
- # Path for screen backlight control
- self.screen_backlight_path = f"/sys/class/backlight/{screen_device}"
-
- # Initialize maximum brightness level
- self.max_screen = self._read_max_brightness(self.screen_backlight_path)
-
- # Monitor screen brightness file
- self.screen_monitor = monitor_file(f"{self.screen_backlight_path}/brightness")
- self.screen_monitor.connect(
- "changed",
- lambda _, file, *args: self.emit(
- "screen", round(int(file.load_bytes()[0].get_data()))
- ),
- )
-
- # Log the initialization of the service
- logger.info(f"Brightness service initialized for device: {screen_device}")
-
- def _read_max_brightness(self, path: str) -> int:
- # Reads the maximum brightness value from the specified path.
- max_brightness_path = os.path.join(path, "max_brightness")
- if os.path.exists(max_brightness_path):
- with open(max_brightness_path) as f:
- return int(f.read().strip())
- return -1 # Return -1 if file doesn't exist, indicating an error.
-
- @Property(int, "read-write")
- def screen_brightness(self) -> int:
- # Property to get or set the screen brightness.
- brightness_path = os.path.join(self.screen_backlight_path, "brightness")
- if os.path.exists(brightness_path):
- with open(brightness_path) as f:
- return int(f.read().strip())
- logger.warning(f"Brightness file does not exist: {brightness_path}")
- return -1 # Return -1 if file doesn't exist, indicating error.
-
- @screen_brightness.setter
- def screen_brightness(self, value: int):
- # Setter for screen brightness property.
- if not (0 <= value <= self.max_screen):
- value = max(0, min(value, self.max_screen))
-
- try:
- exec_brightnessctl_async(f"--device '{screen_device}' set {value}")
- self.emit("screen", int((value / self.max_screen) * 100))
- logger.info(f"Set screen brightness to {value} (out of {self.max_screen})")
- except GLib.Error as e:
- logger.error(f"Error setting screen brightness: {e.message}")
- except Exception as e:
- logger.exception(f"Unexpected error setting screen brightness: {e}")
diff --git a/services/network.py b/services/network.py
deleted file mode 100644
index 04ebcae..0000000
--- a/services/network.py
+++ /dev/null
@@ -1,334 +0,0 @@
-from typing import Any, List, Literal
-
-import gi
-from fabric.core.service import Property, Service, Signal
-from fabric.utils import bulk_connect, exec_shell_command_async
-from gi.repository import Gio
-from loguru import logger
-
-try:
- gi.require_version("NM", "1.0")
- from gi.repository import NM
-except ValueError:
- logger.error("Failed to start network manager")
-
-
-class Wifi(Service):
- """A service to manage wifi devices"""
-
- @Signal
- def changed(self) -> None: ...
-
- @Signal
- def enabled(self) -> bool: ...
-
- @Signal
- def scanning(self, is_scanning: bool) -> None: ...
-
- def __init__(self, client: NM.Client, device: NM.DeviceWifi, **kwargs):
- self._client: NM.Client = client
- self._device: NM.DeviceWifi = device
- self._ap: NM.AccessPoint | None = None
- self._ap_signal: int | None = None
- super().__init__(**kwargs)
-
- self._client.connect(
- "notify::wireless-enabled",
- lambda *args: self.notifier("enabled", args),
- )
- if self._device:
- bulk_connect(
- self._device,
- {
- "notify::active-access-point": lambda *args: self._activate_ap(),
- "access-point-added": lambda *args: self.emit("changed"),
- "access-point-removed": lambda *args: self.emit("changed"),
- "state-changed": lambda *args: self.ap_update(),
- },
- )
- self._activate_ap()
-
- def ap_update(self):
- self.emit("changed")
- for sn in [
- "enabled",
- "internet",
- "strength",
- "frequency",
- "access-points",
- "ssid",
- "state",
- "icon-name",
- ]:
- self.notify(sn)
-
- def _activate_ap(self):
- if self._ap:
- self._ap.disconnect(self._ap_signal)
- self._ap = self._device.get_active_access_point()
- if not self._ap:
- return
-
- self._ap_signal = self._ap.connect(
- "notify::strength", lambda *args: self.ap_update()
- ) # type: ignore
-
- def toggle_wifi(self):
- self._client.wireless_set_enabled(not self._client.wireless_get_enabled())
-
- # def set_active_ap(self, ap):
- # self._device.access
-
- def scan(self):
- """Start scanning for WiFi networks and emit scanning signal"""
- if self._device:
- self.emit("scanning", True) # Emit signal that scanning has started
- self._device.request_scan_async(
- None,
- lambda device, result: [
- device.request_scan_finish(result),
- self.emit(
- "scanning", False
- ), # Emit signal that scanning has stopped
- ],
- )
-
- def notifier(self, name: str, *args):
- self.notify(name)
- self.emit("changed")
- return
-
- @Property(bool, "read-write", default_value=False)
- def enabled(self) -> bool: # type: ignore
- return bool(self._client.wireless_get_enabled())
-
- @enabled.setter
- def enabled(self, value: bool):
- self._client.wireless_set_enabled(value)
-
- @Property(int, "readable")
- def strength(self):
- return self._ap.get_strength() if self._ap else -1
-
- @Property(str, "readable")
- def icon_name(self):
- if not self._ap:
- return "network-wireless-disabled-symbolic"
-
- if self.internet == "activated":
- return {
- 80: "network-wireless-signal-excellent-symbolic",
- 60: "network-wireless-signal-good-symbolic",
- 40: "network-wireless-signal-ok-symbolic",
- 20: "network-wireless-signal-weak-symbolic",
- 00: "network-wireless-signal-none-symbolic",
- }.get(
- min(80, 20 * round(self._ap.get_strength() / 20)),
- "network-wireless-no-route-symbolic",
- )
- if self.internet == "activating":
- return "network-wireless-acquiring-symbolic"
-
- return "network-wireless-offline-symbolic"
-
- @Property(int, "readable")
- def frequency(self):
- return self._ap.get_frequency() if self._ap else -1
-
- @Property(int, "readable")
- def internet(self):
- return {
- NM.ActiveConnectionState.ACTIVATED: "activated",
- NM.ActiveConnectionState.ACTIVATING: "activating",
- NM.ActiveConnectionState.DEACTIVATING: "deactivating",
- NM.ActiveConnectionState.DEACTIVATED: "deactivated",
- }.get(
- self._device.get_active_connection().get_state(),
- "unknown",
- )
-
- @Property(object, "readable")
- def access_points(self) -> List[object]:
- points: list[NM.AccessPoint] = self._device.get_access_points()
-
- def make_ap_dict(ap: NM.AccessPoint):
- return {
- "bssid": ap.get_bssid(),
- # "address": ap.get_
- "last_seen": ap.get_last_seen(),
- "ssid": NM.utils_ssid_to_utf8(ap.get_ssid().get_data())
- if ap.get_ssid()
- else "Unknown",
- "active-ap": self._ap,
- "strength": ap.get_strength(),
- "frequency": ap.get_frequency(),
- "icon-name": {
- 80: "network-wireless-signal-excellent-symbolic",
- 60: "network-wireless-signal-good-symbolic",
- 40: "network-wireless-signal-ok-symbolic",
- 20: "network-wireless-signal-weak-symbolic",
- 00: "network-wireless-signal-none-symbolic",
- }.get(
- min(80, 20 * round(ap.get_strength() / 20)),
- "network-wireless-no-route-symbolic",
- ),
- }
-
- return list(map(make_ap_dict, points))
-
- @Property(str, "readable")
- def ssid(self):
- if not self._ap:
- return "Disconnected"
- ssid = self._ap.get_ssid().get_data()
- return NM.utils_ssid_to_utf8(ssid) if ssid else "Unknown"
-
- @Property(int, "readable")
- def state(self):
- return {
- NM.DeviceState.UNMANAGED: "unmanaged",
- NM.DeviceState.UNAVAILABLE: "unavailable",
- NM.DeviceState.DISCONNECTED: "disconnected",
- NM.DeviceState.PREPARE: "prepare",
- NM.DeviceState.CONFIG: "config",
- NM.DeviceState.NEED_AUTH: "need_auth",
- NM.DeviceState.IP_CONFIG: "ip_config",
- NM.DeviceState.IP_CHECK: "ip_check",
- NM.DeviceState.SECONDARIES: "secondaries",
- NM.DeviceState.ACTIVATED: "activated",
- NM.DeviceState.DEACTIVATING: "deactivating",
- NM.DeviceState.FAILED: "failed",
- }.get(self._device.get_state(), "unknown")
-
-
-class Ethernet(Service):
- """A service to manage ethernet devices"""
-
- @Signal
- def changed(self) -> None: ...
-
- @Signal
- def enabled(self) -> bool: ...
-
- @Property(int, "readable")
- def speed(self) -> int:
- return self._device.get_speed()
-
- @Property(str, "readable")
- def internet(self) -> str:
- return {
- NM.ActiveConnectionState.ACTIVATED: "activated",
- NM.ActiveConnectionState.ACTIVATING: "activating",
- NM.ActiveConnectionState.DEACTIVATING: "deactivating",
- NM.ActiveConnectionState.DEACTIVATED: "deactivated",
- }.get(
- self._device.get_active_connection().get_state(),
- "disconnected",
- )
-
- @Property(str, "readable")
- def icon_name(self) -> str:
- network = self.internet
- if network == "activated":
- return "network-wired-symbolic"
-
- elif network == "activating":
- return "network-wired-acquiring-symbolic"
-
- elif self._device.get_connectivity != NM.ConnectivityState.FULL:
- return "network-wired-no-route-symbolic"
-
- return "network-wired-disconnected-symbolic"
-
- def __init__(self, client: NM.Client, device: NM.DeviceEthernet, **kwargs) -> None:
- super().__init__(**kwargs)
- self._client: NM.Client = client
- self._device: NM.DeviceEthernet = device
-
- for names in (
- "active-connection",
- "icon-name",
- "internet",
- "speed",
- "state",
- ):
- self._device.connect(f"notify::{names}", lambda *_: self.notifier(names))
-
- self._device.connect("notify::speed", lambda *_: print(_))
-
- def notifier(self, names):
- self.notify(names)
- self.emit("changed")
-
-
-class NetworkClient(Service):
- """A service to manage network devices"""
-
- @Signal
- def device_ready(self) -> None: ...
-
- def __init__(self, **kwargs):
- self._client: NM.Client | None = None
- self.wifi_device: Wifi | None = None
- self.ethernet_device: Ethernet | None = None
- super().__init__(**kwargs)
- NM.Client.new_async(
- cancellable=None,
- callback=self._init_network_client,
- **kwargs,
- )
-
- def _init_network_client(self, client: NM.Client, task: Gio.Task, **kwargs):
- self._client = client
- wifi_device: NM.DeviceWifi | None = self._get_device(NM.DeviceType.WIFI) # type: ignore
- ethernet_device: NM.DeviceEthernet | None = self._get_device(
- NM.DeviceType.ETHERNET
- )
-
- if wifi_device:
- self.wifi_device = Wifi(self._client, wifi_device)
- self.emit("device-ready")
-
- if ethernet_device:
- self.ethernet_device = Ethernet(client=self._client, device=ethernet_device)
- self.emit("device-ready")
-
- self.notify("primary-device")
-
- def _get_device(self, device_type) -> Any:
- devices: List[NM.Device] = self._client.get_devices() # type: ignore
- return next(
- (
- x
- for x in devices
- if x.get_device_type() == device_type
- and x.get_active_connection() is not None
- ),
- None,
- )
-
- def _get_primary_device(self) -> Literal["wifi", "wired"] | None:
- if not self._client:
- return None
-
- if self._client.get_primary_connection() is None:
- return "wifi"
- return (
- "wifi"
- if "wireless"
- in str(self._client.get_primary_connection().get_connection_type())
- else "wired"
- if "ethernet"
- in str(self._client.get_primary_connection().get_connection_type())
- else None
- )
-
- def connect_wifi_bssid(self, bssid):
- # We are using nmcli here, idk im lazy
- exec_shell_command_async(
- f"nmcli device wifi connect {bssid}", lambda *args: print(args)
- )
-
- @Property(str, "readable")
- def primary_device(self) -> Literal["wifi", "wired"] | None:
- return self._get_primary_device()
diff --git a/services/notification.py b/services/notification.py
deleted file mode 100644
index 4da918b..0000000
--- a/services/notification.py
+++ /dev/null
@@ -1,226 +0,0 @@
-import json
-import os
-from typing import List
-
-from fabric import Signal
-from fabric.notifications import Notification, Notifications
-from loguru import logger
-import gi
-gi.require_version('GLib', '2.0')
-from gi.repository import GLib
-
-import config.data as data
-
-NOTIFICATION_CACHE_FILE = f"{data.CACHE_DIR}/notifications.json"
-
-
-class CustomNotifications(Notifications):
- """A service to manage the notifications."""
-
- @Signal
- def clear_all(self, value: bool) -> None:
- """Signal emitted when notifications are emptied."""
- # Implement as needed for your application
-
- @Signal
- def notification_count(self, value: int) -> None:
- """Signal emitted when a new notification is added."""
- # Implement as needed for your application
-
- @Signal
- def dnd(self, value: bool) -> None:
- """Signal emitted when dnd is toggled."""
- # Implement as needed for your application
-
- @property
- def count(self) -> int:
- """Return the count of notifications."""
- return len(self.all_notifications)
-
- @property
- def dont_disturb(self) -> bool:
- """Return the pause status."""
- return self._dont_disturb
-
- @dont_disturb.setter
- def dont_disturb(self, value: bool):
- """Set the pause status."""
- self._dont_disturb = value
- self.emit("dnd", value)
-
- def __init__(self, *args, **kwargs):
- super().__init__(*args, **kwargs)
- self.all_notifications = []
- self._count = 0 # Will be updated to highest ID when loading
- self.deserialized_notifications = []
- self._dont_disturb = False
- self._load_notifications()
-
- def _load_notifications(self):
- """Read notifications from the cache file."""
- if os.path.exists(NOTIFICATION_CACHE_FILE):
- try:
- with open(NOTIFICATION_CACHE_FILE, "r") as file:
- notifications = json.load(file)
-
- def validate_with_id(notif):
- """Helper to validate and return ID if valid."""
- try:
- self._deserialize_notification(notif)
- return (True, notif, notif.get("id", 0))
- except Exception as e:
- msg = f"[Notification] Invalid: {str(e)[:50]}"
- logger.error(msg)
- return (False, None, 0)
-
- # Validate all notifications at once
- results = [validate_with_id(n) for n in notifications]
-
- # Process results and find highest ID
- valid_notifications = []
- highest_id = self._count # Start with current count
- for is_valid, notif, notif_id in results:
- if is_valid:
- valid_notifications.append(notif)
- highest_id = max(highest_id, notif_id)
-
- self.all_notifications = valid_notifications
- self._count = highest_id # Update to highest ID seen
- self._write_notifications(self.all_notifications)
-
- except (json.JSONDecodeError, KeyError, ValueError, IndexError) as e:
- logger.error(f"[Notification] {e}")
- self.all_notifications = []
- self._count = 0
-
- def remove_notification(self, id: int):
- """Remove the notification of given id."""
- item = next((p for p in self.all_notifications if p["id"] == id), None)
- if item:
- self.all_notifications.remove(item)
- self._write_notifications(self.all_notifications)
- self.emit("notification_count", len(self.all_notifications))
-
- # Emit clear_all signal if there are no notifications left
- if len(self.all_notifications) == 0:
- self.emit("clear_all", True)
-
- def cache_notification(self, data: Notification):
- """Cache the notification without widget_config and max_count."""
-
- # First clean up any invalid notifications
- self._cleanup_invalid_notifications()
-
- # Create the new notification
- self._count += 1
- new_id = self._count
- timestamp = GLib.get_real_time() / 1000000 # Convert microseconds to seconds
- serialized_data = data.serialize()
- serialized_data.update({
- "id": new_id,
- "app_name": data.app_name,
- "timestamp": timestamp
- })
-
- # Append the new notification
- self.all_notifications.append(serialized_data)
-
- self._write_notifications(self.all_notifications)
- self.emit("notification_count", len(self.all_notifications))
-
- def _cleanup_invalid_notifications(self):
- """Remove any invalid notifications."""
-
- def validate_with_id(notif):
- """Helper to validate and return result with ID."""
- try:
- self._deserialize_notification(notif)
- return (True, notif, None)
- except Exception as e:
- msg = f"[Notification] Removing invalid: {str(e)[:50]}"
- logger.debug(msg)
- return (False, None, notif.get("id", 0))
-
- # Validate all notifications at once
- results = [validate_with_id(n) for n in self.all_notifications]
-
- # Process results
- valid_notifications = []
- invalid_count = 0
- for is_valid, notif, invalid_id in results:
- if is_valid:
- valid_notifications.append(notif)
- else:
- invalid_count += 1
- self.emit("notification-closed", invalid_id, "dismissed-by-limit")
-
- if invalid_count > 0:
- self.all_notifications = valid_notifications
- self._write_notifications(self.all_notifications)
- self.emit("notification_count", len(self.all_notifications))
-
- def _deserialize_notification(self, notification):
- """Deserialize a notification."""
- try:
- notif = Notification.deserialize(notification)
- # Store the timestamp in the notification object
- notif.timestamp = notification.get("timestamp", GLib.get_real_time() / 1000000)
- return notif
- except Exception as e:
- logger.error(
- f"[Notification] Deserialize failed for: {notification}, Error: {e}"
- )
- raise
-
- def clear_all_notifications(self):
- """Empty the notifications."""
- logger.info("[Notification] Clearing all notifications")
- # Clear notifications but preserve the highest ID we've seen
- highest_id = self._count
- self.all_notifications = []
- self._write_notifications(self.all_notifications)
- self.emit("notification_count", 0)
- self.emit("clear_all", True)
- # Restore the ID counter so new notifications get unique IDs
- self._count = highest_id
-
- def _write_notifications(self, data):
- """Write the notifications to the cache file."""
- try:
- with open(NOTIFICATION_CACHE_FILE, "w") as f:
- json.dump(data, f, indent=4, ensure_ascii=False)
- logger.info("[Notification] Notifications written successfully.")
- except Exception as e:
- logger.error(f"[Notification] Failed to write notifications: {e}")
-
- def get_deserialized(self) -> List[Notification]:
- """Return the notifications."""
-
- def deserialize_with_id(notif):
- """Helper to deserialize and return result with ID."""
- try:
- return (self._deserialize_notification(notif), None)
- except Exception as e:
- msg = f"[Notification] Deserialize failed: {str(e)[:50]}"
- logger.error(msg)
- return (None, notif.get("id"))
-
- # Process all notifications at once
- results = [
- deserialize_with_id(notification) for notification in self.all_notifications
- ]
-
- # Split into successful and failed
- deserialized = []
- invalid_ids = []
- for result, error_id in results:
- if result is not None:
- deserialized.append(result)
- elif error_id is not None:
- invalid_ids.append(error_id)
-
- # Clean up invalid notifications
- for invalid_id in invalid_ids:
- self.remove_notification(invalid_id)
-
- return deserialized
diff --git a/services/powerprofile.py b/services/powerprofile.py
deleted file mode 100644
index cccf03e..0000000
--- a/services/powerprofile.py
+++ /dev/null
@@ -1,88 +0,0 @@
-import dbus
-from dbus.mainloop.glib import DBusGMainLoop
-from fabric import Service, Signal
-from loguru import logger
-
-
-class PowerProfiles(Service):
- """Service to interact with the PowerProfiles service."""
-
- @Signal
- def profile(self, value: str) -> None:
- """Signal emitted when profile changes."""
-
- instance = None
-
- @staticmethod
- def get_default():
- if PowerProfiles.instance is None:
- PowerProfiles.instance = PowerProfiles()
-
- return PowerProfiles.instance
-
- def __init__(
- self,
- **kwargs,
- ):
- super().__init__(
- **kwargs,
- )
-
- self.bus_name = "net.hadess.PowerProfiles"
- self.object_path = "/net/hadess/PowerProfiles"
-
- self.power_profiles = {
- "power-saver": {
- "name": "Power Saver",
- "icon_name": "power-profile-power-saver-symbolic",
- },
- "balanced": {
- "name": "Balanced",
- "icon_name": "power-profile-balanced-symbolic",
- },
- "performance": {
- "name": "Performance",
- "icon_name": "power-profile-performance-symbolic",
- },
- }
-
- # Set up the dbus main loop
- DBusGMainLoop(set_as_default=True)
-
- self.bus = dbus.SystemBus()
-
- self.power_profiles_obj = self.bus.get_object(self.bus_name, self.object_path)
-
- self.iface = dbus.Interface(
- self.power_profiles_obj, "org.freedesktop.DBus.Properties"
- )
-
- # Connect the 'g-properties-changed' signal to the handler
- self.iface.connect_to_signal("PropertiesChanged", self.handle_property_change)
-
- def get_current_profile(self):
- try:
- return self.iface.Get("net.hadess.PowerProfiles", "ActiveProfile").strip()
-
- except dbus.DBusException as e:
- logger.error(f"[PowerProfile] Error retrieving current power profile: {e}")
- return "balanced"
-
- def set_power_profile(self, profile):
- try:
- self.iface.Set(self.bus_name, "ActiveProfile", dbus.String(profile))
- logger.info(f"[PowerProfile] Power profile set to {profile}")
- except dbus.DBusException as e:
- logger.error(
- f"[PowerProfile] Could not change power level to {profile}: {e}"
- )
-
- # Function to handle properties change signals
- def handle_property_change(self, proxy, changed, invalidated):
- # Print the changed ActiveProfile
- if "ActiveProfile" in changed:
- logger.info(f"Profile changed: {changed['ActiveProfile']}")
- self.emit("profile", changed["ActiveProfile"])
-
- def get_profile_icon(self, profile):
- return self.power_profiles.get(profile, "balanced").get("icon_name")
diff --git a/services/screen_record.py b/services/screen_record.py
deleted file mode 100644
index 7fbf6d5..0000000
--- a/services/screen_record.py
+++ /dev/null
@@ -1,142 +0,0 @@
-import datetime
-import subprocess
-
-from fabric.core.service import Property, Service, Signal
-from fabric.utils import exec_shell_command, exec_shell_command_async
-from gi.repository import Gio, GLib
-from loguru import logger
-
-
-class ScreenRecorder(Service):
- @Signal
- def recording(self, value: bool) -> None: ...
-
- def __init__(self, **kwargs):
- self.screenshot_path = GLib.get_home_dir() + "/Pictures/Screenshots/"
- self.screenrecord_path = GLib.get_home_dir() + "/Videos/Screencasting/"
-
- super().__init__(**kwargs)
-
- def screenshot(self, fullscreen=False, save_copy=True):
- time = datetime.datetime.today().strftime("%Y-%m-%d_%H-%M-%S")
- file_path = self.screenshot_path + str(time) + ".png"
- command = (
- ["grimblast", "copysave", "screen", file_path]
- if save_copy
- else ["grimblast", "copyscreen"]
- )
- if not fullscreen:
- command[2] = "area"
- try:
- subprocess.run(command, check=True)
- self.send_screenshot_notification(
- file_path=file_path if file_path else None,
- )
- except Exception as e:
- logger.error(f"[SCREENSHOT] Failed to run command: {command}")
-
- def screencast_start(self, fullscreen=False):
- if self.is_recording:
- logger.error(
- "[SCREENRECORD] Another instance of wf-recorder is already running"
- )
- return
- time = datetime.datetime.today().strftime("%Y-%m-%d_%H-%M-%S")
- file_path = self.screenrecord_path + str(time) + ".mp4"
- self._current_screencast_path = file_path
- area = "" if fullscreen else f"-g '{exec_shell_command('slurp')}'"
- command = f"wf-recorder --file={file_path} --pixel-format yuv420p {area}"
- exec_shell_command_async(command)
- self.emit("recording", True)
-
- def screencast_stop(self):
- exec_shell_command_async("killall -INT wf-recorder")
- self.emit("recording", False)
- self.send_screencast_notification(self._current_screencast_path)
-
- def send_screencast_notification(self, file_path):
- # TODO: Generate a thumbnail
- # exec_shell_command_async(f"totem-video-thumbnailer {file_path} {FABRIC_CACHE}")
-
- cmd = ["notify-send"]
- cmd.extend(
- [
- "-A",
- "files=Show in Files",
- "-A",
- "view=View",
- "-i",
- "camera-video-symbolic",
- "-a",
- "Fabric Screenshot Utility",
- "Screencast Saved",
- f"Saved Screencast at {file_path}",
- ]
- )
-
- proc: Gio.Subprocess = Gio.Subprocess.new(cmd, Gio.SubprocessFlags.STDOUT_PIPE)
-
- def do_callback(process: Gio.Subprocess, task: Gio.Task):
- try:
- _, stdout, stderr = process.communicate_utf8_finish(task)
- except Exception:
- logger.error(
- f"[SCREENCAST] Failed read notification action with error {stderr}"
- )
- return
-
- match stdout.strip("\n"):
- case "files":
- exec_shell_command_async(f"xdg-open {self.screenrecord_path}")
- case "view":
- exec_shell_command_async(f"xdg-open {file_path}")
-
- proc.communicate_utf8_async(None, None, do_callback)
-
- def send_screenshot_notification(self, file_path=None):
- cmd = ["notify-send"]
- cmd.extend(
- [
- "-A",
- "files=Show in Files",
- "-A",
- "view=View",
- "-A",
- "edit=Edit",
- "-i",
- "camera-photo-symbolic",
- "-a",
- "Fabric Screenshot Utility",
- "-h",
- f"STRING:image-path:{file_path}",
- "Screenshot Saved",
- f"Saved Screenshot at {file_path}",
- ]
- if file_path
- else ["Screenshot Sent to Clipboard"]
- )
-
- proc: Gio.Subprocess = Gio.Subprocess.new(cmd, Gio.SubprocessFlags.STDOUT_PIPE)
-
- def do_callback(process: Gio.Subprocess, task: Gio.Task):
- try:
- _, stdout, stderr = process.communicate_utf8_finish(task)
- except Exception:
- logger.error(
- f"[SCREENSHOT] Failed read notification action with error {stderr}"
- )
- return
-
- match stdout.strip("\n"):
- case "files":
- exec_shell_command_async(f"xdg-open {self.screenshot_path}")
- case "view":
- exec_shell_command_async(f"xdg-open {file_path}")
- case "edit":
- exec_shell_command_async(f"swappy -f {file_path}")
-
- proc.communicate_utf8_async(None, None, do_callback)
-
- @Property(bool, "readable", default_value=False)
- def is_recording(self):
- return False if len(exec_shell_command("pidof wf-recorder")) == 0 else True
diff --git a/styles/bar.css b/styles/bar.css
deleted file mode 100644
index 6f4993c..0000000
--- a/styles/bar.css
+++ /dev/null
@@ -1,214 +0,0 @@
-#bar {
- padding: 0px 5px 0px 5px;
- margin: 1px;
- transition: all 0.5s cubic-bezier(0.5, 0.25, 0, 1);
- background-color: var(--surface);
- /* Bar background */
-}
-
-#start-container,
-#center-container,
-#end-container {
- padding-top: 2px;
- padding-bottom: 2px;
-}
-
-#bar-corner * {
- background-color: var(--surface);
-}
-
-/**
- * โโ Workspace โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
-#workspaces {
- font-style: normal;
- background-color: var(--surface-container);
- padding: 4px;
- margin-top: 1px;
- margin-bottom: 1px;
- border-radius: 8px;
-}
-
-/**
- * โโ Update โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
-#update {
- background-color: var(--surface-container);
- margin-top: 1px;
- margin-bottom: 1px;
- border-radius: 8px;
- padding: 1px;
- min-width: 24px;
- min-height: 24px;
- transition: background-color 0.1s ease;
-}
-
-#update:hover {
- background-color: var(--surface-bright);
-}
-
-#update:active {
- color: var(--shadow);
- background-color: var(--primary);
-}
-
-#update-icon {
- color: var(--primary);
- font-size: 20px;
- padding: 1px;
- border-radius: 11px;
- transition: all 0.1s ease;
-}
-
-#updated-icon {
- color: var(--primary);
- font-size: 20px;
- padding: 4px;
- border-radius: 8px;
- transition: all 0.1s ease;
-}
-
-#update-label {
- color: var(--primary);
- font-size: 10px;
- font-weight: bold;
- padding: 0px;
- margin-right: 4px;
- border-radius: 8px;
- transition: all 0.1s ease;
-}
-
-/**
- * โโ Clock โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
-#datetime {
- background-color: var(--surface-container);
- margin-top: 1px;
- margin-bottom: 1px;
- padding: 4px;
- border-radius: 8px;
-}
-
-#datetime:hover {
- background-color: var(--surface-bright);
-}
-
-/**
- * โโ Tray โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
-#tray {
- background-color: var(--surface-container);
- margin-top: 1px;
- margin-bottom: 1px;
- padding: 4px;
- border-radius: 8px;
-}
-
-#tray > button {
- margin: 0px;
- padding: 0px;
-}
-
-menu {
- border: solid 1px var(--outline-variant);
- border-radius: 16px;
- background-color: var(--surface);
- padding: 6px;
-}
-
-menu > menuitem {
- border-radius: 10px;
- padding: 6px 10px;
-}
-
-menu > menuitem:hover {
- background-color: var(--surface-bright);
-}
-
-menu > menuitem:hover > label {
- color: var(--on-surface);
-}
-
-/**
- * โโ Logo โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
-#button-bar {
- background-color: var(--surface-container);
- margin-top: 1px;
- padding: 4px;
- border-radius: 8px;
- margin-bottom: 1px;
- transition: all 0.1s ease;
-}
-
-#button-bar-label {
- color: var(--primary);
- font-size: 24px;
- border-radius: 8px;
- transition: all 0.1s ease;
-}
-
-#button-bar:hover #button-bar-label {
- background-color: var(--surface_bright);
-}
-
-#button-bar:active #button-bar-label {
- color: var(--shadow);
- background-color: var(--primary);
-}
-
-/**
- * โโ System Indicator โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
-#system-indicator {
- background-color: var(--surface-container);
- margin-top: 1px;
- padding: 4px;
- margin-bottom: 1px;
- border-radius: 8px;
-}
-
-#system-indicator-icon {
- font-size: 20px;
-}
-
-#system-notif-label {
- font-size: 18px;
-}
-
-/**
- * โโ Metric โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
- */
-#metrics {
- background-color: var(--surface-container);
- border-radius: 8px;
-}
-
-#metrics-circle {
- color: var(--surface-bright);
- border: 3px solid var(--primary);
-}
-
-#metrics-circle.bat {
- border: 3px solid var(--green);
-}
-
-#metrics-circle.alert {
- border: 3px solid var(--red-dim);
-}
-
-#metrics-icon {
- font-size: 15px;
-}
-
-#metrics-icon.alert {
- color: var(--red-dim);
-}
-
-#metrics-level {
- font-weight: bold;
-}
-
-#metrics-sep {
- min-width: 6px;
-}
diff --git a/styles/colors.css b/styles/colors.css
deleted file mode 100644
index f30081e..0000000
--- a/styles/colors.css
+++ /dev/null
@@ -1,44 +0,0 @@
-:vars {
- --foreground: #f1dfdb;
- --background: #1a1110;
- --cursor: #f1dfdb;
- --primary: #ffb4a5;
- --primary-container: #733428;
- --on-primary: #561e14;
- --on-primary-container: #ffdad3;
- --secondary: #e7bdb5;
- --on-secondary: #442a24;
- --secondary-container: #5d3f39;
- --tertiary: #dcc48c;
- --tertiary-container: #554519;
- --on-tertiary: #3d2f04;
- --on-tertiary-container: #f9e0a6;
- --surface: #1a1110;
- --surface-container: #271d1b;
- --surface-bright: #423734;
- --surface-variant: #534340;
- --surface-container-low: #231918;
- --on-surface: #f1dfdb;
- --error: #ffb4ab;
- --error-dim: #ff8678;
- --on-error: #690005;
- --error-container: #93000a;
- --on-error-container: #ffdad6;
- --outline: #a08c88;
- --outline-variant: #534340;
- --shadow: #000000;
- --red: #ffb4a7;
- --red-dim: #ff8974;
- --green: #b7d085;
- --green-dim: #a1c260;
- --yellow: #dec56e;
- --yellow-dim: #d5b444;
- --blue: #cebdfe;
- --blue-dim: #a98bfd;
- --magenta: #fcb0d5;
- --magenta-dim: #fa7fbb;
- --cyan: #84d5c4;
- --cyan-dim: #5ec8b2;
- --white: #82d3e0;
-
-}
diff --git a/styles/dashboard.css b/styles/dashboard.css
deleted file mode 100644
index 6d99fc2..0000000
--- a/styles/dashboard.css
+++ /dev/null
@@ -1,163 +0,0 @@
-#buttons-grid {
- min-height: 52px;
-}
-
-#network-button,
-#bluetooth-button,
-#night-mode-button,
-#power-profile-button,
-#dark-mode-button,
-#caffeine-button {
- border-radius: 20px;
-}
-
-#network-button,
-#bluetooth-button {
- background-color: unset;
- padding: 0;
-}
-
-#night-mode-button,
-#dark-mode-button,
-#power-profile-button,
-#caffeine-button {
- background-color: var(--primary);
- padding: 0 10px;
-}
-
-#night-mode-button.disabled,
-#dark-mode-button.disabled,
-#power-profile-button.disabled,
-#caffeine-button.disabled {
- background-color: var(--surface);
-}
-
-#network-status-button,
-#network-menu-button,
-#bluetooth-status-button,
-#bluetooth-menu-button {
- background-color: var(--primary);
- padding: 0 8px;
-}
-
-#network-menu-button,
-#bluetooth-menu-button {
- border-left: 1px solid var(--outline);
- min-width: 24px;
-}
-
-#network-status-button:hover,
-#network-menu-button:hover,
-#bluetooth-status-button:hover,
-#bluetooth-menu-button:hover,
-#bluetooth-button:hover,
-#night-mode-button:hover,
-#power-profile-button:hover,
-#dark-mode-button:hover,
-#caffeine-button:hover {
- background-color: var(--foreground);
-}
-
-/* Estados Disabled */
-#network-status-button.disabled,
-#network-menu-button.disabled,
-#bluetooth-status-button.disabled,
-#bluetooth-menu-button.disabled {
- background-color: var(--surface);
-}
-
-#network-menu-button.disabled,
-#bluetooth-menu-button.disabled {
- border-left: 1px solid var(--surface-bright);
-}
-
-#network-status-button.disabled:hover,
-#network-menu-button.disabled:hover,
-#bluetooth-status-button.disabled:hover,
-#bluetooth-menu-button.disabled:hover,
-#night-mode-button.disabled:hover,
-#power-profile-button.disabled:hover,
-#dark-mode-button.disabled:hover,
-#caffeine-button.disabled:hover {
- background-color: var(--surface-bright);
-}
-
-#network-status-button,
-#bluetooth-status-button {
- border-radius: 20px 0 0 20px;
-}
-
-#network-menu-button,
-#bluetooth-menu-button {
- border-radius: 0 20px 20px 0;
-}
-
-#network-icon,
-#bluetooth-icon,
-#night-mode-icon,
-#dark-mode-icon,
-#power-profile-icon,
-#caffeine-icon {
- color: var(--shadow);
- font-size: 24px;
-}
-
-#network-icon.disabled,
-#bluetooth-icon.disabled,
-#night-mode-icon.disabled,
-#power-profile-icon.disabled,
-#dark-mode-icon.disabled,
-#caffeine-icon.disabled {
- color: var(--outline);
-}
-
-#network-menu-label,
-#bluetooth-menu-label {
- color: var(--shadow);
- font-size: 16px;
-}
-
-#network-menu-label.disabled,
-#bluetooth-menu-label.disabled {
- color: var(--outline);
-}
-
-#network-label,
-#bluetooth-label,
-#night-mode-label,
-#power-profile-label,
-#caffeine-label,
-#dark-mode-label {
- color: var(--shadow);
- font-size: 14px;
- font-weight: bold;
- margin-bottom: -4px;
-}
-
-#network-label.disabled,
-#bluetooth-label.disabled,
-#night-mode-label.disabled,
-#power-profile-label.disabled,
-#dark-mode-label.disabled,
-#caffeine-label.disabled {
- color: var(--outline);
-}
-
-#network-ssid,
-#bluetooth-status,
-#night-mode-status,
-#power-profile-status,
-#dark-mode-status,
-#caffeine-status {
- color: var(--surface-bright);
- font-size: 12px;
-}
-
-#network-ssid.disabled,
-#bluetooth-status.disabled,
-#night-mode-status.disabled,
-#power-profile-status.disabled,
-#dark-mode-status.disabled,
-#caffeine-status.disabled {
- color: var(--outline);
-}
diff --git a/styles/dock.css b/styles/dock.css
deleted file mode 100644
index c0a9860..0000000
--- a/styles/dock.css
+++ /dev/null
@@ -1,78 +0,0 @@
-#dock {
- background-color: var(--surface);
- padding: 8px;
- margin: 8px 8px 7px 8px;
- border-radius: 20px 20px 0 0;
- transition: all 0.25s cubic-bezier(0.5, 0.25, 0, 1);
-}
-
-#dock.vertical {
- margin: 8px 79px 8px 8px;
- border-radius: 20px 0 0 20px;
-}
-
-#dock.vertical-left {
- margin: 8px 39px 8px 0px;
- border-radius: 0 20px 20px 0;
-}
-
-#dock.vertical-right{
- margin: 8px 79px 8px 8px;
- border-radius: 20px 0 0 20px;
-}
-
-#dock-full {
- transition: all 0.25s cubic-bezier(0.5, 0.25, 0, 1);
- opacity: 1;
-}
-
-#dock-full.occluded {
- @apply dock_nmargin();
-}
-
-#dock-separator {
- padding: 1px;
- @apply dock_sep();
- border-radius: 16px;
- background-color: var(--surface-bright);
-}
-
-#dock-app-button {
- padding: 4px;
- border-radius: 12px;
-}
-
-#dock-app-button:hover,
-#dock-app-button:hover.instance {
- background-color: var(--surface-bright);
- border-top: 1px solid var(--primary);
- padding-top: 3px;
-}
-
-#dock-app-button:active,
-#dock-app-button:active.instance {
- background-color: var(--primary);
-}
-
-#dock-app-button.instance {
- background: linear-gradient(to top, transparent, var(--surface-bright));
- border-bottom: 1px solid var(--surface-container);
- border-top: 1px solid var(--outline);
- padding: 3px 4px;
-}
-
-#dock-corner-left {
- margin: 0 -8px 7px 0;
-}
-
-#dock-corner-right {
- margin: 0 0 7px -8px;
-}
-
-#dock-corner-top {
- margin: 0 79px -8px 0;
-}
-
-#dock-corner-bottom {
- margin: -8px 79px 0 0;
-}
diff --git a/styles/launcher.css b/styles/launcher.css
deleted file mode 100644
index 8a58328..0000000
--- a/styles/launcher.css
+++ /dev/null
@@ -1,885 +0,0 @@
-/*
- * โโ launcher โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-#dashboard {
- min-height: 100px;
-}
-
-#launcher {
- margin: 18px;
- border: 1px solid var(--outline-variant);
- background-color: var(--surface);
- border-radius: 24px;
- padding: 18px;
- transition: all 0.5s cubic-bezier(0.5, 0.25, 0, 1);
-}
-
-#launcher entry {
- transition: all 0.1s ease;
- border-radius: 11px;
- padding: 10px;
- margin: 12px;
- box-shadow: inset 0 0 0 1px var(--outline-variant);
-}
-
-#launcher-content.wallpapers {
- padding: 16px;
- min-height: 420px;
- min-width: 620px;
-}
-
-.applauncher-scrolled-window {
- transition: min-height 0.2s ease;
-}
-
-#launcher-content.bluetooth {
- min-height: 464px;
-}
-
-#launcher-content.wifi {
- min-height: 464px;
-}
-
-#launcher-content.tools {
- min-width: 308px;
-}
-
-#launcher-content.calendar {
- min-height: 420px;
- min-width: 420px;
-}
-
-#launcher-content {
- min-width: 485px;
-}
-
-#launcher-content.power {
- min-width: 200px;
- min-height: 52px;
-}
-
-#launcher-content.kanban {
- min-width: 1000px;
- min-height: 478px;
-}
-
-#launcher-content.tmux {
- min-height: 220px;
- min-width: 480px;
-}
-
-#scrolled-window scrollbar {
- border-radius: 10px;
- background-color: var(--surface);
- padding: 4px;
- margin-left: 6px;
-}
-
-#scrolled-window slider {
- border-radius: 8px;
- min-width: 10px;
- min-height: 48px;
- background-color: var(--primary);
-}
-
-#config-button:active,
-#new-session-button:active {
- background-color: var(--primary);
-}
-
-#config-label,
-#clear-label,
-#new-session-label {
- color: var(--primary);
- font-size: 24px;
-}
-
-#config-button:active #config-label,
-#clear-button:active
- #clear-label
- #new-session-button:active
- #new-session-label {
- color: var(--shadow);
-}
-
-#config-button,
-#clear-button,
-#new-session-button {
- background-color: var(--surface-variant);
- border-radius: 16px;
- padding: 8px;
- margin-bottom: 12px;
- margin-top: 12px;
-}
-
-#config-button:hover,
-#config-button:focus,
-#clear-button:hover,
-#clear-button:focus,
-#new-session-button:hover,
-#new-session-button:focus {
- background-color: var(--surface-bright);
-}
-
-#clear-button:active {
- background-color: var(--red-dim);
-}
-
-/*
- * โโ applauncher โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-#app-desc {
- color: var(--outline);
- font-size: 12px;
- font-style: italic;
-}
-
-#slot-button {
- border-radius: 16px;
- padding: 16px;
- min-height: 0;
- transition: padding 0.25s cubic-bezier(0.5, 0.25, 0, 1);
- animation: loadSlot 0.5s ease;
-}
-
-@keyframes loadSlot {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-}
-
-#slot-button:hover,
-#slot-button:focus,
-#slot-button:selected,
-#slot-button.selected {
- background-color: var(--surface-bright);
- padding-left: 24px;
-}
-
-#slot-button:active {
- background-color: var(--primary);
-}
-
-#app-icon {
- margin-right: 8px;
-}
-
-#app-label {
- color: var(--foreground);
- font-weight: bold;
-}
-
-#slot-button:hover #app-label,
-#slot-button:focus #app-label,
-#slot-button:selected #app-label,
-#slot-button.selected #app-label {
- color: var(--primary);
-}
-
-#slot-button:active #app-label {
- color: var(--shadow);
-}
-
-/*
- * โโ wallpaper โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-#search-entry-walls {
- min-width: 512px;
-}
-
-#wallpaper-icons:selected {
- background-color: var(--outline);
- border-radius: 0px;
-}
-
-#scheme-dropdown {
- background-color: var(--surface);
- padding: 8px;
-}
-
-#scheme-dropdown > * > * {
- font-weight: bold;
- color: var(--shadow);
-}
-
-#scheme-dropdown.box button.box.cellview {
- background-color: var(--primary);
-}
-
-#scheme-dropdown.box button.box.cellview:selected {
- background-color: var(--surface-bright);
-}
-
-#search-entry-walls {
- min-width: 512px;
-}
-
-/* Customize the overall switch appearance */
-#matugen-switcher,
-#dnd-switch {
- min-width: 40px;
- min-height: 20px;
- background-color: var(--surface);
- border-radius: 15px;
- padding: 2px;
- transition: background-color 0.3s ease;
-}
-
-/* Style the switch's slider */
-#matugen-switcher slider,
-#dnd-switch slider {
- background-color: var(--primary);
- border-radius: 16px;
- min-width: 20px;
- min-height: 20px;
- transition:
- background-color 0.1s cubic-bezier(0.5, 0.25, 0, 1.25),
- transform 0.25s cubic-bezier(0.5, 0.25, 0, 1.25);
-}
-
-/* When the switch is active (checked) */
-#matugen-switcher:checked,
-#dnd-switch:checked {
- background-color: var(--primary);
-}
-
-/* Optional: additional styling for the slider when active */
-#matugen-switcher:checked slider,
-#dnd-switch:checked slider {
- background-color: var(--shadow);
-}
-
-#matugen-switcher:checked image,
-#dnd-switch:checked image {
- color: var(--shadow);
-}
-
-#mat-label {
- font-size: 24px;
- margin-left: 4px;
-}
-
-#custom-color-selector-box {
- background-color: var(--surface);
- border-radius: 20px;
- padding: 8px;
-}
-
-#hue-slider {
- margin: 0 24px;
-}
-
-#hue-slider trough {
- background-image: linear-gradient(
- to right,
- #ff0000,
- #ffff00,
- #00ff00,
- #00ffff,
- #0000ff,
- #ff00ff,
- #ff0000
- );
- border-radius: 8px;
- min-height: 8px;
-}
-
-#hue-slider slider {
- min-height: 16px;
- min-width: 8px;
- margin: -10px 0;
- background-color: var(--primary);
- box-shadow: 0 0 4px alpha(var(--shadow), 0.5);
-}
-
-#apply-color-button {
- background-color: var(--primary);
- color: var(--shadow);
- border-radius: 20px;
- padding: 8px;
- box-shadow: 0 0 4px alpha(var(--shadow), 0.5);
-}
-
-#apply-color-button:hover {
- background-color: var(--foreground);
-}
-
-#apply-color-button:active {
- background-color: var(--shadow);
-}
-
-#apply-color-label {
- font-size: 16px;
- color: var(--shadow);
-}
-
-#apply-color-button:active #apply-color-label {
- color: var(--foreground);
-}
-/*
- * โโ emoji โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-
-#emoji #search-entry {
- font-weight: bold;
- background-color: var(--surface);
- color: var(--foreground);
- border-radius: 16px;
- padding: 10px;
-}
-
-#emoji #search-entry selection {
- color: var(--background);
- background-color: var(--primary);
-}
-
-#emoji #close-button {
- background-color: var(--surface);
- border-radius: 16px;
- padding: 8px;
-}
-
-#emoji #close-button:hover,
-#emoji #close-button:focus {
- background-color: var(--surface-bright);
-}
-
-#emoji #close-button:active {
- background-color: var(--red-dim);
-}
-
-#emoji #close-label {
- color: var(--red-dim);
- font-size: 24px;
-}
-
-#emoji #close-button:active #close-label {
- color: var(--shadow);
-}
-
-#emoji #emoji-slot-button {
- border-radius: 16px;
- padding: 16px;
- transition: padding 0.1s ease;
-}
-
-@keyframes loadEmojiSlot {
- 0% {
- opacity: 0;
- }
-
- 100% {
- opacity: 1;
- }
-}
-
-#emoji #emoji-slot-button:hover,
-#emoji #emoji-slot-button:focus,
-#emoji #emoji-slot-button:selected,
-#emoji #emoji-slot-button.selected {
- background-color: var(--surface-bright);
- text-shadow: 0 0 4px var(--shadow);
- animation: highlightEmojiSlot 0.25s cubic-bezier(0, 0, 0.5, 1);
-}
-
-@keyframes highlightEmojiSlot {
- 0% {
- padding: 16px;
- text-shadow: 0 4px 4px var(--shadow);
- }
-
- 50% {
- padding: 8px 16px 24px 16px;
- text-shadow: 0 12px 4px var(--shadow);
- }
-
- 100% {
- padding: 16px;
- text-shadow: 0 4px 4px var(--shadow);
- }
-}
-
-#emoji #emoji-slot-button:active {
- background-color: var(--primary);
-}
-
-#emoji #emoji-name-label {
- color: var(--foreground);
- font-weight: bold;
-}
-
-#emoji #emoji-slot-button:hover #emoji-name-label,
-#emoji #emoji-slot-button:focus #emoji-name-label,
-#emoji #emoji-slot-button:selected #emoji-name-label,
-#emoji #emoji-slot-button.selected #emoji-name-label {
- color: var(--primary);
-}
-
-#emoji #emoji-slot-button:active #emoji-name-label {
- color: var(--shadow);
-}
-
-#emoji #emoji-char-label {
- font-size: 24px;
-}
-
-/*
- * โโ powermenu โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-
-#power-menu-button {
- border-radius: 20px;
- min-width: 52px;
- min-height: 52px;
- padding: 5px;
-}
-
-#power-menu-button label {
- font-size: 35px;
- color: var(--foreground);
-}
-
-#power-menu-button:hover,
-#power-menu-button:focus {
- background-color: var(--surface-bright);
-}
-
-#power-menu-button:hover label,
-#power-menu-button:focus label {
- color: var(--primary);
-}
-
-#power-menu-button:active {
- background-color: var(--primary);
-}
-
-#power-menu-button:active label {
- color: var(--shadow);
-}
-
-/*
- * โโ sh runner โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-
-#sh-item {
- border-radius: 11px;
- background-color: transparent;
- background-image: none;
- box-shadow: none;
- padding: 0px;
-}
-
-#sh-item {
- font-size: 24px;
-}
-
-#sh-item:hover,
-#sh-item:focus {
- text-shadow: 2px 2px 2px var(--primary);
-}
-
-/*
- * โโ bluetooth โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-
-#bluetooth-header {
- border: 2px solid var(--shadow);
- padding: 4px;
- background-color: var(--surface-container);
- border-radius: 12px;
-}
-
-#bluetooth-device {
- border: 2px solid var(--shadow);
- background-color: var(--surface-container);
- border-radius: 12px;
- padding: 4px;
-}
-
-#bluetooth-scan,
-#bluetooth-back {
- background-color: var(--surface);
- border-radius: 8px;
- padding: 4px;
-}
-
-#bluetooth-back-label {
- font-size: 20px;
-}
-
-#bluetooth-scan:hover,
-#bluetooth-back:hover {
- background-color: var(--surface-bright);
-}
-
-#bluetooth-scan label,
-#bluetooth-toggle label {
- font-weight: bold;
-}
-
-#bluetooth-text,
-#bluetooth-section {
- font-weight: bold;
-}
-
-#bluetooth-section {
- background-color: var(--surface-container);
- border-radius: 12px;
- padding: 8px;
-}
-
-#bluetooth-connection {
- font-size: 20px;
-}
-
-#bluetooth-connect {
- background-color: var(--surface);
- border-radius: 8px;
- padding: 8px;
-}
-
-#bluetooth-connect:hover {
- background-color: var(--surface-bright);
-}
-
-#bluetooth-paired,
-#bluetooth-available {
- background-color: var(--surface-container);
- border-radius: 20px;
- padding: 4px;
-}
-
-#bt-sep {
- border-radius: 16px;
-}
-
-#bluetooth-scan-label {
- font-size: 20px;
- color: var(--primary);
-}
-
-#bluetooth-scan.scanning {
- animation: blink 0.5s ease infinite;
-}
-
-#bluetooth-scan-label.scanning {
- animation: blink 0.5s ease infinite;
-}
-
-@keyframes blink {
- 0% {
- background-color: var(--blue);
- color: var(--shadow);
- }
-
- 50% {
- background-color: var(--shadow);
- color: var(--blue);
- }
-
- 100% {
- background-color: var(--blue);
- color: var(--shadow);
- }
-}
-
-/*
- * โโ wifi โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-
-#wifi-header {
- background-color: var(--surface-container);
- padding: 4px;
- border-radius: 20px;
-}
-
-#wifi-device {
- background-color: var(--secondary-container);
- border-radius: 16px;
- padding: 4px;
- margin-bottom: 4px;
-}
-
-#wifi-toggle-icon {
- font-size: 24px;
-}
-
-#wifi-scan,
-#wifi-toggle {
- background-color: var(--surface-container);
- margin-left: 0px;
- min-width: 44px;
- min-height: 44px;
- border-radius: 100%;
-}
-
-#wifi-scan:hover,
-#wifi-toggle:hover {
- background-color: var(--surface-bright);
-}
-
-#wifi-scan label,
-#wifi-toggle label {
- font-weight: bold;
-}
-
-#wifi-text,
-#wifi-section {
- font-weight: bold;
-}
-
-#wifi-text {
- color: var(--primary);
-}
-
-#wifi-connection {
- font-size: 20px;
-}
-
-#wifi-connect {
- background-color: var(--surface);
- border-radius: 16px;
- padding: 8px 16px;
-}
-
-#wifi-connect:hover {
- background-color: var(--surface-bright);
-}
-
-#wifi-available {
- /*background-color: var(--surface-container);*/
- padding: 4px;
- border-radius: 20px;
-}
-
-/*
- * โโ calendar โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-
-#header {
- border-radius: 12px;
- padding: 4px;
-}
-
-#weekday-row {
- background-color: var(--surface);
- border-radius: 8px;
- padding: 4px;
-}
-
-#month-label,
-#weekday-label,
-#day-label {
- font-weight: bold;
-}
-
-#day-label,
-#day-empty {
- min-width: 38px;
- min-height: 22px;
-}
-
-#day-empty {
- color: var(--surface-bright);
-}
-
-#weekday-label {
- color: var(--primary);
-}
-
-#calendar-grid {
- border-radius: 16px;
- padding: 4px;
-}
-
-#day-label.current-day {
- background-color: var(--primary);
- color: var(--shadow);
- border-radius: 20px;
-}
-
-#prev-month-button,
-#next-month-button {
- background-color: var(--surface);
- border-radius: 8px;
- padding: 4px;
-}
-
-#prev-month-button:hover,
-#next-month-button:hover {
- background-color: var(--surface-bright);
-}
-
-#prev-month-button:active,
-#next-month-button:active {
- background-color: var(--primary);
-}
-
-#month-button-label {
- color: var(--primary);
- font-size: 20px;
-}
-
-#prev-month-button:active #month-button-label,
-#next-month-button:active #month-button-label {
- color: var(--shadow);
-}
-
-#prev-month-day,
-#next-month-day {
- color: mix(var(--on-surface), transparent, 0.6);
-}
-
-/*
- * โโ tools โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-
-#toolbox-button {
- border-radius: 20px;
- min-width: 52px;
- min-height: 52px;
- padding: 5px;
-}
-
-#toolbox-button label {
- font-size: 35px;
- color: var(--foreground);
-}
-
-#toolbox-button.recording label {
- color: var(--red-dim);
-}
-
-#toolbox-button:hover,
-#toolbox-button:focus {
- background-color: var(--surface-bright);
-}
-
-#toolbox-button:hover label,
-#toolbox-button:focus label {
- color: var(--primary);
-}
-
-#toolbox-button.recording:hover label,
-#toolbox-button.recording:focus label {
- color: var(--red-dim);
-}
-
-#toolbox-button:active {
- background-color: var(--primary);
-}
-
-#toolbox-button:active label {
- color: var(--shadow);
-}
-
-#toolbox-button.recording:active {
- background-color: var(--red-dim);
-}
-
-#toolbox-button.recording:active label {
- color: var(--shadow);
-}
-
-#tool-sep {
- min-width: 4px;
- min-height: 4px;
- margin: 0px 4px;
- border-radius: 16px;
- background-color: var(--surface-bright);
-}
-
-/*
- * โโ kanban โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-
-#kanban-header {
- border: 2px solid var(--outline);
- border-radius: 12px;
- padding: 4px;
-}
-
-#column-header {
- padding: 4px;
- font-weight: bold;
- color: var(--primary);
-}
-
-#kanban-note {
- background-color: var(--surface-container);
- border-radius: 4px;
- padding: 8px;
- margin-bottom: 4px;
-}
-
-#kanban-row:first-child > * > * {
- border-radius: 16px 16px 4px 4px;
-}
-
-#kanban-row:last-child > * > * {
- border-radius: 4px 4px 16px 16px;
-}
-
-#inline-editor {
- border: 2px solid var(--surface-bright);
- border-radius: 12px;
- padding: 8px;
-}
-
-#kanban-btn,
-#kanban-btn-add {
- background-color: var(--surface-container);
- border-radius: 8px;
- padding: 4px;
-}
-
-#kanban-btn {
- background-color: var(--surface);
-}
-
-#kanban-btn:hover,
-#kanban-btn-add:hover {
- background-color: var(--surface-bright);
-}
-
-#kanban-btn:active,
-#kanban-btn-add:active {
- background-color: var(--primary);
-}
-
-#kanban-btn-label,
-#kanban-btn-neg {
- font-size: 20px;
- color: var(--primary);
-}
-
-#kanban-btn-neg {
- color: var(--red-dim);
-}
-
-#kanban-btn:active #kanban-btn-label,
-#kanban-btn-add:active #kanban-btn-label {
- color: var(--surface);
-}
-
-/* tmux */
-#no-notif,
-#no-tmux,
-#no-clip {
- font-size: 96px;
- color: var(--surface-container);
-}
-
-#clip-label {
- font-weight: bold;
-}
-
-#tmux-icon,
-#clip-icon {
- font-size: 20px;
- color: var(--primary);
-}
diff --git a/styles/main.css b/styles/main.css
deleted file mode 100644
index 79e35a8..0000000
--- a/styles/main.css
+++ /dev/null
@@ -1,51 +0,0 @@
-@import "./colors.css";
-@import "./bar.css";
-@import "./notification.css";
-@import "./osd.css";
-@import "./launcher.css";
-@import "./dashboard.css";
-@import "./dock.css";
-@import "./notif_center.css";
-
-* {
- all: unset;
- color: var(--foreground);
- font-size: 15px;
- font-family: "Monaspace Radon";
-}
-
-/*
- * โโ tooltip โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
-*/
-tooltip {
- border: solid 1px;
- border-radius: 16px;
- border-color: var(--outline);
- background-color: var(--surface);
- animation: tooltipShow 0.25s cubic-bezier(0.5, 0.25, 0, 1);
-}
-
-@keyframes tooltipShow {
- from {
- opacity: 0;
- }
-
- to {
- opacity: 1;
- }
-}
-
-tooltip > * {
- padding: 6px 10px;
- border-radius: 10px;
-}
-
-#corner {
- background-color: var(--surface);
- border-radius: 0;
-}
-
-#corner-container {
- min-width: 20px;
- min-height: 20px;
-}
diff --git a/styles/notif_center.css b/styles/notif_center.css
deleted file mode 100644
index 85f0f1d..0000000
--- a/styles/notif_center.css
+++ /dev/null
@@ -1,28 +0,0 @@
-#nhh {
- font-weight: bold;
-}
-
-#nhh-button {
- border-radius: 8px;
- background-color: var(--surface);
- padding: 4px;
-}
-
-#nhh-button:hover {
- background-color: var(--surface-bright);
-}
-
-#nhh-button-label {
- color: var(--red-dim);
- font-size: 20px;
-}
-
-#dnd-label {
- font-size: 20px;
- margin-left: 8px;
-}
-
-#no-notifications-label {
- font-size: 96px;
- color: var(--surface-container);
-}
diff --git a/styles/notification.css b/styles/notification.css
deleted file mode 100644
index dddf9bf..0000000
--- a/styles/notification.css
+++ /dev/null
@@ -1,83 +0,0 @@
-#notification-action-buttons button.start-action {
- border-radius: 20px;
-}
-
-#notification-action-buttons button {
- background-color: var(--surface);
- padding: 8px;
- border-radius: 20px;
- transition: background-color 0.1s ease;
-}
-
-#notification-action-buttons button:hover {
- background-color: var(--surface-bright);
-}
-
-#notification-action-buttons button:active {
- background-color: var(--primary);
-}
-
-#notification-action-buttons button:active #button-label {
- color: var(--shadow);
-}
-
-#notification-image image {
- border-radius: 16px;
- color: var(--on-surface);
-}
-
-#notification-summary,
-#button-label {
- font-weight: bold;
-}
-
-#notification-summary {
- font-weight: bold;
- color: var(--primary);
-}
-
-#notification-app-name {
- color: var(--outline);
-}
-
-#action-button {
- margin-top: 8px;
-}
-
-#notification-box {
- border-radius: 20px;
- padding: 16px;
- background-color: var(--surface-container-low);
- margin: 4px;
- min-width: 336px;
- transition: background-color 0.1s ease;
-}
-
-#boxed-notification-revealer {
- margin-top: -14px;
-}
-
-#notif-close-button {
- background-color: var(--surface);
- border-radius: 16px;
- padding: 4px;
- transition: background-color 0.1s ease;
-}
-
-#notif-close-button:hover,
-#notif-close-button:focus {
- background-color: var(--surface-bright);
-}
-
-#notif-close-button:active {
- background-color: var(--red-dim);
-}
-
-#notif-close-label {
- color: var(--red-dim);
- font-size: 20px;
-}
-
-#notif-close-button:active #notif-close-label {
- color: var(--shadow);
-}
diff --git a/styles/osd.css b/styles/osd.css
deleted file mode 100644
index 017865d..0000000
--- a/styles/osd.css
+++ /dev/null
@@ -1,47 +0,0 @@
-#osd {
- background-color: var(--surface);
- padding: 10px;
- border-radius: 6px;
- margin-top: 6px;
-}
-
-#osd scale {
- min-width: 150px;
-}
-
-#osd trough {
- background: var(--surface-container);
- min-height: 10px;
- margin-right: 4px;
- border-radius: 99px;
-}
-
-#osd highlight {
- border-radius: 99px 0px 0px 99px;
- background: var(--primary);
- margin-right: 6px;
-}
-
-#osd slider {
- color: var(--on-primary);
- background-color: var(--primary);
- background-size: 20px;
- min-width: 5px;
- min-height: 10px;
- border-radius: 12px;
- padding: 0px;
- background-repeat: no-repeat;
- background-position: center;
- margin-left: 0px;
- margin-right: 0px;
- margin-top: -10px;
- margin-bottom: -10px;
- box-shadow:
- -1px 0px 0px 1px var(--surface-container),
- 1px 0px 0px 1px var(--surface-container);
-}
-
-#osd-label {
- font-weight: bold;
- font-size: 17px;
-}
diff --git a/utils/__init__.py b/utils/__init__.py
deleted file mode 100644
index 43abad3..0000000
--- a/utils/__init__.py
+++ /dev/null
@@ -1,4 +0,0 @@
-from .custom_image import *
-from .icon_resolver import *
-from .occlusion import *
-from .animator import *
diff --git a/utils/animator.py b/utils/animator.py
deleted file mode 100644
index 1799ae6..0000000
--- a/utils/animator.py
+++ /dev/null
@@ -1,179 +0,0 @@
-from typing import cast
-
-from fabric import Property, Service, Signal
-from gi.repository import GLib, Gtk
-
-
-class Animator(Service):
- @Signal
- def finished(self) -> None: ...
-
- @Property(tuple[float, float, float, float], "read-write")
- def bezier_curve(self) -> tuple[float, float, float, float]:
- return self._bezier_curve
-
- @bezier_curve.setter
- def bezier_curve(self, value: tuple[float, float, float, float]):
- self._bezier_curve = value
- return
-
- @Property(float, "read-write")
- def value(self):
- return self._value
-
- @value.setter
- def value(self, value: float):
- self._value = value
- return
-
- @Property(float, "read-write")
- def max_value(self):
- return self._max_value
-
- @max_value.setter
- def max_value(self, value: float):
- self._max_value = value
- return
-
- @Property(float, "read-write")
- def min_value(self):
- return self._min_value
-
- @min_value.setter
- def min_value(self, value: float):
- self._min_value = value
- return
-
- @Property(bool, "read-write", default_value=False)
- def playing(self):
- return self._playing
-
- @playing.setter
- def playing(self, value: bool):
- self._playing = value
- return
-
- @Property(bool, "read-write", default_value=False)
- def repeat(self):
- return self._repeat
-
- @repeat.setter
- def repeat(self, value: bool):
- self._repeat = value
- return
-
- def __init__(
- self,
- bezier_curve: tuple[float, float, float, float],
- duration: float,
- min_value: float = 0.0,
- max_value: float = 1.0,
- repeat: bool = False,
- tick_widget: Gtk.Widget | None = None,
- **kwargs,
- ):
- super().__init__(**kwargs)
- self._bezier_curve = (1, 0, 1, 1)
- self._duration = 5
- self._value = 0.0
- self._min_value = 0.0
- self._max_value = 1.0
- self._repeat = False
-
- self.bezier_curve = bezier_curve
- self.duration = duration
- self.value = min_value
- self.min_value = min_value
- self.max_value = max_value
- self.repeat = repeat
-
- self.playing = False
- self._start_time = None
- self._tick_handler = None
- self._timeline_pos = 0
- self._tick_widget = tick_widget
-
- def do_get_time_now(self):
- return GLib.get_monotonic_time() / 1_000_000
-
- def do_lerp(self, start: float, end: float, time: float) -> float:
- return start + (end - start) * time
-
- def do_interpolate_cubic_bezier(self, time: float) -> float:
- y_points = (0, self.bezier_curve[1], self.bezier_curve[3], 1)
- return (
- (1 - time) ** 3 * y_points[0]
- + 3 * (1 - time) ** 2 * time * y_points[1]
- + 3 * (1 - time) * time**2 * y_points[2]
- + time**3 * y_points[3]
- )
-
- def do_ease(self, time: float) -> float:
- return self.do_lerp(
- self.min_value, self.max_value, self.do_interpolate_cubic_bezier(time)
- )
-
- def do_update_value(self, delta_time: float):
- if not self.playing:
- return
-
- elapsed_time = delta_time - cast(float, self._start_time)
-
- self._timeline_pos = min(1, elapsed_time / self.duration)
-
- self.value = self.do_ease(self._timeline_pos)
-
- if not self._timeline_pos >= 1:
- return
-
- if not self.repeat:
- self.value = self.max_value
- self.finished()
- self.pause()
- return
-
- self._start_time = delta_time
- self._timeline_pos = 0
- return
-
- def do_handle_tick(self, *_):
- current_time = self.do_get_time_now()
- self.do_update_value(current_time)
- return True
-
- def do_remove_tick_handlers(self):
- if self._tick_handler:
- if self._tick_widget:
- self._tick_widget.remove_tick_callback(self._tick_handler)
- else:
- GLib.source_remove(self._tick_handler)
- self._tick_handler = None
- return
-
- def play(self):
- if self.playing:
- return
-
- self._start_time = self.do_get_time_now()
-
- if not self._tick_handler:
- if self._tick_widget:
- self._tick_handler = self._tick_widget.add_tick_callback(
- self.do_handle_tick
- )
- else:
- self._tick_handler = GLib.timeout_add(16, self.do_handle_tick)
-
- self.playing = True
- return
-
- def pause(self):
- self.playing = False
- return self.do_remove_tick_handlers()
-
- def stop(self):
- if not self._tick_handler:
- self._timeline_pos = 0
- self.playing = False
- return
- return self.do_remove_tick_handlers()
diff --git a/utils/custom_image.py b/utils/custom_image.py
deleted file mode 100644
index 1aae7f6..0000000
--- a/utils/custom_image.py
+++ /dev/null
@@ -1,39 +0,0 @@
-import math
-from typing import cast
-
-import cairo
-from gi.repository import Gtk
-
-from fabric.widgets.image import Image
-
-
-class CustomImage(Image):
- def do_render_rectangle(
- self, cr: cairo.Context, width: int, height: int, radius: int = 0
- ):
- cr.move_to(radius, 0)
- cr.line_to(width - radius, 0)
- cr.arc(width - radius, radius, radius, -(math.pi / 2), 0)
- cr.line_to(width, height - radius)
- cr.arc(width - radius, height - radius, radius, 0, (math.pi / 2))
- cr.line_to(radius, height)
- cr.arc(radius, height - radius, radius, (math.pi / 2), math.pi)
- cr.line_to(0, radius)
- cr.arc(radius, radius, radius, math.pi, (3 * (math.pi / 2)))
- cr.close_path()
-
- def do_draw(self, cr: cairo.Context):
- context = self.get_style_context()
- width, height = self.get_allocated_width(), self.get_allocated_height()
- cr.save()
-
- self.do_render_rectangle(
- cr,
- width,
- height,
- cast(int, context.get_property("border-radius", Gtk.StateFlags.NORMAL)),
- )
- cr.clip()
- Image.do_draw(self, cr)
-
- cr.restore()
diff --git a/utils/icon_resolver.py b/utils/icon_resolver.py
deleted file mode 100644
index b8bedeb..0000000
--- a/utils/icon_resolver.py
+++ /dev/null
@@ -1,102 +0,0 @@
-import json
-import os
-import re
-
-import gi
-
-gi.require_version("Gtk", "3.0")
-from gi.repository import GLib, Gtk
-from loguru import logger
-
-import config.data as data
-
-ICON_CACHE_FILE = data.CACHE_DIR + "/icons.json"
-if not os.path.exists(data.CACHE_DIR):
- os.makedirs(data.CACHE_DIR)
-
-
-class IconResolver:
- def __init__(self, default_applicaiton_icon: str = "application-x-executable-symbolic"):
- if os.path.exists(ICON_CACHE_FILE):
- with open(ICON_CACHE_FILE) as f:
- try:
- self._icon_dict = json.load(f)
- except json.JSONDecodeError:
- logger.info("[ICONS] Cache file does not exist or is corrupted")
- self._icon_dict = {}
- else:
- self._icon_dict = {}
-
- self.default_applicaiton_icon = default_applicaiton_icon
-
- def get_icon_name(self, app_id: str):
- if app_id in self._icon_dict:
- return self._icon_dict[app_id]
- new_icon = self._compositor_find_icon(app_id)
- logger.info(
- f"[ICONS] found new icon: '{new_icon}' for app id: '{app_id}', storing..."
- )
- self._store_new_icon(app_id, new_icon)
- return new_icon
-
- def get_icon_pixbuf(self, app_id: str, size: int = 16):
- icon_theme = Gtk.IconTheme.get_default()
- icon_name = self.get_icon_name(app_id)
- try:
- # Try to load the resolved icon.
- return icon_theme.load_icon(icon_name, size, Gtk.IconLookupFlags.FORCE_SIZE)
- except GLib.Error as primary_error:
- logger.warning(
- f"Warning: Icon '{icon_name}' not found in theme. Error: {primary_error}"
- )
- try:
- # Fallback to the default application icon.
- return icon_theme.load_icon(
- self.default_applicaiton_icon, size, Gtk.IconLookupFlags.FORCE_SIZE
- )
- except GLib.Error as fallback_error:
- logger.error(
- f"Error: Fallback icon '{self.default_applicaiton_icon}' also not found. Error: {fallback_error}"
- )
- return None
-
- def _store_new_icon(self, app_id: str, icon: str):
- self._icon_dict[app_id] = icon
- with open(ICON_CACHE_FILE, "w") as f:
- json.dump(self._icon_dict, f)
-
- def _get_icon_from_desktop_file(self, desktop_file_path: str):
- # Retrieve the icon specified in the [Desktop Entry] section.
- with open(desktop_file_path) as f:
- for line in f.readlines():
- if "Icon=" in line:
- return "".join(line[5:].split())
- return self.default_applicaiton_icon
-
- def _get_desktop_file(self, app_id: str) -> str | None:
- data_dirs = GLib.get_system_data_dirs()
- for data_dir in data_dirs:
- data_dir = os.path.join(data_dir, "applications")
- if os.path.exists(data_dir):
- files = os.listdir(data_dir)
- matching = [s for s in files if "".join(app_id.lower().split()) in s.lower()]
- if matching:
- return os.path.join(data_dir, matching[0])
- for word in list(filter(None, re.split(r"-|\.|_|\s", app_id))):
- matching = [s for s in files if word.lower() in s.lower()]
- if matching:
- return os.path.join(data_dir, matching[0])
- return None
-
- def _compositor_find_icon(self, app_id: str):
- icon_theme = Gtk.IconTheme.get_default()
- if icon_theme.has_icon(app_id):
- return app_id
- if icon_theme.has_icon(app_id + "-desktop"):
- return app_id + "-desktop"
- desktop_file = self._get_desktop_file(app_id)
- return (
- self._get_icon_from_desktop_file(desktop_file)
- if desktop_file
- else self.default_applicaiton_icon
- )
\ No newline at end of file
diff --git a/utils/icons.py b/utils/icons.py
deleted file mode 100644
index ed61594..0000000
--- a/utils/icons.py
+++ /dev/null
@@ -1,185 +0,0 @@
-# Parameters
-font_family: str = "tabler-icons"
-font_weight: str = "normal"
-
-span: str = f""
-
-# Panels
-apps: str = ""
-dashboard: str = ""
-chat: str = ""
-wallpapers: str = ""
-windows: str = ""
-
-# Bar
-colorpicker: str = ""
-media: str = ""
-
-# Circles
-temp: str = ""
-disk: str = ""
-battery: str = ""
-memory: str = "流"
-cpu: str = ""
-swap: str = ""
-
-# Toolbox
-toolbox: str = ""
-ssfull: str = ""
-ssregion: str = ""
-screenshots: str = ""
-screenrecord: str = ""
-recordings: str = ""
-ocr: str = "ﳃ"
-gamemode: str = ""
-gamemode_off: str = ""
-close: str = ""
-bell: str = ""
-bell_off: str = ""
-notifications_on: str = ""
-notifications_off: str = ""
-notifications_clear: str = ""
-notifications: str = ""
-
-# Wallpapers
-add: str = ""
-sort: str = ""
-circle: str = ""
-check_box: str = ""
-box: str = ""
-scan: str = ""
-scan_stop: str = ""
-record: str = ""
-update: str = "見"
-updated: str = ""
-
-# Chevrons
-chevron_up: str = ""
-chevron_down: str = ""
-chevron_left: str = ""
-chevron_right: str = ""
-
-# Power
-lock: str = ""
-suspend: str = ""
-logout: str = ""
-reboot: str = ""
-shutdown: str = ""
-
-# Power Manager
-power_saving: str = ""
-power_balanced: str = "勺"
-power_performance: str = ""
-charging: str = ""
-discharging: str = ""
-alert: str = ""
-bat_charging: str = ""
-bat_discharging: str = ""
-bat_low: str = "="
-bat_full: str = ""
-
-
-moderate_signal: str = ""
-good_signal: str = ""
-no_signal: str = ""
-weak_signal: str = ""
-# Applets
-lan: str = ""
-wifi_0: str = ""
-wifi_1: str = ""
-wifi_2: str = ""
-wifi_3: str = ""
-wifi: str = ""
-world: str = ""
-world_off: str = ""
-bluetooth: str = ""
-night: str = ""
-coffee: str = ""
-dnd: str = ""
-darkmode: str = "﹖"
-
-
-wifi_off: str = ""
-bluetooth_off: str = ""
-night_off: str = ""
-dnd_off: str = ""
-
-
-# Bluetooth
-bluetooth_connected: str = ""
-bluetooth_disconnected: str = ""
-
-
-# Player
-pause: str = ""
-play: str = ""
-stop: str = ""
-skip_back: str = ""
-skip_forward: str = ""
-prev: str = ""
-next: str = ""
-shuffle: str = ""
-repeat: str = ""
-music: str = ""
-rewind_backward_5: str = "謹"
-rewind_forward_5: str = "難"
-
-# Volume
-vol_off: str = ""
-vol_mute: str = ""
-vol_medium: str = ""
-vol_high: str = ""
-
-
-mic: str = ""
-mic_muted: str = ""
-
-
-# Overview
-circle_plus: str = ""
-
-# Pins
-copy_plus: str = "ﶮ"
-
-# Clipboard Manager
-clipboard: str = ""
-clip_text: str = ""
-
-# Confirm
-accept: str = ""
-cancel: str = ""
-trash: str = ""
-clear: str = ""
-
-# Config
-config: str = ""
-
-# Icons
-firefox: str = ""
-chromium: str = ""
-spotify: str = "ﺆ"
-disc: str = ""
-disc_off: str = ""
-
-# Misc
-dot: str = ""
-palette: str = ""
-cloud_off: str = ""
-loader: str = ""
-radar: str = ""
-emoji: str = ""
-keyboard: str = ""
-terminal: str = ""
-
-
-exceptions: list[str] = ["font_family", "font_weight", "span"]
-
-
-def apply_span() -> None:
- global_dict = globals()
- for key in global_dict:
- if key not in exceptions and not key.startswith("__"):
- global_dict[key] = f"{span}{global_dict[key]}"
-
-
-apply_span()
diff --git a/utils/occlusion.py b/utils/occlusion.py
deleted file mode 100644
index bf06350..0000000
--- a/utils/occlusion.py
+++ /dev/null
@@ -1,141 +0,0 @@
-import subprocess
-import json
-
-import config.data as data
-
-
-def get_current_workspace():
- """
- Get the current workspace ID using hyprctl.
- """
- try:
- result = subprocess.run(
- ["hyprctl", "activeworkspace"], capture_output=True, text=True
- )
- # Assume the output similar to: "ID "
- # Extracting the number from the output
- parts = result.stdout.split()
- for i, part in enumerate(parts):
- if part == "ID" and i + 1 < len(parts):
- return int(parts[i + 1])
- except Exception as e:
- print(f"Error getting current workspace: {e}")
- return -1
-
-
-def get_screen_dimensions():
- """
- Get screen dimensions from hyprctl.
-
- Returns:
- tuple: (width, height) of the monitor containing the current workspace
- """
- try:
- # Get current workspace
- workspace_id = get_current_workspace()
-
- # Get monitor information
- result = subprocess.run(
- ["hyprctl", "-j", "monitors"], capture_output=True, text=True
- )
- monitors = json.loads(result.stdout)
-
- # Find the monitor containing our workspace
- for monitor in monitors:
- if monitor.get("activeWorkspace", {}).get("id") == workspace_id:
- return monitor.get("width", data.CURRENT_WIDTH), monitor.get(
- "height", data.CURRENT_HEIGHT
- )
-
- # Fallback to first monitor
- if monitors:
- return monitors[0].get("width", data.CURRENT_WIDTH), monitors[0].get(
- "height", data.CURRENT_HEIGHT
- )
- except Exception as e:
- print(f"Error getting screen dimensions: {e}")
-
- # Default fallback values
- return data.CURRENT_WIDTH, data.CURRENT_HEIGHT
-
-
-def check_occlusion(occlusion_region, workspace=None):
- """
- Check if a region is occupied by any window on a given workspace.
-
- Parameters:
- occlusion_region: Can be one of:
- - tuple (side, size): where side is "top", "bottom", "left", or "right"
- and size is the pixel width of the region
- - tuple (x, y, width, height): The full region coordinates (legacy format)
- workspace (int, optional): The workspace ID to check. If None, the current workspace is used.
-
- Returns:
- bool: True if any window overlaps with the occlusion region, False otherwise.
- """
- if workspace is None:
- workspace = get_current_workspace()
-
- # Handle simplified side-based format
- if isinstance(occlusion_region, tuple) and len(occlusion_region) == 2:
- side, size = occlusion_region
- if isinstance(side, str):
- # Convert side-based format to coordinates
- screen_width, screen_height = get_screen_dimensions()
-
- if side.lower() == "bottom":
- occlusion_region = (0, screen_height - size, screen_width, size)
- elif side.lower() == "top":
- occlusion_region = (0, 0, screen_width, size)
- elif side.lower() == "left":
- occlusion_region = (0, 0, size, screen_height)
- elif side.lower() == "right":
- occlusion_region = (screen_width - size, 0, size, screen_height)
-
- # Ensure occlusion_region is in the correct format (x, y, width, height)
- if not isinstance(occlusion_region, tuple) or len(occlusion_region) != 4:
- print(f"Invalid occlusion region format: {occlusion_region}")
- return False
-
- try:
- result = subprocess.run(
- ["hyprctl", "-j", "clients"], capture_output=True, text=True
- )
- clients = json.loads(result.stdout)
- except Exception as e:
- print(f"Error retrieving client windows: {e}")
- return False
-
- occ_x, occ_y, occ_width, occ_height = occlusion_region
- occ_x2 = occ_x + occ_width
- occ_y2 = occ_y + occ_height
-
- for client in clients:
- # Check if client is mapped
- if not client.get("mapped", False):
- continue
-
- # Ensure client has proper workspace information and matches the workspace
- client_workspace = client.get("workspace", {})
- if client_workspace.get("id") != workspace:
- continue
-
- # Ensure client has position and size info
- position = client.get("at")
- size = client.get("size")
- if not position or not size:
- continue
-
- x, y = position
- width, height = size
- win_x1, win_y1 = x, y
- win_x2, win_y2 = x + width, y + height
-
- # Check for intersection between the window and occlusion region
- if not (
- win_x2 <= occ_x or win_x1 >= occ_x2 or win_y2 <= occ_y or win_y1 >= occ_y2
- ):
- return True # Occlusion region is occupied
-
- return False # No window overlaps the occlusion region
-
diff --git a/utils/wayland.py b/utils/wayland.py
deleted file mode 100644
index 472dc40..0000000
--- a/utils/wayland.py
+++ /dev/null
@@ -1,359 +0,0 @@
-import re
-from collections.abc import Iterable
-from enum import Enum
-from typing import Literal, cast
-
-import cairo
-import gi
-from fabric.core.service import Property
-from fabric.utils.helpers import extract_css_values, get_enum_member
-from fabric.widgets.window import Window
-from gi.repository import Gdk, GObject, Gtk
-from loguru import logger
-
-gi.require_version("Gtk", "3.0")
-
-try:
- gi.require_version("GtkLayerShell", "0.1")
- from gi.repository import GtkLayerShell
-except:
- raise ImportError(
- "looks like we don't have gtk-layer-shell installed, make sure to install it first (as well as using wayland)"
- )
-
-
-class WaylandWindowExclusivity(Enum):
- NONE = 1
- NORMAL = 2
- AUTO = 3
-
-
-class Layer(GObject.GEnum):
- BACKGROUND = 0
- BOTTOM = 1
- TOP = 2
- OVERLAY = 3
- ENTRY_NUMBER = 4
-
-
-class KeyboardMode(GObject.GEnum):
- NONE = 0
- EXCLUSIVE = 1
- ON_DEMAND = 2
- ENTRY_NUMBER = 3
-
-
-class Edge(GObject.GEnum):
- LEFT = 0
- RIGHT = 1
- TOP = 2
- BOTTOM = 3
- ENTRY_NUMBER = 4
-
-
-class WaylandWindow(Window):
- @Property(
- Layer,
- flags="read-write",
- default_value=Layer.TOP,
- )
- def layer(self) -> Layer: # type: ignore
- return self._layer # type: ignore
-
- @layer.setter
- def layer(
- self,
- value: Literal["background", "bottom", "top", "overlay"] | Layer,
- ) -> None:
- self._layer = get_enum_member(Layer, value, default=Layer.TOP)
- return GtkLayerShell.set_layer(self, self._layer)
-
- @Property(int, "read-write")
- def monitor(self) -> int:
- if not (monitor := cast(Gdk.Monitor, GtkLayerShell.get_monitor(self))):
- return -1
- display = monitor.get_display() or Gdk.Display.get_default()
- for i in range(0, display.get_n_monitors()):
- if display.get_monitor(i) is monitor:
- return i
- return -1
-
- @monitor.setter
- def monitor(self, monitor: int | Gdk.Monitor) -> bool:
- if isinstance(monitor, int):
- display = Gdk.Display().get_default()
- monitor = display.get_monitor(monitor)
- return (
- (GtkLayerShell.set_monitor(self, monitor), True)[1]
- if monitor is not None
- else False
- )
-
- @Property(WaylandWindowExclusivity, "read-write")
- def exclusivity(self) -> WaylandWindowExclusivity:
- return self._exclusivity
-
- @exclusivity.setter
- def exclusivity(
- self, value: Literal["none", "normal", "auto"] | WaylandWindowExclusivity
- ) -> None:
- value = get_enum_member(
- WaylandWindowExclusivity, value, default=WaylandWindowExclusivity.NONE
- )
- self._exclusivity = value
- match value:
- case WaylandWindowExclusivity.NORMAL:
- return GtkLayerShell.set_exclusive_zone(self, True)
- case WaylandWindowExclusivity.AUTO:
- return GtkLayerShell.auto_exclusive_zone_enable(self)
- case _:
- return GtkLayerShell.set_exclusive_zone(self, False)
-
- @Property(bool, "read-write", default_value=False)
- def pass_through(self) -> bool:
- return self._pass_through
-
- @pass_through.setter
- def pass_through(self, pass_through: bool = False):
- self._pass_through = pass_through
- region = cairo.Region() if pass_through is True else None
- self.input_shape_combine_region(region)
- del region
- return
-
- @Property(
- KeyboardMode,
- "read-write",
- default_value=KeyboardMode.NONE,
- )
- def keyboard_mode(self) -> KeyboardMode:
- return self._keyboard_mode
-
- @keyboard_mode.setter
- def keyboard_mode(
- self,
- value: Literal[
- "none",
- "exclusive",
- "on-demand",
- "entry-number",
- ]
- | KeyboardMode,
- ):
- self._keyboard_mode = get_enum_member(
- KeyboardMode, value, default=KeyboardMode.NONE
- )
- return GtkLayerShell.set_keyboard_mode(self, self._keyboard_mode)
-
- @Property(tuple[Edge, ...], "read-write")
- def anchor(self):
- return tuple(
- x
- for x in [
- Edge.TOP,
- Edge.RIGHT,
- Edge.BOTTOM,
- Edge.LEFT,
- ]
- if GtkLayerShell.get_anchor(self, x)
- )
-
- @anchor.setter
- def anchor(self, value: str | Iterable[Edge]) -> None:
- self._anchor = value
- if isinstance(value, (list, tuple)) and all(
- isinstance(edge, Edge) for edge in value
- ):
- for edge in [
- Edge.TOP,
- Edge.RIGHT,
- Edge.BOTTOM,
- Edge.LEFT,
- ]:
- if edge not in value:
- GtkLayerShell.set_anchor(self, edge, False)
- GtkLayerShell.set_anchor(self, edge, True)
- return
- elif isinstance(value, str):
- for edge, anchored in WaylandWindow.extract_edges_from_string(
- value
- ).items():
- GtkLayerShell.set_anchor(self, edge, anchored)
-
- return
-
- @Property(tuple[int, ...], flags="read-write")
- def margin(self) -> tuple[int, ...]:
- return tuple(
- GtkLayerShell.get_margin(self, x)
- for x in [
- Edge.TOP,
- Edge.RIGHT,
- Edge.BOTTOM,
- Edge.LEFT,
- ]
- )
-
- @margin.setter
- def margin(self, value: str | Iterable[int]) -> None:
- for edge, mrgv in WaylandWindow.extract_margin(value).items():
- GtkLayerShell.set_margin(self, edge, mrgv)
- return
-
- @Property(object, "read-write")
- def keyboard_mode(self):
- kb_mode = GtkLayerShell.get_keyboard_mode(self)
- if GtkLayerShell.get_keyboard_interactivity(self):
- kb_mode = KeyboardMode.EXCLUSIVE
- return kb_mode
-
- @keyboard_mode.setter
- def keyboard_mode(
- self,
- value: Literal["none", "exclusive", "on-demand"] | KeyboardMode,
- ):
- return GtkLayerShell.set_keyboard_mode(
- self,
- get_enum_member(
- KeyboardMode,
- value,
- default=KeyboardMode.NONE,
- ),
- )
-
- def __init__(
- self,
- layer: Literal["background", "bottom", "top", "overlay"] | Layer = Layer.TOP,
- anchor: str = "",
- margin: str | Iterable[int] = "0px 0px 0px 0px",
- exclusivity: Literal["auto", "normal", "none"]
- | WaylandWindowExclusivity = WaylandWindowExclusivity.NONE,
- keyboard_mode: Literal["none", "exclusive", "on-demand"]
- | KeyboardMode = KeyboardMode.NONE,
- pass_through: bool = False,
- monitor: int | Gdk.Monitor | None = None,
- title: str = "fabric",
- type: Literal["top-level", "popup"] | Gtk.WindowType = Gtk.WindowType.TOPLEVEL,
- child: Gtk.Widget | None = None,
- name: str | None = None,
- visible: bool = True,
- all_visible: bool = False,
- style: str | None = None,
- style_classes: Iterable[str] | str | None = None,
- tooltip_text: str | None = None,
- tooltip_markup: str | None = None,
- h_align: Literal["fill", "start", "end", "center", "baseline"]
- | Gtk.Align
- | None = None,
- v_align: Literal["fill", "start", "end", "center", "baseline"]
- | Gtk.Align
- | None = None,
- h_expand: bool = False,
- v_expand: bool = False,
- size: Iterable[int] | int | None = None,
- **kwargs,
- ):
- Window.__init__(
- self,
- title=title,
- type=type,
- child=child,
- name=name,
- visible=False,
- all_visible=False,
- style=style,
- style_classes=style_classes,
- tooltip_text=tooltip_text,
- tooltip_markup=tooltip_markup,
- h_align=h_align,
- v_align=v_align,
- h_expand=h_expand,
- v_expand=v_expand,
- size=size,
- **kwargs,
- )
- self._layer = Layer.ENTRY_NUMBER
- self._keyboard_mode = KeyboardMode.NONE
- self._anchor = anchor
- self._exclusivity = WaylandWindowExclusivity.NONE
- self._pass_through = pass_through
-
- GtkLayerShell.init_for_window(self)
- GtkLayerShell.set_namespace(self, title)
- self.connect(
- "notify::title",
- lambda *_: GtkLayerShell.set_namespace(self, self.get_title()),
- )
- if monitor is not None:
- self.monitor = monitor
- self.layer = layer
- self.anchor = anchor
- self.margin = margin
- self.keyboard_mode = keyboard_mode
- self.exclusivity = exclusivity
- self.pass_through = pass_through
- self.show_all() if all_visible is True else self.show() if visible is True else None
-
- def steal_input(self) -> None:
- return GtkLayerShell.set_keyboard_interactivity(self, True)
-
- def return_input(self) -> None:
- return GtkLayerShell.set_keyboard_interactivity(self, False)
-
- # custom overrides
- def show(self) -> None:
- super().show()
- return self.do_handle_post_show_request()
-
- def show_all(self) -> None:
- super().show_all()
- return self.do_handle_post_show_request()
-
- def do_handle_post_show_request(self) -> None:
- if not self.get_children():
- logger.warning(
- "[WaylandWindow] showing an empty window is not recommended, some compositors might freak out."
- )
- self.pass_through = self._pass_through
- return
-
- @staticmethod
- def extract_anchor_values(string: str) -> tuple[str, ...]:
- """
- extracts the geometry values from a given geometry string.
-
- :param string: the string containing the geometry values.
- :type string: str
- :return: a list of unique directions extracted from the geometry string.
- :rtype: list
- """
- direction_map = {"l": "left", "t": "top", "r": "right", "b": "bottom"}
- pattern = re.compile(r"\b(left|right|top|bottom)\b", re.IGNORECASE)
- matches = pattern.findall(string)
- return tuple(set(tuple(direction_map[match.lower()[0]] for match in matches)))
-
- @staticmethod
- def extract_edges_from_string(string: str) -> dict["Edge", bool]:
- anchor_values = WaylandWindow.extract_anchor_values(string.lower())
- return {
- Edge.TOP: "top" in anchor_values,
- Edge.RIGHT: "right" in anchor_values,
- Edge.BOTTOM: "bottom" in anchor_values,
- Edge.LEFT: "left" in anchor_values,
- }
-
- @staticmethod
- def extract_margin(input: str | Iterable[int]) -> dict["Edge", int]:
- margins = (
- extract_css_values(input.lower())
- if isinstance(input, str)
- else input
- if isinstance(input, (tuple, list)) and len(input) == 4
- else (0, 0, 0, 0)
- )
- return {
- Edge.TOP: margins[0],
- Edge.RIGHT: margins[1],
- Edge.BOTTOM: margins[2],
- Edge.LEFT: margins[3],
- }