diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c8a29e75..b7cb8f8b 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,9 +50,10 @@ Dependencies * autotools, gettext * intltool, libtool +* libX11 * libdrm (Optional, for DRM support) * libxcb, libxcb-randr (Optional, for RandR support) -* libX11, libXxf86vm (Optional, for VidMode support) +* libXxf86vm (Optional, for VidMode support) * Glib 2 (Optional, for GeoClue2 support) * python3, pygobject, pyxdg (Optional, for GUI support) diff --git a/po/POTFILES.in b/po/POTFILES.in index 5ef8dacc..985990d0 100644 --- a/po/POTFILES.in +++ b/po/POTFILES.in @@ -7,6 +7,7 @@ data/applications/redshift-gtk.desktop.in src/redshift.c src/options.c src/config-ini.c +src/fullscreen.c src/gamma-drm.c src/gamma-randr.c diff --git a/src/Makefile.am b/src/Makefile.am index 8aa96ead..9f58d5ff 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -19,7 +19,8 @@ redshift_SOURCES = \ redshift.c redshift.h \ signals.c signals.h \ solar.c solar.h \ - systemtime.c systemtime.h + systemtime.c systemtime.h \ + fullscreen.c fullscreen.h EXTRA_redshift_SOURCES = \ gamma-drm.c gamma-drm.h \ diff --git a/src/fullscreen.c b/src/fullscreen.c new file mode 100644 index 00000000..2c0ba018 --- /dev/null +++ b/src/fullscreen.c @@ -0,0 +1,93 @@ +/* fullscreen.h -- Fullscreen detector + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see . + + Copyright (c) 2021 Angelo Elias Dalzotto +*/ + +#ifdef HAVE_CONFIG_H +# include "config.h" +#endif + +#include +#include +#include +#include + +#ifdef ENABLE_NLS +# include +# define _(s) gettext(s) +#else +# define _(s) s +#endif + +#ifndef _WIN32 +# include +#endif + +#include "fullscreen.h" +#include "redshift.h" + +#ifndef _WIN32 +static Display *display; +#endif + +static int +fullscreen_init() +{ +#ifndef _WIN32 + display = XOpenDisplay(NULL); + if (display == NULL) { + fprintf(stderr, _("X request failed: %s\n"), "XOpenDisplay"); + return -1; + } +#endif + + return 0; +} + +static int +fullscreen_check() +{ +#ifndef _WIN32 + Window window; + int revert_to = RevertToParent; + int result = XGetInputFocus(display, &window, &revert_to); + + int win_x, win_y, win_w, win_h, win_b, win_d; + int scr_x, scr_y, scr_w, scr_h, scr_b, scr_d; + if (result) { + Window rootWindow; + result = XGetGeometry(display, window, &rootWindow, &win_x, &win_y, &win_w, &win_h, &win_b, &win_d); + if (rootWindow) { + result = XGetGeometry(display, rootWindow, &rootWindow, &scr_x, &scr_y, &scr_w, &scr_h, &scr_b, &scr_d); + } + } + + if (result && win_w == scr_w && win_h == scr_h) { + return 1; + } else { +#endif + return 0; +#ifndef _WIN32 + } +#endif +} + +const fullscreen_t fullscreen = { + "fullscreen", + (fullscreen_init_func *)fullscreen_init, + (fullscreen_check_func *)fullscreen_check, +}; diff --git a/src/fullscreen.h b/src/fullscreen.h new file mode 100644 index 00000000..f12683af --- /dev/null +++ b/src/fullscreen.h @@ -0,0 +1,28 @@ +/* fullscreen.h -- Fullscreen detector header + This file is part of Redshift. + + Redshift is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + Redshift is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with Redshift. If not, see . + + Copyright (c) 2021 Angelo Elias Dalzotto +*/ + +#ifndef REDSHIFT_FULLSCREEN_H +#define REDSHIFT_FULLSCREEN_H + +#include "redshift.h" + +extern const fullscreen_t fullscreen; + +#endif /* ! REDSHIFT_FULLSCREEN_H */ + diff --git a/src/options.c b/src/options.c index 33bf623a..58e0d1b0 100644 --- a/src/options.c +++ b/src/options.c @@ -178,6 +178,7 @@ print_help(const char *program_name) no-wrap */ fputs(_(" -b DAY:NIGHT\tScreen brightness to apply (between 0.1 and 1.0)\n" " -c FILE\tLoad settings from specified configuration file\n" + " -f BOOL\tEnable or disable fullscreen windows bypass\n" " -g R:G:B\tAdditional gamma correction to apply\n" " -l LAT:LON\tYour current location\n" " -l PROVIDER\tSelect provider for automatic" @@ -323,6 +324,7 @@ options_init(options_t *options) options->preserve_gamma = 1; options->mode = PROGRAM_MODE_CONTINUAL; options->verbose = 0; + options->fullscreen_check = 1; } /* Parse a single option from the command-line. */ @@ -345,6 +347,9 @@ parse_command_line_option( free(options->config_filepath); options->config_filepath = strdup(value); break; + case 'f': + options->fullscreen_check = atoi(value); + break; case 'g': r = parse_gamma_string(value, options->scheme.day.gamma); if (r < 0) { @@ -495,7 +500,7 @@ options_parse_args( { const char* program_name = argv[0]; int opt; - while ((opt = getopt(argc, argv, "b:c:g:hl:m:oO:pPrt:vVx")) != -1) { + while ((opt = getopt(argc, argv, "b:c:f:g:hl:m:oO:pPrt:vVx")) != -1) { char option = opt; int r = parse_command_line_option( option, optarg, options, program_name, gamma_methods, diff --git a/src/options.h b/src/options.h index 9993a07f..2e331db5 100644 --- a/src/options.h +++ b/src/options.h @@ -29,6 +29,7 @@ typedef struct { transition_scheme_t scheme; program_mode_t mode; int verbose; + int fullscreen_check; /* Temperature to set in manual mode. */ int temp_set; diff --git a/src/redshift-gtk/controller.py b/src/redshift-gtk/controller.py index 24c58ae7..b3704caf 100644 --- a/src/redshift-gtk/controller.py +++ b/src/redshift-gtk/controller.py @@ -34,6 +34,7 @@ class RedshiftController(GObject.GObject): __gsignals__ = { 'inhibit-changed': (GObject.SIGNAL_RUN_FIRST, None, (bool,)), + 'fs_bypass_inhibit-changed': (GObject.SIGNAL_RUN_FIRST, None, (bool,)), 'temperature-changed': (GObject.SIGNAL_RUN_FIRST, None, (int,)), 'period-changed': (GObject.SIGNAL_RUN_FIRST, None, (str,)), 'location-changed': (GObject.SIGNAL_RUN_FIRST, None, (float, float)), @@ -51,6 +52,7 @@ def __init__(self, args): # Initialize state variables self._inhibited = False + self._fs_bypass_inhibited = False self._temperature = 0 self._period = 'Unknown' self._location = (0.0, 0.0) @@ -112,6 +114,11 @@ def relay_signal_handler(signal): def inhibited(self): """Current inhibition state.""" return self._inhibited + + @property + def fs_bypass_inhibited(self): + """Current fullscreen bypass inhibition state.""" + return self._fs_bypass_inhibited @property def temperature(self): @@ -133,6 +140,11 @@ def set_inhibit(self, inhibit): if inhibit != self._inhibited: self._child_toggle_inhibit() + def set_fs_bypass_inhibit(self, fs_bypass_inhibit): + """Set inhibition state.""" + if fs_bypass_inhibit != self._fs_bypass_inhibited: + self._child_toggle_fs_bypass_inhibit() + def _child_signal(self, sg): """Send signal to child process.""" os.kill(self._process[0], sg) @@ -141,6 +153,10 @@ def _child_toggle_inhibit(self): """Sends a request to the child process to toggle state.""" self._child_signal(signal.SIGUSR1) + def _child_toggle_fs_bypass_inhibit(self): + """Sends a request to the child process to toggle fullscreen bypass state.""" + self._child_signal(signal.SIGUSR2) + def _child_cb(self, pid, status, data=None): """Called when the child process exists.""" @@ -175,6 +191,11 @@ def parse_coord(s): if new_inhibited != self._inhibited: self._inhibited = new_inhibited self.emit('inhibit-changed', new_inhibited) + elif key == 'Fullscreen bypass': + new_fs_bypass_inhibited = value != 'Enabled' + if new_fs_bypass_inhibited != self._fs_bypass_inhibited: + self._fs_bypass_inhibited = new_fs_bypass_inhibited + self.emit('fs_bypass_inhibit-changed', new_fs_bypass_inhibited) elif key == 'Color temperature': new_temperature = int(value.rstrip('K'), 10) if new_temperature != self._temperature: diff --git a/src/redshift-gtk/statusicon.py b/src/redshift-gtk/statusicon.py index b4adfb00..f6705fa0 100644 --- a/src/redshift-gtk/statusicon.py +++ b/src/redshift-gtk/statusicon.py @@ -93,6 +93,13 @@ def __init__(self, controller): suspend_menu_item.set_submenu(suspend_menu) self.status_menu.append(suspend_menu_item) + # Add fullscreen bypass toggle + self.fs_bypass_toggle_item = Gtk.CheckMenuItem.new_with_label( + _('Fullscreen bypass')) + self.fs_bypass_toggle_item.connect( + 'activate', self.fs_bypass_toggle_item_cb) + self.status_menu.append(self.fs_bypass_toggle_item) + # Add autostart option if utils.supports_autostart(): autostart_item = Gtk.CheckMenuItem.new_with_label(_('Autostart')) @@ -157,6 +164,8 @@ def __init__(self, controller): # Setup signals to property changes self._controller.connect('inhibit-changed', self.inhibit_change_cb) + self._controller.connect( + 'fs_bypass_inhibit-changed', self.fs_bypass_inhibit_change_cb) self._controller.connect('period-changed', self.period_change_cb) self._controller.connect( 'temperature-changed', self.temperature_change_cb) @@ -167,6 +176,7 @@ def __init__(self, controller): # Set info box text self.change_inhibited(self._controller.inhibited) + self.change_fs_bypass_inhibited(self._controller.fs_bypass_inhibited) self.change_period(self._controller.period) self.change_temperature(self._controller.temperature) self.change_location(self._controller.location) @@ -235,6 +245,17 @@ def toggle_item_cb(self, widget, data=None): self.remove_suspend_timer() self._controller.set_inhibit(not self._controller.inhibited) + def fs_bypass_toggle_item_cb(self, widget, data=None): + """Callback when a request to toggle fullscreen bypass was made. + + This ensures that the state of redshift is synchronised with + the toggle state of the widget (e.g. Gtk.CheckMenuItem). + """ + active = not self._controller.fs_bypass_inhibited + if active != widget.get_active(): + self._controller.set_fs_bypass_inhibit( + not self._controller.fs_bypass_inhibited) + # Info dialog callbacks def show_info_cb(self, widget, data=None): """Callback when the info dialog should be presented.""" @@ -276,6 +297,10 @@ def inhibit_change_cb(self, controller, inhibit): """Callback when controller changes inhibition status.""" self.change_inhibited(inhibit) + def fs_bypass_inhibit_change_cb(self, controller, fs_bypass_inhibit): + """Callback when controller changes inhibition status.""" + self.change_fs_bypass_inhibited(fs_bypass_inhibit) + def period_change_cb(self, controller, period): """Callback when controller changes period.""" self.change_period(period) @@ -313,6 +338,13 @@ def change_inhibited(self, inhibited): _('Status: {}').format( _('Disabled') if inhibited else _('Enabled'))) + def change_fs_bypass_inhibited(self, fs_bypass_inhibited): + """Change interface to new fullscreen bypass inhibition status.""" + self.fs_bypass_toggle_item.set_active(not fs_bypass_inhibited) + self.status_label.set_markup( + _('Fullscreen Bypass: {}').format( + _('Disabled') if fs_bypass_inhibited else _('Enabled'))) + def change_temperature(self, temperature): """Change interface to new temperature.""" self.temperature_label.set_markup( diff --git a/src/redshift.c b/src/redshift.c index d2ba577c..f1885fe9 100644 --- a/src/redshift.c +++ b/src/redshift.c @@ -66,6 +66,7 @@ int poll(struct pollfd *fds, int nfds, int timeout) { abort(); return -1; } #include "hooks.h" #include "signals.h" #include "options.h" +#include "fullscreen.h" /* pause() is not defined on windows platform but is not needed either. Use a noop macro instead. */ @@ -608,7 +609,7 @@ run_continual_mode(const location_provider_t *provider, const transition_scheme_t *scheme, const gamma_method_t *method, gamma_state_t *method_state, - int use_fade, int preserve_gamma, int verbose) + int use_fade, int preserve_gamma, int fullscreen_check, int verbose) { int r; @@ -662,11 +663,18 @@ run_continual_mode(const location_provider_t *provider, printf(_("Brightness: %.2f\n"), interp.brightness); } + if (fullscreen_check) { + fullscreen.init(); + } + /* Continuously adjust color temperature */ int done = 0; int prev_disabled = 1; int disabled = 0; int location_available = 1; + int prev_fs_bypass_disabled = 1; + int fs_bypass_disabled = 0; + int is_fullscreen = 0; while (1) { /* Check to see if disable signal was caught */ if (disable && !done) { @@ -674,6 +682,12 @@ run_continual_mode(const location_provider_t *provider, disable = 0; } + /* Check to see if fs_disable signal was caught */ + if (fs_bypass_disable && !done){ + fs_bypass_disabled = !fs_bypass_disabled; + fs_bypass_disable = 0; + } + /* Check to see if exit signal was caught */ if (exiting) { if (done) { @@ -686,13 +700,26 @@ run_continual_mode(const location_provider_t *provider, exiting = 0; } + if (fullscreen.check() && !done && !fs_bypass_disabled) { + is_fullscreen = 1; + } else { + is_fullscreen = 0; + } + /* Print status change */ if (verbose && disabled != prev_disabled) { printf(_("Status: %s\n"), disabled ? _("Disabled") : _("Enabled")); } + /* Print fullscreen bypass enable/disable change */ + if (verbose && fs_bypass_disabled != prev_fs_bypass_disabled) { + printf(_("Fullscreen bypass: %s\n"), fs_bypass_disabled ? + _("Disabled") : _("Enabled")); + } + prev_disabled = disabled; + prev_fs_bypass_disabled = fs_bypass_disabled; /* Read timestamp */ double now; @@ -727,7 +754,7 @@ run_continual_mode(const location_provider_t *provider, interpolate_transition_scheme( scheme, transition_prog, &target_interp); - if (disabled) { + if (disabled || is_fullscreen) { period = PERIOD_NONE; color_setting_reset(&target_interp); } @@ -1308,6 +1335,7 @@ main(int argc, char *argv[]) options.provider, location_state, scheme, options.method, method_state, options.use_fade, options.preserve_gamma, + options.fullscreen_check, options.verbose); if (r < 0) exit(EXIT_FAILURE); } diff --git a/src/redshift.h b/src/redshift.h index 0282d839..242a3966 100644 --- a/src/redshift.h +++ b/src/redshift.h @@ -150,4 +150,18 @@ typedef struct { } location_provider_t; +/* Fullscreen detector */ +typedef int fullscreen_init_func(); +typedef int fullscreen_check_func(); + +typedef struct { + char *name; + + /* Initialize display. */ + fullscreen_init_func *init; + + /* Check if active window is fullscreen. */ + fullscreen_check_func *check; +} fullscreen_t; + #endif /* ! REDSHIFT_REDSHIFT_H */ diff --git a/src/signals.c b/src/signals.c index cee5ece1..b4af48b3 100644 --- a/src/signals.c +++ b/src/signals.c @@ -34,6 +34,7 @@ volatile sig_atomic_t exiting = 0; volatile sig_atomic_t disable = 0; +volatile sig_atomic_t fs_bypass_disable = 0; /* Signal handler for exit signals */ @@ -50,10 +51,18 @@ sigdisable(int signo) disable = 1; } +/* Signal handler for disabling the fullscreen detector signal */ +static void +sigfsbypassdisable(int signo) +{ + fs_bypass_disable = 1; +} + #else /* ! HAVE_SIGNAL_H || __WIN32__ */ int disable = 0; int exiting = 0; +int fs_bypass_disable = 0; #endif /* ! HAVE_SIGNAL_H || __WIN32__ */ @@ -95,6 +104,17 @@ signals_install_handlers(void) return -1; } + /* Install signal handler for USR2 signal */ + sigact.sa_handler = sigfsbypassdisable; + sigact.sa_mask = sigset; + sigact.sa_flags = 0; + + r = sigaction(SIGUSR2, &sigact, NULL); + if (r < 0) { + perror("sigaction"); + return -1; + } + /* Ignore CHLD signal. This causes child processes (hooks) to be reaped automatically. */ sigact.sa_handler = SIG_IGN; diff --git a/src/signals.h b/src/signals.h index 7a1d22ee..3c65d79c 100644 --- a/src/signals.h +++ b/src/signals.h @@ -25,10 +25,12 @@ extern volatile sig_atomic_t exiting; extern volatile sig_atomic_t disable; +extern volatile sig_atomic_t fs_bypass_disable; #else /* ! HAVE_SIGNAL_H || __WIN32__ */ extern int exiting; extern int disable; +extern int fs_bypass_disable; #endif /* ! HAVE_SIGNAL_H || __WIN32__ */