pnmixer
Volume mixer for the system tray
main.c
Go to the documentation of this file.
1 /* main.c
2  * PNmixer is written by Nick Lanham, a fork of OBmixer
3  * which was programmed by Lee Ferrett, derived
4  * from the program "AbsVolume" by Paul Sherman
5  * This program is free software; you can redistribute
6  * it and/or modify it under the terms of the GNU General
7  * Public License v3. source code is available at
8  * <http://github.com/nicklan/pnmixer>
9  */
10 
19 #ifdef HAVE_CONFIG_H
20 #include "config.h"
21 #endif
22 
23 #include <stdlib.h>
24 #include <glib.h>
25 
26 #include "main.h"
27 #include "audio.h"
28 #include "notif.h"
29 #include "hotkeys.h"
30 #include "prefs.h"
31 #include "support-intl.h"
32 #include "support-log.h"
33 #include "ui-about-dialog.h"
34 #include "ui-prefs-dialog.h"
35 #include "ui-popup-menu.h"
36 #include "ui-popup-window.h"
37 #include "ui-tray-icon.h"
38 
39 /* Life-long instances */
40 static Audio *audio;
44 static Hotkeys *hotkeys;
45 static Notif *notif;
46 
47 /* Main window, used as the parent for every other window that needs one.
48  * This is also a life-long instance.
49  */
50 static GtkWindow *main_window;
51 
52 /* Temporary instances */
55 
61 static void
62 run_command(const gchar *cmd)
63 {
64  GError *error = NULL;
65 
66  g_assert(cmd != NULL);
67 
68  if (g_spawn_command_line_async(cmd, &error) == FALSE) {
69  run_error_dialog(_("Unable to run command: %s"), error->message);
70  g_error_free(error);
71  error = NULL;
72  }
73 }
74 
78 void
80 {
81  gchar *cmd;
82 
83  cmd = prefs_get_string("VolumeControlCommand", NULL);
84 
85  if (cmd) {
86  run_command(cmd);
87  g_free(cmd);
88  } else {
89  run_error_dialog(_("No mixer application was found on your system. "
90  "Please open preferences and set the command you want "
91  "to run for volume control."));
92  }
93 }
94 
98 void
100 {
101  gchar *cmd;
102 
103  cmd = prefs_get_string("CustomCommand", NULL);
104 
105  if (cmd) {
106  run_command(cmd);
107  g_free(cmd);
108  } else {
109  run_error_dialog(_("You have not specified a custom command to run, "
110  "please specify one in preferences."));
111  }
112 }
113 
117 static void
118 prefs_dialog_response_cb(PrefsDialog *this_dialog, gint response_id)
119 {
120  g_assert(this_dialog == prefs_dialog);
121 
122  /* Get values from the prefs dialog */
123  if (response_id == GTK_RESPONSE_OK || response_id == GTK_RESPONSE_APPLY)
125 
126  if (response_id != GTK_RESPONSE_APPLY) {
127  /* Now we can destroy it */
129  prefs_dialog = NULL;
130  }
131 
132  /* Apply the new preferences.
133  * It's safer to do that after destroying the preferences dialog,
134  * since it listens for some audio signals that will be emitted
135  * while new prefs are applied.
136  */
137  if (response_id == GTK_RESPONSE_OK || response_id == GTK_RESPONSE_APPLY) {
138  /* Ask every instance to reload its preferences */
144 
145  /* Save preferences to file */
146  prefs_save();
147  }
148 }
149 
150 void
152 {
153  /* Create the prefs dialog if needed */
154  if (prefs_dialog == NULL) {
158  }
159 
160  /* Present it to user */
162 }
163 
167 void
169 {
170  /* Ensure there's no dialog already running */
171  if (about_dialog)
172  return;
173 
174  /* Run the about dialog */
178  about_dialog = NULL;
179 }
180 
187 void
188 run_error_dialog(const char *fmt, ...)
189 {
190  GtkWidget *dialog;
191  char err_buf[512];
192  va_list ap;
193 
194  va_start(ap, fmt);
195  vsnprintf(err_buf, sizeof err_buf, fmt, ap);
196  va_end(ap);
197 
198  ERROR("%s", err_buf);
199 
200  if (!main_window)
201  return;
202 
203  dialog = gtk_message_dialog_new(main_window,
204  GTK_DIALOG_DESTROY_WITH_PARENT,
205  GTK_MESSAGE_ERROR,
206  GTK_BUTTONS_CLOSE,
207  NULL);
208 
209  g_object_set(dialog, "text", err_buf, NULL);
210  gtk_window_set_title(GTK_WINDOW(dialog), _("PNMixer Error"));
211  gtk_dialog_run(GTK_DIALOG(dialog));
212  gtk_widget_destroy(dialog);
213 }
214 
222 gint
224 {
225  GtkWidget *dialog;
226  gint resp;
227 
228  ERROR("Connection with audio failed, "
229  "you probably need to restart pnmixer.");
230 
231  if (!main_window)
232  return GTK_RESPONSE_NO;
233 
234  dialog = gtk_message_dialog_new
235  (main_window,
236  GTK_DIALOG_DESTROY_WITH_PARENT,
237  GTK_MESSAGE_ERROR,
238  GTK_BUTTONS_YES_NO,
239  _("Warning: Connection to sound system failed."));
240 
241  gtk_message_dialog_format_secondary_text
242  (GTK_MESSAGE_DIALOG(dialog),
243  _("Do you want to re-initialize the audio connection ?\n\n"
244  "If you do not, you will either need to restart PNMixer "
245  "or select the 'Reload Audio' option in the right-click "
246  "menu in order for PNMixer to function."));
247 
248  gtk_window_set_title(GTK_WINDOW(dialog), _("PNMixer Error"));
249  resp = gtk_dialog_run(GTK_DIALOG(dialog));
250  gtk_widget_destroy(dialog);
251 
252  return resp;
253 }
254 
258 void
260 {
261  /* If a modal dialog is opened, we can open the popup window
262  * (the tray icon catches the mouse click), but we can't close it anymore
263  * (the popup window doesn't catch clicks, because the modal dialog does).
264  * A simple way to solve that is just to forbid showing the popup window
265  * when there's a modal dialog opened.
266  */
267  if (about_dialog)
268  return;
269 
271 }
272 
276 void
277 do_show_popup_menu(GtkMenuPositionFunc func, gpointer data, guint button, guint activate_time)
278 {
280  popup_menu_show(popup_menu, func, data, button, activate_time);
281 }
282 
290 static void
291 on_audio_changed(Audio *audio, AudioEvent *event, G_GNUC_UNUSED gpointer data)
292 {
293  switch (event->signal) {
296  break;
297  case AUDIO_CARD_ERROR:
298  if (run_audio_error_dialog() == GTK_RESPONSE_YES)
300  break;
301  default:
302  break;
303  }
304 
305 }
306 
307 /*
308  * Options for command-line invokation.
309  */
310 static gboolean version = FALSE;
311 static GOptionEntry option_entries[] = {
312  { "version", 'v', 0, G_OPTION_ARG_NONE, &version, "Show version and exit", NULL },
313  { "debug", 'd', 0, G_OPTION_ARG_NONE, &want_debug, "Run in debug mode", NULL },
314  { NULL, 0, 0, 0, NULL, NULL, NULL }
315 };
316 
325 int
326 main(int argc, char *argv[])
327 {
328  GOptionContext *context;
329 
330  /* Init internationalization stuff */
331  intl_init();
332 
333  /* Parse options */
334  context = g_option_context_new(_("- A mixer for the system tray."));
335  g_option_context_add_main_entries(context, option_entries, GETTEXT_PACKAGE);
336  g_option_context_add_group(context, gtk_get_option_group(TRUE));
337  g_option_context_parse(context, &argc, &argv, NULL);
338  g_option_context_free(context);
339 
340  /* Print version and exit */
341  if (version) {
342  printf(_("%s version: %s\n"), PACKAGE, PACKAGE_VERSION);
343  exit(EXIT_SUCCESS);
344  }
345 
346  /* Init Gtk+ */
347  gtk_init(&argc, &argv);
348 
349  /* Load preferences.
350  * This must be done at first, all the following init code rely on it.
351  */
353  prefs_load();
354 
355  /* Init the low-level (aka the audio system) at first */
356  audio = audio_new();
357 
358  /* Init the high-level (aka the ui) */
362 
363  /* Save the main window */
365 
366  /* Init what's left */
368  notif = notif_new(audio);
369 
370  /* Get the audio system ready */
373 
374  /* Run */
375  DEBUG("---- Running main loop ----");
376  gtk_main();
377  DEBUG("---- Exiting main loop ----");
378 
379  /* Cleanup */
381  notif_free(notif);
386  audio_free(audio);
387 
388  return EXIT_SUCCESS;
389 }
#define _(String)
Definition: support-intl.h:44
Internationalization support.
Logging support.
Header for audio.c.
int main(int argc, char *argv[])
Definition: main.c:326
void hotkeys_free(Hotkeys *hotkeys)
Definition: hotkeys.c:228
void notif_reload(G_GNUC_UNUSED Notif *notif)
Definition: notif.c:290
Header for hotkeys.c.
static Audio * audio
Definition: main.c:40
PopupWindow * popup_window_create(Audio *audio)
void audio_signals_disconnect(Audio *audio, AudioCallback callback, gpointer data)
Definition: audio.c:318
PopupMenu * popup_menu_create(Audio *audio)
void audio_signals_connect(Audio *audio, AudioCallback callback, gpointer data)
Definition: audio.c:337
void prefs_dialog_present(PrefsDialog *dialog)
void hotkeys_reload(Hotkeys *hotkeys)
Definition: hotkeys.c:121
Header for prefs.c.
gboolean want_debug
Definition: support-log.c:38
static PrefsDialog * prefs_dialog
Definition: main.c:53
void prefs_dialog_retrieve(PrefsDialog *dialog)
header for ui-about-dialog.c.
static GOptionEntry option_entries[]
Definition: main.c:311
void tray_icon_destroy(TrayIcon *icon)
Definition: ui-tray-icon.c:565
static void prefs_dialog_response_cb(PrefsDialog *this_dialog, gint response_id)
Definition: main.c:118
#define DEBUG(...)
Definition: support-log.h:38
void about_dialog_run(AboutDialog *dialog)
Header for main.c.
static PopupWindow * popup_window
Definition: main.c:42
static void on_audio_changed(Audio *audio, AudioEvent *event, G_GNUC_UNUSED gpointer data)
Definition: main.c:291
void popup_window_toggle(PopupWindow *window)
Header for ui-popup-menu.c.
void popup_window_destroy(PopupWindow *window)
static gboolean version
Definition: main.c:310
PrefsDialog * prefs_dialog_create(GtkWindow *parent, Audio *audio, Hotkeys *hotkeys, PrefsDialogResponseCallback cb)
void run_mixer_command(void)
Definition: main.c:79
Audio * audio_new(void)
Definition: audio.c:710
void run_custom_command(void)
Definition: main.c:99
Definition: audio.c:198
AboutDialog * about_dialog_create(GtkWindow *parent)
static AboutDialog * about_dialog
Definition: main.c:54
static TrayIcon * tray_icon
Definition: main.c:43
AudioSignal signal
Definition: audio.h:74
#define ERROR(...)
Definition: support-log.h:36
void prefs_save(void)
Definition: prefs.c:381
Notif * notif_new(G_GNUC_UNUSED Audio *audio)
Definition: notif.c:284
void popup_menu_show(PopupMenu *menu, GTK_3_22_UNUSED GtkMenuPositionFunc func, GTK_3_22_UNUSED gpointer data, GTK_3_22_UNUSED guint button, GTK_3_22_UNUSED guint activate_time)
void do_show_popup_menu(GtkMenuPositionFunc func, gpointer data, guint button, guint activate_time)
Definition: main.c:277
gchar * prefs_get_string(const gchar *key, const gchar *def)
Definition: prefs.c:171
void prefs_load(void)
Definition: prefs.c:344
void prefs_ensure_save_dir(void)
Definition: prefs.c:405
void run_prefs_dialog(void)
Definition: main.c:151
static GtkWindow * main_window
Definition: main.c:50
Hotkeys * hotkeys_new(Audio *audio)
Definition: hotkeys.c:250
Header for ui-prefs-dialog.c.
void run_error_dialog(const char *fmt,...)
Definition: main.c:188
GtkWindow * popup_menu_get_window(PopupMenu *menu)
void about_dialog_destroy(AboutDialog *dialog)
static void run_command(const gchar *cmd)
Definition: main.c:62
void intl_init(void)
Definition: support-intl.c:27
Header for ui-popup-window.c.
static Hotkeys * hotkeys
Definition: main.c:44
void tray_icon_reload(TrayIcon *icon)
Definition: ui-tray-icon.c:536
void prefs_dialog_populate(PrefsDialog *dialog)
static Notif * notif
Definition: main.c:45
static PopupMenu * popup_menu
Definition: main.c:41
TrayIcon * tray_icon_create(Audio *audio)
Definition: ui-tray-icon.c:582
void audio_free(Audio *audio)
Definition: audio.c:691
void popup_window_reload(PopupWindow *window)
Header for notif.c.
void audio_reload(Audio *audio)
Definition: audio.c:669
gint run_audio_error_dialog(void)
Definition: main.c:223
void popup_window_hide(PopupWindow *window)
void popup_menu_destroy(PopupMenu *menu)
struct notif Notif
Definition: notif.h:24
void notif_free(G_GNUC_UNUSED Notif *notif)
Definition: notif.c:279
void run_about_dialog(void)
Definition: main.c:168
Header for ui-tray-icon.c.
void prefs_dialog_destroy(PrefsDialog *dialog)
void do_toggle_popup_window(void)
Definition: main.c:259