1 /*
2  * LavaLauncher - A simple launcher panel for Wayland
3  *
4  * Copyright (C) 2020 - 2021 Leon Henrik Plickat
5  *
6  * This program is free software: you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation, either version 3 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program.  If not, see <https://www.gnu.org/licenses/>.
18  */
19 
20 #define _POSIX_C_SOURCE 200809L
21 
22 #include<stdio.h>
23 #include<stdlib.h>
24 #include<unistd.h>
25 #include<stdbool.h>
26 #include<string.h>
27 #include<getopt.h>
28 
29 #include"bar.h"
30 #include"config.h"
31 #include"event-loop.h"
32 #include"lavalauncher.h"
33 #include"str.h"
34 #include"wayland-connection.h"
35 #include"misc-event-sources.h"
36 
37 /* The context is used basically everywhere. So instead of passing pointers
38  * around, just have it global.
39  */
40 struct Lava_context context = {0};
41 
handle_command_flags(int argc,char * argv[])42 static bool handle_command_flags (int argc, char *argv[])
43 {
44 	const char usage[] =
45 		"Usage: lavalauncher [options...]\n"
46 		"  -c <path>, --config <path> Path to config file.\n"
47 		"  -h,        --help          Print this help text.\n"
48 		"  -v,        --verbose       Enable verbose output.\n"
49 		"  -V,        --version       Show version.\n"
50 		"\n"
51 		"The configuration syntax is documented in the man page.\n";
52 
53 	static struct option opts[] = {
54 		{"config",  required_argument, NULL, 'c'},
55 		{"help",    no_argument,       NULL, 'h'},
56 		{"verbose", no_argument,       NULL, 'v'},
57 		{"version", no_argument,       NULL, 'V'},
58 		{0,         0,                 0,    0  }
59 	};
60 
61 	extern int optind;
62 	optind = 0;
63 	extern char *optarg;
64 	for (int c; (c = getopt_long(argc, argv, "c:hvV", opts, &optind)) != -1 ;) switch (c)
65 	{
66 		case 'c':
67 			set_string(&context.config_path, optarg);
68 			break;
69 
70 		case 'h':
71 			fputs(usage, stderr);
72 			context.ret = EXIT_SUCCESS;
73 			return false;
74 
75 		case 'v':
76 			context.verbosity++;
77 			break;
78 
79 		case 'V':
80 			fputs("LavaLauncher version " LAVALAUNCHER_VERSION"\n", stderr);
81 			context.ret = EXIT_SUCCESS;
82 			return false;
83 
84 		default:
85 			return false;
86 	}
87 
88 	return true;
89 }
90 
get_default_config_path(void)91 static bool get_default_config_path (void)
92 {
93 	struct
94 	{
95 		const char *fmt;
96 		const char *env;
97 	} paths[] = {
98 		{ .fmt = "./lavalauncher.conf",                           .env = NULL                      },
99 		{ .fmt = "%s/lavalauncher/lavalauncher.conf",             .env = getenv("XDG_CONFIG_HOME") },
100 		{ .fmt = "%s/.config/lavalauncher/lavalauncher.conf",     .env = getenv("HOME")            },
101 		{ .fmt = "/usr/local/etc/lavalauncher/lavalauncher.conf", .env = NULL                      },
102 		{ .fmt = "/etc/lavalauncher/lavalauncher.conf",           .env = NULL                      }
103 	};
104 
105 	FOR_ARRAY(paths, i)
106 	{
107 		context.config_path = get_formatted_buffer(paths[i].fmt, paths[i].env);
108 		if (! access(context.config_path, F_OK | R_OK))
109 		{
110 			log_message(1, "[main] Using default configuration file path: %s\n", context.config_path);
111 			return true;
112 		}
113 		free_if_set(context.config_path);
114 	}
115 
116 	log_message(0, "ERROR: Can not find configuration file.\n"
117 			"INFO: You can provide a path manually with '-c'.\n");
118 	return false;
119 }
120 
init_context(void)121 static void init_context (void)
122 {
123 	context.ret         = EXIT_FAILURE;
124 	context.loop        = true;
125 	context.reload      = false;
126 	context.verbosity   = 0;
127 	context.config_path = NULL;
128 
129 #if WATCH_CONFIG
130 	context.watch = false;
131 #endif
132 
133 	context.display            = NULL;
134 	context.registry           = NULL;
135 
136 	context.compositor         = NULL;
137 	context.subcompositor      = NULL;
138 	context.shm                = NULL;
139 	context.layer_shell        = NULL;
140 	context.xdg_output_manager = NULL;
141 
142 	context.river_status_manager = NULL;
143 	context.need_river_status    = false;
144 
145 	context.need_keyboard = false;
146 	context.need_pointer  = false;
147 	context.need_touch    = false;
148 
149 	wl_list_init(&context.bars);
150 	context.last_bar = NULL;
151 
152 	wl_list_init(&context.outputs);
153 	wl_list_init(&context.seats);
154 }
155 
main(int argc,char * argv[])156 int main (int argc, char *argv[])
157 {
158 reload:
159 	init_context();
160 
161 	if (! handle_command_flags(argc, argv))
162 		return context.ret;
163 
164 	log_message(1, "[main] LavaLauncher: version=%s\n", LAVALAUNCHER_VERSION);
165 
166 	/* If the user did not provide the path to a configuration file, try
167 	 * the default location.
168 	 */
169 	if ( context.config_path == NULL )
170 		if (! get_default_config_path())
171 			return EXIT_FAILURE;
172 
173 	/* Try to parse the configuration file. If this fails, there might
174 	 * already be heap objects, so some cleanup is needed.
175 	 */
176 	if (! parse_config_file())
177 		goto exit;
178 
179 	context.ret = EXIT_SUCCESS;
180 
181 	/* Set up the event loop and attach all event sources. */
182 	struct Lava_event_loop loop;
183 	event_loop_init(&loop);
184 	event_loop_add_event_source(&loop, &wayland_source);
185 #if WATCH_CONFIG
186 	if (context.watch)
187 		event_loop_add_event_source(&loop, &inotify_source);
188 #endif
189 #if HANDLE_SIGNALS
190 	event_loop_add_event_source(&loop, &signal_source);
191 #endif
192 
193 	/* Run the event loop. */
194 	if (! event_loop_run(&loop))
195 		context.ret = EXIT_FAILURE;
196 
197 exit:
198 	free(context.config_path);
199 
200 	/* Clean up objects created when parsing the configuration file. */
201 	destroy_all_bars();
202 
203 	if (context.reload)
204 		goto reload;
205 	return context.ret;
206 }
207 
208