1{ config, lib, pkgs, ... }:
2
3with lib;
4
5let
6 cfg = config.programs.phosh;
7
8 # Based on https://source.puri.sm/Librem5/librem5-base/-/blob/4596c1056dd75ac7f043aede07887990fd46f572/default/sm.puri.OSK0.desktop
9 oskItem = pkgs.makeDesktopItem {
10 name = "sm.puri.OSK0";
11 type = "Application";
12 desktopName = "On-screen keyboard";
13 exec = "${pkgs.squeekboard}/bin/squeekboard";
14 categories = "GNOME;Core;";
15 extraEntries = ''
16 OnlyShowIn=GNOME;
17 NoDisplay=true
18 X-GNOME-Autostart-Phase=Panel
19 X-GNOME-Provides=inputmethod
20 X-GNOME-Autostart-Notify=true
21 X-GNOME-AutoRestart=true
22 '';
23 };
24
25 phocConfigType = types.submodule {
26 options = {
27 xwayland = mkOption {
28 description = ''
29 Whether to enable XWayland support.
30
31 To start XWayland immediately, use `immediate`.
32 '';
33 type = types.enum [ "true" "false" "immediate" ];
34 default = "false";
35 };
36 cursorTheme = mkOption {
37 description = ''
38 Cursor theme to use in Phosh.
39 '';
40 type = types.str;
41 default = "default";
42 };
43 outputs = mkOption {
44 description = ''
45 Output configurations.
46 '';
47 type = types.attrsOf phocOutputType;
48 default = {
49 DSI-1 = {
50 scale = 2;
51 };
52 };
53 };
54 };
55 };
56
57 phocOutputType = types.submodule {
58 options = {
59 modeline = mkOption {
60 description = ''
61 One or more modelines.
62 '';
63 type = types.either types.str (types.listOf types.str);
64 default = [];
65 example = [
66 "87.25 720 776 848 976 1440 1443 1453 1493 -hsync +vsync"
67 "65.13 768 816 896 1024 1024 1025 1028 1060 -HSync +VSync"
68 ];
69 };
70 mode = mkOption {
71 description = ''
72 Default video mode.
73 '';
74 type = types.nullOr types.str;
75 default = null;
76 example = "768x1024";
77 };
78 scale = mkOption {
79 description = ''
80 Display scaling factor.
81 '';
82 type = types.nullOr types.ints.unsigned;
83 default = null;
84 example = 2;
85 };
86 rotate = mkOption {
87 description = ''
88 Screen transformation.
89 '';
90 type = types.enum [
91 "90" "180" "270" "flipped" "flipped-90" "flipped-180" "flipped-270" null
92 ];
93 default = null;
94 };
95 };
96 };
97
98 optionalKV = k: v: if v == null then "" else "${k} = ${builtins.toString v}";
99
100 renderPhocOutput = name: output: let
101 modelines = if builtins.isList output.modeline
102 then output.modeline
103 else [ output.modeline ];
104 renderModeline = l: "modeline = ${l}";
105 in ''
106 [output:${name}]
107 ${concatStringsSep "\n" (map renderModeline modelines)}
108 ${optionalKV "mode" output.mode}
109 ${optionalKV "scale" output.scale}
110 ${optionalKV "rotate" output.rotate}
111 '';
112
113 renderPhocConfig = phoc: let
114 outputs = mapAttrsToList renderPhocOutput phoc.outputs;
115 in ''
116 [core]
117 xwayland = ${phoc.xwayland}
118 ${concatStringsSep "\n" outputs}
119 [cursor]
120 theme = ${phoc.cursorTheme}
121 '';
122in {
123 options = {
124 programs.phosh = {
125 enable = mkEnableOption ''
126 Whether to enable, Phosh, related packages and default configurations.
127 '';
128 phocConfig = mkOption {
129 description = ''
130 Configurations for the Phoc compositor.
131 '';
132 type = types.oneOf [ types.lines types.path phocConfigType ];
133 default = {};
134 };
135 };
136 };
137
138 config = mkIf cfg.enable {
139 environment.systemPackages = [
140 pkgs.phoc
141 pkgs.phosh
142 pkgs.squeekboard
143 oskItem
144 ];
145
146 programs.feedbackd.enable = true;
147
148 security.pam.services.phosh = {};
149
150 services.gnome.core-shell.enable = true;
151 services.gnome.core-os-services.enable = true;
152 services.xserver.displayManager.sessionPackages = [ pkgs.phosh ];
153
154 environment.etc."phosh/phoc.ini".source =
155 if builtins.isPath cfg.phocConfig then cfg.phocConfig
156 else if builtins.isString cfg.phocConfig then pkgs.writeText "phoc.ini" cfg.phocConfig
157 else pkgs.writeText "phoc.ini" (renderPhocConfig cfg.phocConfig);
158 };
159}