From fcee8ea9e3a1b031af71072ffd3e2d08ca872c3c Mon Sep 17 00:00:00 2001 From: Youwen Wu Date: Sun, 3 Aug 2025 16:12:08 +0800 Subject: [PATCH] feat: add chinese hanyu input methods --- .../desktop-environment/hyprland/default.nix | 18 +- .../hyprland/windowrules.nix | 182 +++++++++--------- modules/linux/default.nix | 1 + modules/linux/localization/default.nix | 143 ++++++++++++++ reference/modules/default.nix | 2 + 5 files changed, 256 insertions(+), 90 deletions(-) create mode 100644 modules/linux/localization/default.nix diff --git a/hm/modules/linux/desktop-environment/hyprland/default.nix b/hm/modules/linux/desktop-environment/hyprland/default.nix index 16f0faf..30d3049 100644 --- a/hm/modules/linux/desktop-environment/hyprland/default.nix +++ b/hm/modules/linux/desktop-environment/hyprland/default.nix @@ -65,6 +65,13 @@ in Whether to enable `hyprsunset` as a daemon. ''; }; + fcitx5.enable = lib.mkOption { + type = lib.types.bool; + default = osConfig.i18n.inputMethod.enable && (osConfig.i18n.inputMethod.type == "fcitx5"); + description = '' + Whether to execute fcitx5 at startup. + ''; + }; }; config = lib.mkIf cfg.enable { @@ -86,9 +93,14 @@ in enable = true; plugins = [ pkgs.hyprlandPlugins.hyprscroller ]; settings = { - exec-once = [ - "hyprctl dispatch workspace 100000" - ]; + exec-once = + [ + "hyprctl dispatch workspace 100000" + ] + ++ (lib.optionals cfg.fcitx5.enable [ + "fcitx5 -d -r" + "fcitx5-remote -r" + ]); "$mod" = "SUPER"; "$Left" = "H"; "$Right" = "L"; diff --git a/hm/modules/linux/desktop-environment/hyprland/windowrules.nix b/hm/modules/linux/desktop-environment/hyprland/windowrules.nix index c67d657..aa23c4a 100644 --- a/hm/modules/linux/desktop-environment/hyprland/windowrules.nix +++ b/hm/modules/linux/desktop-environment/hyprland/windowrules.nix @@ -1,97 +1,105 @@ { config, lib, ... }: +let + cfg = config.liminalOS.desktop.hyprland; +in { config.wayland.windowManager.hyprland.settings.windowrulev2 = lib.mkIf config.liminalOS.desktop.hyprland.enable - [ - "opacity 0.90 0.90,class:^(librewolf)$" - "opacity 0.90 0.90,class:^(floorp)$" - "opacity 0.90 0.90,class:^(firefox)$" - "opacity 0.90 0.90,class:^(zen-alpha)$" - "opacity 0.90 0.90,class:^(zen-beta)$" - "opacity 0.90 0.90,class:^(zen)$" - "opacity 0.90 0.90,class:^(Brave-browser)$" - "opacity 0.80 0.80,class:^(Steam)$" - "opacity 0.80 0.80,class:^(steam)$" - "opacity 0.80 0.80,class:^(steamwebhelper)$" - "opacity 0.80 0.80,class:^(Spotify)$" - "opacity 0.80 0.80,initialTitle:^(Spotify Premium)$" - "opacity 0.80 0.80,initialTitle:^(Spotify Free)$" - "opacity 0.80 0.80,class:^(code-oss)$" - "opacity 0.80 0.80,class:^(Code)$" - "opacity 0.80 0.80,class:^(code-url-handler)$" - "opacity 0.80 0.80,class:^(code-insiders-url-handler)$" - "opacity 0.80 0.80,class:^(kitty)$" - "opacity 0.80 0.80,class:^(neovide)$" - "opacity 0.80 0.80,class:^(org.kde.dolphin)$" - "opacity 0.80 0.80,class:^(org.gnome.Nautilus)$" - "opacity 0.80 0.80,class:^(org.kde.ark)$" - "opacity 0.80 0.80,class:^(nwg-look)$" - "opacity 0.80 0.80,class:^(qt5ct)$" - "opacity 0.80 0.80,class:^(qt6ct)$" - "opacity 0.80 0.80,class:^(kvantummanager)$" - "opacity 0.80 0.80,class:^(waypaper)$" - "opacity 0.80 0.80,class:^(org.pulseaudio.pavucontrol)$" - "opacity 0.80 0.80,class:^(com.github.wwmm.easyeffects)$" - "opacity 0.80 0.80,class:^(thunderbird)$" + ( + [ + "opacity 0.90 0.90,class:^(librewolf)$" + "opacity 0.90 0.90,class:^(floorp)$" + "opacity 0.90 0.90,class:^(firefox)$" + "opacity 0.90 0.90,class:^(zen-alpha)$" + "opacity 0.90 0.90,class:^(zen-beta)$" + "opacity 0.90 0.90,class:^(zen)$" + "opacity 0.90 0.90,class:^(Brave-browser)$" + "opacity 0.80 0.80,class:^(Steam)$" + "opacity 0.80 0.80,class:^(steam)$" + "opacity 0.80 0.80,class:^(steamwebhelper)$" + "opacity 0.80 0.80,class:^(Spotify)$" + "opacity 0.80 0.80,initialTitle:^(Spotify Premium)$" + "opacity 0.80 0.80,initialTitle:^(Spotify Free)$" + "opacity 0.80 0.80,class:^(code-oss)$" + "opacity 0.80 0.80,class:^(Code)$" + "opacity 0.80 0.80,class:^(code-url-handler)$" + "opacity 0.80 0.80,class:^(code-insiders-url-handler)$" + "opacity 0.80 0.80,class:^(kitty)$" + "opacity 0.80 0.80,class:^(neovide)$" + "opacity 0.80 0.80,class:^(org.kde.dolphin)$" + "opacity 0.80 0.80,class:^(org.gnome.Nautilus)$" + "opacity 0.80 0.80,class:^(org.kde.ark)$" + "opacity 0.80 0.80,class:^(nwg-look)$" + "opacity 0.80 0.80,class:^(qt5ct)$" + "opacity 0.80 0.80,class:^(qt6ct)$" + "opacity 0.80 0.80,class:^(kvantummanager)$" + "opacity 0.80 0.80,class:^(waypaper)$" + "opacity 0.80 0.80,class:^(org.pulseaudio.pavucontrol)$" + "opacity 0.80 0.80,class:^(com.github.wwmm.easyeffects)$" + "opacity 0.80 0.80,class:^(thunderbird)$" - "opacity 0.90 0.90,class:^(com.github.rafostar.Clapper)$ # Clapper-Gtk" - "opacity 0.80 0.80,class:^(com.github.tchx84.Flatseal)$ # Flatseal-Gtk" - "opacity 0.80 0.80,class:^(hu.kramo.Cartridges)$ # Cartridges-Gtk" - "opacity 0.80 0.80,class:^(com.obsproject.Studio)$ # Obs-Qt" - "opacity 0.80 0.80,class:^(gnome-boxes)$ # Boxes-Gtk" - "opacity 0.80 0.80,class:^(discord)$ # Discord-Electron" - "opacity 0.80 0.80,class:^(vesktop)$ # Vesktop-Electron" - "opacity 0.80 0.80,class:^(Element)$ # Vesktop-Electron" - "opacity 0.80 0.80,class:^(ArmCord)$ # ArmCord-Electron" - "opacity 0.80 0.80,class:^(app.drey.Warp)$ # Warp-Gtk" - "opacity 0.80 0.80,class:^(net.davidotek.pupgui2)$ # ProtonUp-Qt" - "opacity 0.80 0.80,class:^(yad)$ # Protontricks-Gtk" - "opacity 0.80 0.80,class:^(signal)$ # Signal-Gtk" - "opacity 0.80 0.80,class:^(io.github.alainm23.planify)$ # planify-Gtk" - "opacity 0.80 0.80,class:^(io.gitlab.theevilskeleton.Upscaler)$ # Upscaler-Gtk" - "opacity 0.80 0.80,class:^(com.github.unrud.VideoDownloader)$ # VideoDownloader-Gtk" - "opacity 0.80 0.80,class:^(lutris)$ # Lutris game launcher" + "opacity 0.90 0.90,class:^(com.github.rafostar.Clapper)$ # Clapper-Gtk" + "opacity 0.80 0.80,class:^(com.github.tchx84.Flatseal)$ # Flatseal-Gtk" + "opacity 0.80 0.80,class:^(hu.kramo.Cartridges)$ # Cartridges-Gtk" + "opacity 0.80 0.80,class:^(com.obsproject.Studio)$ # Obs-Qt" + "opacity 0.80 0.80,class:^(gnome-boxes)$ # Boxes-Gtk" + "opacity 0.80 0.80,class:^(discord)$ # Discord-Electron" + "opacity 0.80 0.80,class:^(vesktop)$ # Vesktop-Electron" + "opacity 0.80 0.80,class:^(Element)$ # Vesktop-Electron" + "opacity 0.80 0.80,class:^(ArmCord)$ # ArmCord-Electron" + "opacity 0.80 0.80,class:^(app.drey.Warp)$ # Warp-Gtk" + "opacity 0.80 0.80,class:^(net.davidotek.pupgui2)$ # ProtonUp-Qt" + "opacity 0.80 0.80,class:^(yad)$ # Protontricks-Gtk" + "opacity 0.80 0.80,class:^(signal)$ # Signal-Gtk" + "opacity 0.80 0.80,class:^(io.github.alainm23.planify)$ # planify-Gtk" + "opacity 0.80 0.80,class:^(io.gitlab.theevilskeleton.Upscaler)$ # Upscaler-Gtk" + "opacity 0.80 0.80,class:^(com.github.unrud.VideoDownloader)$ # VideoDownloader-Gtk" + "opacity 0.80 0.80,class:^(lutris)$ # Lutris game launcher" - "opacity 0.80 0.70,class:^(pavucontrol)$" - "opacity 0.80 0.70,class:^(blueman-manager)$" - "opacity 0.80 0.70,class:^(nm-applet)$" - "opacity 0.80 0.70,class:^(nm-connection-editor)$" - "opacity 0.80 0.70,class:^(org.kde.polkit-kde-authentication-agent-1)$" + "opacity 0.80 0.70,class:^(pavucontrol)$" + "opacity 0.80 0.70,class:^(blueman-manager)$" + "opacity 0.80 0.70,class:^(nm-applet)$" + "opacity 0.80 0.70,class:^(nm-connection-editor)$" + "opacity 0.80 0.70,class:^(org.kde.polkit-kde-authentication-agent-1)$" - "float,class:^(org.kde.dolphin)$,title:^(Progress Dialog — Dolphin)$" - "float,class:^(org.kde.dolphin)$,title:^(Copying — Dolphin)$" - "float,title:^(Picture-in-Picture)$" - "float,class:^(librewolf)$,title:^(Library)$" - "float,class:^(floorp)$,title:^(Library)$" - "float,class:^(zen-alpha)$,title:^(Library)$" - "float,class:^(zen-beta)$,title:^(Library)$" - "float,class:^(zen)$,title:^(Library)$" - ''float,class:^(zen)$,title:^(.*Extension: \(Bitwarden Password Manager\).*)$'' - "float,class:^(vlc)$" - "float,class:^(kvantummanager)$" - "float,class:^(qt5ct)$" - "float,class:^(qt6ct)$" - "float,class:^(nwg-look)$" - "float,class:^(org.kde.ark)$" - "float,class:^(org.pulseaudio.pavucontrol)$" - "float,class:^(com.github.rafostar.Clapper)$ # Clapper-Gtk" - "float,class:^(app.drey.Warp)$ # Warp-Gtk" - "float,class:^(net.davidotek.pupgui2)$ # ProtonUp-Qt" - "float,class:^(yad)$ # Protontricks-Gtk" - "float,class:^(eog)$ # Imageviewer-Gtk" - "float,class:^(io.github.alainm23.planify)$ # planify-Gtk" - "float,class:^(io.gitlab.theevilskeleton.Upscaler)$ # Upscaler-Gtk" - "float,class:^(com.github.unrud.VideoDownloader)$ # VideoDownloader-Gkk" - "float,class:^(blueman-manager)$" - "float,class:^(nm-applet)$" - "float,class:^(nm-connection-editor)$" - "float,class:^(org.kde.polkit-kde-authentication-agent-1)$" - "opacity 0.80 0.80,class:^(org.freedesktop.impl.portal.desktop.gtk)$" - "opacity 0.80 0.80,class:^(org.freedesktop.impl.portal.desktop.hyprland)$" + "float,class:^(org.kde.dolphin)$,title:^(Progress Dialog — Dolphin)$" + "float,class:^(org.kde.dolphin)$,title:^(Copying — Dolphin)$" + "float,title:^(Picture-in-Picture)$" + "float,class:^(librewolf)$,title:^(Library)$" + "float,class:^(floorp)$,title:^(Library)$" + "float,class:^(zen-alpha)$,title:^(Library)$" + "float,class:^(zen-beta)$,title:^(Library)$" + "float,class:^(zen)$,title:^(Library)$" + ''float,class:^(zen)$,title:^(.*Extension: \(Bitwarden Password Manager\).*)$'' + "float,class:^(vlc)$" + "float,class:^(kvantummanager)$" + "float,class:^(qt5ct)$" + "float,class:^(qt6ct)$" + "float,class:^(nwg-look)$" + "float,class:^(org.kde.ark)$" + "float,class:^(org.pulseaudio.pavucontrol)$" + "float,class:^(com.github.rafostar.Clapper)$ # Clapper-Gtk" + "float,class:^(app.drey.Warp)$ # Warp-Gtk" + "float,class:^(net.davidotek.pupgui2)$ # ProtonUp-Qt" + "float,class:^(yad)$ # Protontricks-Gtk" + "float,class:^(eog)$ # Imageviewer-Gtk" + "float,class:^(io.github.alainm23.planify)$ # planify-Gtk" + "float,class:^(io.gitlab.theevilskeleton.Upscaler)$ # Upscaler-Gtk" + "float,class:^(com.github.unrud.VideoDownloader)$ # VideoDownloader-Gkk" + "float,class:^(blueman-manager)$" + "float,class:^(nm-applet)$" + "float,class:^(nm-connection-editor)$" + "float,class:^(org.kde.polkit-kde-authentication-agent-1)$" + "opacity 0.80 0.80,class:^(org.freedesktop.impl.portal.desktop.gtk)$" + "opacity 0.80 0.80,class:^(org.freedesktop.impl.portal.desktop.hyprland)$" - ''size 70% 70%,class:^(zen)$,title:^(.*Extension: \(Bitwarden Password Manager\).*)$'' - "size 50% 50%,class:^(org.pulseaudio.pavucontrol)" + ''size 70% 70%,class:^(zen)$,title:^(.*Extension: \(Bitwarden Password Manager\).*)$'' + "size 50% 50%,class:^(org.pulseaudio.pavucontrol)" - "stayfocused, class:^(pinentry-)" # fix pinentry losing focus - ]; + "stayfocused, class:^(pinentry-)" # fix pinentry losing focus + ] + ++ (lib.optionals cfg.fcitx5.enable [ + "pseudo, class:^(fcitx)" + ]) + ); } diff --git a/modules/linux/default.nix b/modules/linux/default.nix index 9a4067a..6ad9ede 100644 --- a/modules/linux/default.nix +++ b/modules/linux/default.nix @@ -15,6 +15,7 @@ ./wine ./wsl ./graphics + ./localization ]; options.liminalOS.enable = lib.mkOption { diff --git a/modules/linux/localization/default.nix b/modules/linux/localization/default.nix new file mode 100644 index 0000000..6e7e999 --- /dev/null +++ b/modules/linux/localization/default.nix @@ -0,0 +1,143 @@ +{ + pkgs, + lib, + config, + ... +}: +let + cfg = config.liminalOS.desktop.localization; +in +{ + options.liminalOS.desktop.localization = { + chinese.input.enable = lib.mkEnableOption "Chinese input method using fcitx5."; + chinese.script = lib.mkOption { + type = lib.types.enum [ + "simplified" + "traditional" + ]; + default = "simplified"; + description = '' + Whether to use simplified or traditional characters. + ''; + }; + }; + + config = lib.mkIf cfg.chinese.input.enable { + i18n.inputMethod = { + enable = true; + type = "fcitx5"; + fcitx5.waylandFrontend = true; + fcitx5.addons = with pkgs; [ + fcitx5-gtk + fcitx5-chinese-addons + fcitx5-tokyonight + ]; + fcitx5.settings.globalOptions = { + Hotkey = { + # Enumerate when press trigger key repeatedly + EnumerateWithTriggerKeys = true; + # Temporally switch between first and current Input Method + AltTriggerKeys = null; + # Enumerate Input Method Forward + EnumerateForwardKeys = null; + # Enumerate Input Method Backward + EnumerateBackwardKeys = null; + # Skip first input method while enumerating + EnumerateSkipFirst = false; + # Enumerate Input Method Group Forward + EnumerateGroupForwardKeys = null; + # Enumerate Input Method Group Backward + EnumerateGroupBackwardKeys = null; + # Activate Input Method + ActivateKeys = null; + # Deactivate Input Method + DeactivateKeys = null; + # Default Previous page + PrevPage = null; + # Default Next page + NextPage = null; + # Default Previous Candidate + PrevCandidate = null; + # Default Next Candidate + NextCandidate = null; + # Toggle embedded preedit + TogglePreedit = null; + # Time limit in milliseconds for triggering modifier key shortcuts + ModifierOnlyKeyTimeout = 250; + }; + + "Hotkey/TriggerKeys" = { + "0" = "Control+Super+space"; + "1" = "Zenkaku_Hankaku"; + "2" = "Hangul"; + }; + + Behavior = { + # Active By Default; + ActiveByDefault = false; + # Reset state on Focus In; + resetStateWhenFocusIn = "No"; + # Share Input State; + ShareInputState = "No"; + # Show preedit in application; + PreeditEnabledByDefault = true; + # Show Input Method Information when switch input method; + ShowInputMethodInformation = true; + # Show Input Method Information when changing focus; + showInputMethodInformationWhenFocusIn = false; + # Show compact input method information; + CompactInputMethodInformation = true; + # Show first input method information; + ShowFirstInputMethodInformation = true; + # Default page size; + DefaultPageSize = 10; + # Override Xkb Option; + OverrideXkbOption = false; + # Custom Xkb Option; + CustomXkbOption = ""; + # Force Enabled Addons; + EnabledAddons = ""; + # Force Disabled Addons; + DisabledAddons = ""; + # Preload input method to be used by default; + PreloadInputMethod = true; + # Allow input method in the password field; + AllowInputMethodForPassword = false; + # Show preedit text when typing password; + ShowPreeditForPassword = false; + # Interval of saving user data in minutes; + AutoSavePeriod = 30; + + }; + }; + fcitx5.settings.inputMethod = { + "Groups/0" = { + # Group Name + Name = "Default"; + # Layout + "Default Layout" = "us"; + # Default Input Method + DefaultIM = "pinyin"; + }; + "Groups/0/Items/0" = { + # Name + Name = "keyboard-us"; + # Layout + Layout = ""; + }; + + "Groups/0/Items/1" = { + # Name + Name = "pinyin"; + # Layout + Layout = ""; + }; + + "GroupOrder" = { + "0" = "Default"; + }; + + }; + }; + }; +} diff --git a/reference/modules/default.nix b/reference/modules/default.nix index 305437e..912a193 100644 --- a/reference/modules/default.nix +++ b/reference/modules/default.nix @@ -144,6 +144,8 @@ polarity = lib.mkDefault "dark"; }; + liminalOS.desktop.localization.chinese.input.enable = true; + environment.etc.polarity.text = lib.mkDefault "dusk"; specialisation = {