Fortran · 8993 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 ! Shell settings
186 str_val = toml_get_string(tf, 'shell', 'program', '')
187 if (len_trim(str_val) > 0) cfg%shell_program = str_val
188
189 call toml_close(tf)
190 cfg%loaded = .true.
191
192 end function config_load
193
194 ! Helper: Load a palette color from TOML
195 subroutine load_palette_color(tf, key, color)
196 type(toml_file_t), intent(in) :: tf
197 character(len=*), intent(in) :: key
198 type(color_t), intent(inout) :: color
199 character(len=256) :: str_val
200
201 str_val = toml_get_string(tf, 'colors', key, '')
202 if (len_trim(str_val) > 0) then
203 color = parse_hex_color(str_val)
204 end if
205 end subroutine load_palette_color
206
207 ! Parse a hex color string (#RRGGBB) to color_t
208 function parse_hex_color(hex_str) result(c)
209 character(len=*), intent(in) :: hex_str
210 type(color_t) :: c
211 character(len=2) :: r_str, g_str, b_str
212 integer :: r, g, b, start_pos
213
214 c = color_t(COLOR_RGB, 0, 255, 255, 255) ! Default white
215
216 ! Skip leading # if present
217 if (hex_str(1:1) == '#') then
218 start_pos = 2
219 else
220 start_pos = 1
221 end if
222
223 ! Need at least 6 characters for RRGGBB
224 if (len_trim(hex_str) < start_pos + 5) return
225
226 r_str = hex_str(start_pos:start_pos+1)
227 g_str = hex_str(start_pos+2:start_pos+3)
228 b_str = hex_str(start_pos+4:start_pos+5)
229
230 r = hex_to_int(r_str)
231 g = hex_to_int(g_str)
232 b = hex_to_int(b_str)
233
234 c = make_rgb_color(r, g, b)
235
236 end function parse_hex_color
237
238 ! Convert 2-character hex string to integer
239 function hex_to_int(hex_str) result(val)
240 character(len=2), intent(in) :: hex_str
241 integer :: val
242 integer :: i, digit
243 character(len=1) :: ch
244
245 val = 0
246 do i = 1, 2
247 ch = hex_str(i:i)
248 if (ch >= '0' .and. ch <= '9') then
249 digit = ichar(ch) - ichar('0')
250 else if (ch >= 'a' .and. ch <= 'f') then
251 digit = ichar(ch) - ichar('a') + 10
252 else if (ch >= 'A' .and. ch <= 'F') then
253 digit = ichar(ch) - ichar('A') + 10
254 else
255 digit = 0
256 end if
257 val = val * 16 + digit
258 end do
259
260 end function hex_to_int
261
262 ! Helper: Create an RGB color
263 function make_rgb_color(r, g, b) result(c)
264 integer, intent(in) :: r, g, b
265 type(color_t) :: c
266
267 c%mode = COLOR_RGB
268 c%index = 0
269 c%r = r
270 c%g = g
271 c%b = b
272 end function make_rgb_color
273
274 end module config_mod
275