Fortran · 9424 bytes Raw Blame History
1 module config_mod
2 use cell_mod, only: color_t, COLOR_RGB
3 use toml_parser_mod
4 implicit none
5 private
6
7 public :: config_t
8 public :: config_load, config_get_path, config_init_defaults
9
10 type :: config_t
11 ! Window
12 integer :: window_width = 800
13 integer :: window_height = 600
14 real :: window_opacity = 1.0 ! 0.0 (transparent) to 1.0 (opaque)
15 logical :: window_blur = .false. ! macOS only: enable background blur
16
17 ! Font
18 character(len=256) :: font_path = '' ! Empty = use fontconfig
19 character(len=256) :: font_fallback = '' ! Empty = auto-detect
20 integer :: font_size = 16
21
22 ! Colors
23 type(color_t) :: fg_color
24 type(color_t) :: bg_color
25 type(color_t) :: cursor_color
26 type(color_t) :: palette(0:15)
27
28 ! Terminal
29 integer :: scrollback_lines = 10000
30
31 ! Cursor
32 integer :: cursor_style = 0 ! 0=block, 1=underline, 2=bar
33 logical :: cursor_blink = .true. ! Enable cursor blinking
34
35 ! Shell
36 character(len=256) :: shell_program = ''
37
38 ! State
39 logical :: loaded = .false.
40 end type config_t
41
42 contains
43
44 ! Initialize config with default values
45 subroutine config_init_defaults(cfg)
46 type(config_t), intent(inout) :: cfg
47
48 cfg%window_width = 800
49 cfg%window_height = 600
50 cfg%window_opacity = 1.0
51 cfg%window_blur = .false.
52
53 cfg%font_path = ''
54 cfg%font_fallback = ''
55 cfg%font_size = 16
56
57 ! Default colors (white on dark gray)
58 cfg%fg_color = color_t(COLOR_RGB, 7, 255, 255, 255)
59 cfg%bg_color = color_t(COLOR_RGB, 0, 26, 26, 30) ! #1A1A1E
60 cfg%cursor_color = color_t(COLOR_RGB, 7, 179, 179, 179) ! #B3B3B3
61
62 ! Default 16-color palette (VGA-ish)
63 cfg%palette(0) = make_rgb_color(0, 0, 0) ! Black
64 cfg%palette(1) = make_rgb_color(128, 0, 0) ! Red
65 cfg%palette(2) = make_rgb_color(0, 128, 0) ! Green
66 cfg%palette(3) = make_rgb_color(128, 128, 0) ! Yellow
67 cfg%palette(4) = make_rgb_color(0, 0, 128) ! Blue
68 cfg%palette(5) = make_rgb_color(128, 0, 128) ! Magenta
69 cfg%palette(6) = make_rgb_color(0, 128, 128) ! Cyan
70 cfg%palette(7) = make_rgb_color(192, 192, 192) ! White
71 cfg%palette(8) = make_rgb_color(128, 128, 128) ! Bright Black
72 cfg%palette(9) = make_rgb_color(255, 0, 0) ! Bright Red
73 cfg%palette(10) = make_rgb_color(0, 255, 0) ! Bright Green
74 cfg%palette(11) = make_rgb_color(255, 255, 0) ! Bright Yellow
75 cfg%palette(12) = make_rgb_color(0, 0, 255) ! Bright Blue
76 cfg%palette(13) = make_rgb_color(255, 0, 255) ! Bright Magenta
77 cfg%palette(14) = make_rgb_color(0, 255, 255) ! Bright Cyan
78 cfg%palette(15) = make_rgb_color(255, 255, 255) ! Bright White
79
80 cfg%scrollback_lines = 10000
81 cfg%cursor_style = 0 ! block
82 cfg%cursor_blink = .true.
83 cfg%shell_program = ''
84 cfg%loaded = .false.
85
86 end subroutine config_init_defaults
87
88 ! Get the path to the config file (XDG or ~/.config)
89 function config_get_path() result(path)
90 character(len=512) :: path
91 character(len=256) :: xdg_config, home
92 integer :: len_val
93 logical :: file_exists
94
95 path = ''
96
97 ! Try XDG_CONFIG_HOME first
98 call get_environment_variable('XDG_CONFIG_HOME', xdg_config, len_val)
99 if (len_val > 0) then
100 path = trim(xdg_config) // '/fortty/fortty.toml'
101 inquire(file=path, exist=file_exists)
102 if (file_exists) return
103 end if
104
105 ! Fall back to ~/.config
106 call get_environment_variable('HOME', home, len_val)
107 if (len_val > 0) then
108 path = trim(home) // '/.config/fortty/fortty.toml'
109 inquire(file=path, exist=file_exists)
110 if (file_exists) return
111 end if
112
113 ! No config file found
114 path = ''
115
116 end function config_get_path
117
118 ! Load configuration from file
119 function config_load(path) result(cfg)
120 character(len=*), intent(in) :: path
121 type(config_t) :: cfg
122 type(toml_file_t) :: tf
123 character(len=512) :: config_path
124 character(len=256) :: str_val
125
126 ! Initialize with defaults
127 call config_init_defaults(cfg)
128
129 ! If no path provided, try to find config file
130 if (len_trim(path) == 0) then
131 config_path = config_get_path()
132 if (len_trim(config_path) == 0) then
133 ! No config file found, use defaults
134 return
135 end if
136 tf = toml_open(trim(config_path))
137 else
138 tf = toml_open(path)
139 end if
140
141 if (.not. tf%loaded) return
142
143 ! Window settings
144 cfg%window_width = toml_get_integer(tf, 'window', 'width', cfg%window_width)
145 cfg%window_height = toml_get_integer(tf, 'window', 'height', cfg%window_height)
146 cfg%window_opacity = toml_get_real(tf, 'window', 'opacity', cfg%window_opacity)
147 cfg%window_blur = toml_get_logical(tf, 'window', 'blur', cfg%window_blur)
148
149 ! Font settings
150 str_val = toml_get_string(tf, 'font', 'family', '')
151 if (len_trim(str_val) > 0) cfg%font_path = str_val
152 str_val = toml_get_string(tf, 'font', 'fallback', '')
153 if (len_trim(str_val) > 0) cfg%font_fallback = str_val
154 cfg%font_size = toml_get_integer(tf, 'font', 'size', cfg%font_size)
155
156 ! Color settings
157 str_val = toml_get_string(tf, 'colors', 'foreground', '')
158 if (len_trim(str_val) > 0) cfg%fg_color = parse_hex_color(str_val)
159 str_val = toml_get_string(tf, 'colors', 'background', '')
160 if (len_trim(str_val) > 0) cfg%bg_color = parse_hex_color(str_val)
161 str_val = toml_get_string(tf, 'colors', 'cursor', '')
162 if (len_trim(str_val) > 0) cfg%cursor_color = parse_hex_color(str_val)
163
164 ! Palette colors
165 call load_palette_color(tf, 'black', cfg%palette(0))
166 call load_palette_color(tf, 'red', cfg%palette(1))
167 call load_palette_color(tf, 'green', cfg%palette(2))
168 call load_palette_color(tf, 'yellow', cfg%palette(3))
169 call load_palette_color(tf, 'blue', cfg%palette(4))
170 call load_palette_color(tf, 'magenta', cfg%palette(5))
171 call load_palette_color(tf, 'cyan', cfg%palette(6))
172 call load_palette_color(tf, 'white', cfg%palette(7))
173 call load_palette_color(tf, 'bright_black', cfg%palette(8))
174 call load_palette_color(tf, 'bright_red', cfg%palette(9))
175 call load_palette_color(tf, 'bright_green', cfg%palette(10))
176 call load_palette_color(tf, 'bright_yellow', cfg%palette(11))
177 call load_palette_color(tf, 'bright_blue', cfg%palette(12))
178 call load_palette_color(tf, 'bright_magenta', cfg%palette(13))
179 call load_palette_color(tf, 'bright_cyan', cfg%palette(14))
180 call load_palette_color(tf, 'bright_white', cfg%palette(15))
181
182 ! Terminal settings
183 cfg%scrollback_lines = toml_get_integer(tf, 'terminal', 'scrollback_lines', cfg%scrollback_lines)
184
185 ! Cursor settings
186 str_val = toml_get_string(tf, 'cursor', 'style', '')
187 if (len_trim(str_val) > 0) then
188 select case (trim(str_val))
189 case ('block')
190 cfg%cursor_style = 0
191 case ('underline')
192 cfg%cursor_style = 1
193 case ('bar', 'beam')
194 cfg%cursor_style = 2
195 end select
196 end if
197 cfg%cursor_blink = toml_get_logical(tf, 'cursor', 'blink', cfg%cursor_blink)
198
199 ! Shell settings
200 str_val = toml_get_string(tf, 'shell', 'program', '')
201 if (len_trim(str_val) > 0) cfg%shell_program = str_val
202
203 call toml_close(tf)
204 cfg%loaded = .true.
205
206 end function config_load
207
208 ! Helper: Load a palette color from TOML
209 subroutine load_palette_color(tf, key, color)
210 type(toml_file_t), intent(in) :: tf
211 character(len=*), intent(in) :: key
212 type(color_t), intent(inout) :: color
213 character(len=256) :: str_val
214
215 str_val = toml_get_string(tf, 'colors', key, '')
216 if (len_trim(str_val) > 0) then
217 color = parse_hex_color(str_val)
218 end if
219 end subroutine load_palette_color
220
221 ! Parse a hex color string (#RRGGBB) to color_t
222 function parse_hex_color(hex_str) result(c)
223 character(len=*), intent(in) :: hex_str
224 type(color_t) :: c
225 character(len=2) :: r_str, g_str, b_str
226 integer :: r, g, b, start_pos
227
228 c = color_t(COLOR_RGB, 0, 255, 255, 255) ! Default white
229
230 ! Skip leading # if present
231 if (hex_str(1:1) == '#') then
232 start_pos = 2
233 else
234 start_pos = 1
235 end if
236
237 ! Need at least 6 characters for RRGGBB
238 if (len_trim(hex_str) < start_pos + 5) return
239
240 r_str = hex_str(start_pos:start_pos+1)
241 g_str = hex_str(start_pos+2:start_pos+3)
242 b_str = hex_str(start_pos+4:start_pos+5)
243
244 r = hex_to_int(r_str)
245 g = hex_to_int(g_str)
246 b = hex_to_int(b_str)
247
248 c = make_rgb_color(r, g, b)
249
250 end function parse_hex_color
251
252 ! Convert 2-character hex string to integer
253 function hex_to_int(hex_str) result(val)
254 character(len=2), intent(in) :: hex_str
255 integer :: val
256 integer :: i, digit
257 character(len=1) :: ch
258
259 val = 0
260 do i = 1, 2
261 ch = hex_str(i:i)
262 if (ch >= '0' .and. ch <= '9') then
263 digit = ichar(ch) - ichar('0')
264 else if (ch >= 'a' .and. ch <= 'f') then
265 digit = ichar(ch) - ichar('a') + 10
266 else if (ch >= 'A' .and. ch <= 'F') then
267 digit = ichar(ch) - ichar('A') + 10
268 else
269 digit = 0
270 end if
271 val = val * 16 + digit
272 end do
273
274 end function hex_to_int
275
276 ! Helper: Create an RGB color
277 function make_rgb_color(r, g, b) result(c)
278 integer, intent(in) :: r, g, b
279 type(color_t) :: c
280
281 c%mode = COLOR_RGB
282 c%index = 0
283 c%r = r
284 c%g = g
285 c%b = b
286 end function make_rgb_color
287
288 end module config_mod
289