Skip to content

Commit 3645b56

Browse files
committed
Add visual feedback for screenshots (X11 & Wayland)
1 parent 40022c3 commit 3645b56

File tree

5 files changed

+113
-1
lines changed

5 files changed

+113
-1
lines changed

package/batocera/core/batocera-resolution/batocera-resolution.mk

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
#
55
################################################################################
66

7-
BATOCERA_RESOLUTION_VERSION = 1.5
7+
BATOCERA_RESOLUTION_VERSION = 1.6
88
BATOCERA_RESOLUTION_LICENSE = GPL
99
BATOCERA_RESOLUTION_DEPENDENCIES = pciutils
1010
BATOCERA_RESOLUTION_SOURCE=
@@ -37,6 +37,7 @@ endif
3737
define BATOCERA_RESOLUTION_INSTALL_TARGET_CMDS
3838
install -m 0755 $(BATOCERA_RESOLUTION_PATH)/resolution/batocera-resolution.$(BATOCERA_SCRIPT_TYPE) $(TARGET_DIR)/usr/bin/batocera-resolution
3939
install -m 0755 $(BATOCERA_RESOLUTION_PATH)/screenshot/batocera-screenshot.$(BATOCERA_SCRIPT_TYPE) $(TARGET_DIR)/usr/bin/batocera-screenshot
40+
install -m 0755 $(BATOCERA_RESOLUTION_PATH)/screenshot/batocera-flash-screen.py $(TARGET_DIR)/usr/bin/batocera-flash-screen
4041
endef
4142

4243
define BATOCERA_RESOLUTION_INSTALL_RK3128
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
#!/usr/bin/env python3
2+
#
3+
# This file is part of the batocera distribution (https://batocera.org).
4+
# Copyright (c) 2025 lbrpdx for the Batocera team
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, version 3.
9+
#
10+
# You should have received a copy of the GNU General Public License
11+
# along with this program. If not, see <http://www.gnu.org/licenses/>.
12+
#
13+
# YOU MUST KEEP THIS HEADER AS IT IS
14+
#
15+
import os
16+
import sys
17+
import time
18+
os.environ.setdefault("NO_AT_BRIDGE", "1")
19+
import gi
20+
gi.require_version('Gtk', '3.0')
21+
gi.require_version('Gdk', '3.0')
22+
from gi.repository import Gtk, Gdk
23+
24+
def ensure_display():
25+
# Ensures we're running inside a GUI session (Wayland or X11).
26+
# Wayland compositors set WAYLAND_DISPLAY; X11 sets DISPLAY
27+
if os.environ.get("WAYLAND_DISPLAY") or os.environ.get("DISPLAY"):
28+
return True
29+
return False
30+
31+
def gtk_init_check():
32+
try:
33+
# Gtk.init_check returns (initialized: bool, argv: list) in PyGObject
34+
ok, _ = Gtk.init_check(sys.argv)
35+
return bool(ok)
36+
except Exception:
37+
return False
38+
39+
def flash(duration_seconds: float = 0.1, color: str = "#ffffff"):
40+
# Show a fullscreen window filled with `color` for `duration_seconds`
41+
if not ensure_display():
42+
raise RuntimeError(
43+
"ERROR: No GUI display detected. Set DISPLAY (X11) or WAYLAND_DISPLAY (Wayland)."
44+
)
45+
46+
if not gtk_init_check():
47+
raise RuntimeError(
48+
"ERROR: Gtk couldn't be initialized."
49+
)
50+
51+
# Create the fullscreen borderless window
52+
win = Gtk.Window(type=Gtk.WindowType.TOPLEVEL)
53+
win.set_title("Screenshot taken")
54+
win.set_decorated(False)
55+
win.set_app_paintable(True)
56+
win.fullscreen()
57+
box = Gtk.Box(orientation=Gtk.Orientation.VERTICAL)
58+
box.set_hexpand(True)
59+
box.set_vexpand(True)
60+
win.add(box)
61+
css = Gtk.CssProvider()
62+
css.load_from_data(f"""
63+
window, box {{
64+
background-color: {color};
65+
}}
66+
""".encode("utf-8"))
67+
screen = Gdk.Screen.get_default()
68+
Gtk.StyleContext.add_provider_for_screen(
69+
screen, css, Gtk.STYLE_PROVIDER_PRIORITY_APPLICATION
70+
)
71+
# Close on any key or mouse to avoid getting stuck
72+
def close(_w, *_a):
73+
Gtk.main_quit()
74+
win.connect("key-press-event", close)
75+
win.connect("button-press-event", close)
76+
win.show_all()
77+
start = time.monotonic()
78+
while time.monotonic() - start < duration_seconds:
79+
while Gtk.events_pending():
80+
Gtk.main_iteration_do(False)
81+
time.sleep(0.004)
82+
Gtk.main_quit()
83+
84+
def parse_args():
85+
# Arguments: duration and color
86+
duration = 0.1
87+
color = "#ffffff"
88+
if len(sys.argv) >= 2:
89+
try:
90+
duration = float(sys.argv[1])
91+
except ValueError:
92+
pass
93+
if len(sys.argv) >= 3:
94+
color = sys.argv[2]
95+
return duration, color
96+
97+
if __name__ == "__main__":
98+
dur, col = parse_args()
99+
try:
100+
flash(dur, col)
101+
except RuntimeError as e:
102+
sys.stderr.write(f"{e}\n")
103+
sys.exit(1)
104+

package/batocera/core/batocera-resolution/scripts/screenshot/batocera-screenshot.wayland-labwc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ fi
88
OUTPUT=$(batocera-resolution currentOutput)
99

1010
grim -o "$OUTPUT" "$FILE" 2>/dev/null || exit 1
11+
# Visual feedback when screenshot is taken
12+
batocera-flash-screen 2>/dev/null

package/batocera/core/batocera-resolution/scripts/screenshot/batocera-screenshot.wayland-sway

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,3 +8,5 @@ fi
88
OUTPUT=$(batocera-resolution currentOutput)
99

1010
grim -o "$OUTPUT" "$FILE" 2>/dev/null || exit 1
11+
# Visual feedback when screenshot is taken
12+
batocera-flash-screen 2>/dev/null

package/batocera/core/batocera-resolution/scripts/screenshot/batocera-screenshot.xorg

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,3 +7,6 @@ if [ -z ${FILE} ]; then
77
fi
88

99
ffmpeg -f x11grab -i "${DISPLAY}" -vframes 1 "${FILE}" 2>/dev/null || exit 1
10+
11+
# Visual feedback when screenshot is taken
12+
batocera-flash-screen 2>/dev/null

0 commit comments

Comments
 (0)