gdctl: Add bash completion integration

This auto-completes things such as available connectors, modes, scales,
transforms, etc.

Part-of: <https://gitlab.gnome.org/GNOME/mutter/-/merge_requests/4190>
This commit is contained in:
Jonas Ådahl
2024-12-20 10:15:47 +01:00
committed by Marge Bot
parent a0c5c09e9b
commit f9bb7aa2e6
5 changed files with 122 additions and 11 deletions

View File

@ -11,6 +11,7 @@ include:
meson-options: meson-options:
-Dxwayland_initfd=enabled -Dxwayland_initfd=enabled
-Dprofiler=true -Dprofiler=true
-Dbash_completion=false
build-sysext: build-sysext:
before_script: before_script:
@ -124,6 +125,7 @@ variables:
zenity zenity
python3-dbusmock python3-dbusmock
gnome-desktop-testing gnome-desktop-testing
python3-argcomplete
ruff ruff
FDO_DISTRIBUTION_EXEC: | FDO_DISTRIBUTION_EXEC: |
@ -194,8 +196,6 @@ variables:
mkdir -p /opt/mutter mkdir -p /opt/mutter
cp build/src/tests/kvm/bzImage /opt/mutter/bzImage cp build/src/tests/kvm/bzImage /opt/mutter/bzImage
dnf install -y python3-argcomplete
git clone https://github.com/arighi/virtme-ng.git git clone https://github.com/arighi/virtme-ng.git
cd virtme-ng cd virtme-ng
git fetch --tags git fetch --tags

View File

@ -378,6 +378,7 @@ have_kvm_tests = false
have_tty_tests = false have_tty_tests = false
have_installed_tests = false have_installed_tests = false
have_x11_tests = false have_x11_tests = false
have_bash_completion = get_option('bash_completion')
if have_tests if have_tests
gtk3_dep = dependency('gtk+-3.0', version: gtk3_req) gtk3_dep = dependency('gtk+-3.0', version: gtk3_req)
@ -744,6 +745,8 @@ summary('mandir', mandir, section: 'Directories')
summary('buildtype', get_option('buildtype'), section: 'Build Configuration') summary('buildtype', get_option('buildtype'), section: 'Build Configuration')
summary('debug', get_option('debug'), section: 'Build Configuration') summary('debug', get_option('debug'), section: 'Build Configuration')
summary('Bash completion', have_bash_completion, section: 'Shell integration')
summary('OpenGL', have_gl, section: 'Rendering APIs') summary('OpenGL', have_gl, section: 'Rendering APIs')
summary('GLES2', have_gles2, section: 'Rendering APIs') summary('GLES2', have_gles2, section: 'Rendering APIs')
summary('EGL', have_egl, section: 'Rendering APIs') summary('EGL', have_egl, section: 'Rendering APIs')

View File

@ -224,3 +224,9 @@ option('fonts',
value: true, value: true,
description: 'Enable font rendering integration using Pango' description: 'Enable font rendering integration using Pango'
) )
option('bash_completion',
type: 'boolean',
value: true,
description: 'Integrate bash completion for gdctl'
)

View File

@ -1,11 +1,13 @@
#!/usr/bin/env python3 #!/usr/bin/env python3
import argparse import argparse
import argcomplete
import sys import sys
from dataclasses import dataclass from dataclasses import dataclass
from gi.repository import GLib, Gio from gi.repository import GLib, Gio
from enum import Enum, Flag from enum import Enum, Flag
from argcomplete.completers import BaseCompleter, SuppressCompleter
NAME = "org.gnome.Mutter.DisplayConfig" NAME = "org.gnome.Mutter.DisplayConfig"
INTERFACE = "org.gnome.Mutter.DisplayConfig" INTERFACE = "org.gnome.Mutter.DisplayConfig"
@ -1130,6 +1132,70 @@ class GdctlParser(argparse.ArgumentParser):
return namespace return namespace
class MonitorCompleter(BaseCompleter):
def __call__(self, **kwargs):
try:
display_config = DisplayConfig()
monitors_state = MonitorsState(display_config)
return tuple(monitors_state.monitors)
except Exception:
return ()
class MonitorModeCompleter(BaseCompleter):
def __call__(self, parsed_args=None, **kwargs):
try:
(connector,) = parsed_args._current_sub_group["key"]
display_config = DisplayConfig()
monitors_state = MonitorsState(display_config)
monitor = monitors_state.monitors[connector]
return (mode.name for mode in monitor.modes)
except Exception:
return ()
class ScaleCompleter(BaseCompleter):
def __call__(self, parsed_args=None, **kwargs):
try:
(connector,) = parsed_args._current_sub_group["key"]
display_config = DisplayConfig()
monitors_state = MonitorsState(display_config)
monitor = monitors_state.monitors[connector]
mode = parsed_args._current_sub_group.get("mode", None)
if not mode:
mode = monitor.preferred_mode
scales = mode.supported_scales
scales.sort(key=lambda scale: abs(scale - mode.preferred_scale))
return (repr(scale) for scale in scales)
except Exception:
return ()
class NamedEnumCompleter(BaseCompleter):
def __init__(self, enum_type):
self.enum_type = enum_type
def __call__(self, **kwargs):
return (str(enum_value) for enum_value in self.enum_type)
class LayoutModeCompleter(NamedEnumCompleter):
def __init__(self):
super().__init__(LayoutMode)
class TransformCompleter(NamedEnumCompleter):
def __init__(self):
super().__init__(Transform)
if __name__ == "__main__": if __name__ == "__main__":
parser = GdctlParser( parser = GdctlParser(
description="Display control utility", description="Display control utility",
@ -1196,7 +1262,7 @@ if __name__ == "__main__":
choices=[str(layout_mode) for layout_mode in list(LayoutMode)], choices=[str(layout_mode) for layout_mode in list(LayoutMode)],
type=str, type=str,
action=AppendToGlobal, action=AppendToGlobal,
) ).completer = LayoutModeCompleter()
set_parser.add_argument( set_parser.add_argument(
"-L", "-L",
"--logical-monitor", "--logical-monitor",
@ -1216,8 +1282,9 @@ if __name__ == "__main__":
dest="monitors", dest="monitors",
metavar="CONNECTOR", metavar="CONNECTOR",
action=SubGroupAction, action=SubGroupAction,
nargs=1,
help="Configure monitor", help="Configure monitor",
) ).completer = MonitorCompleter()
monitor_parser = set_parser.add_argument_group( monitor_parser = set_parser.add_argument_group(
"monitor", "monitor",
"Monitor options (pass after --monitor)", "Monitor options (pass after --monitor)",
@ -1229,7 +1296,7 @@ if __name__ == "__main__":
action=AppendToSubGroup, action=AppendToSubGroup,
help="Monitor mode", help="Monitor mode",
type=str, type=str,
) ).completer = MonitorModeCompleter()
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--primary", "--primary",
"-p", "-p",
@ -1245,7 +1312,7 @@ if __name__ == "__main__":
action=AppendToGroup, action=AppendToGroup,
help="Logical monitor scale", help="Logical monitor scale",
type=float, type=float,
) ).completer = ScaleCompleter()
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--transform", "--transform",
"-t", "-t",
@ -1253,7 +1320,7 @@ if __name__ == "__main__":
help="Apply viewport transform", help="Apply viewport transform",
choices=[str(transform) for transform in list(Transform)], choices=[str(transform) for transform in list(Transform)],
type=str, type=str,
) ).completer = TransformCompleter()
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--x", "--x",
"-x", "-x",
@ -1274,28 +1341,38 @@ if __name__ == "__main__":
metavar="CONNECTOR", metavar="CONNECTOR",
help="Place right of other monitor", help="Place right of other monitor",
type=str, type=str,
) ).completer = MonitorCompleter()
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--left-of", "--left-of",
action=AppendToGroup, action=AppendToGroup,
metavar="CONNECTOR", metavar="CONNECTOR",
help="Place left of other monitor", help="Place left of other monitor",
type=str, type=str,
) ).completer = MonitorCompleter()
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--above", "--above",
action=AppendToGroup, action=AppendToGroup,
metavar="CONNECTOR", metavar="CONNECTOR",
help="Place above other monitor", help="Place above other monitor",
type=str, type=str,
) ).completer = MonitorCompleter()
logical_monitor_parser.add_argument( logical_monitor_parser.add_argument(
"--below", "--below",
action=AppendToGroup, action=AppendToGroup,
metavar="CONNECTOR", metavar="CONNECTOR",
help="Place below other monitor", help="Place below other monitor",
type=str, type=str,
) ).completer = MonitorCompleter()
for action in [
GroupAction,
SubGroupAction,
AppendToGroup,
AppendToSubGroup,
AppendToGlobal,
]:
argcomplete.safe_actions.add(action)
argcomplete.autocomplete(parser, default_completer=SuppressCompleter)
args = parser.parse_args() args = parser.parse_args()

View File

@ -3,5 +3,30 @@ install_data(
install_dir: bindir, install_dir: bindir,
) )
if have_bash_completion
bash_completion = dependency('bash-completion', required: false)
if bash_completion.found()
bash_completion_dir = bash_completion.get_variable(pkgconfig: 'completionsdir')
else
bash_completion_dir = get_option('sysconfdir') / 'bash_completion.d'
endif
register_python_argcomplete = find_program('register-python-argcomplete')
custom_target(
'gdctl-bash-completion',
output: 'gdctl',
command: [
register_python_argcomplete,
'gdctl',
'--complete-arguments',
'-o nosort',
],
capture: true,
install_dir: bash_completion_dir,
install: true,
)
endif
gdctl = find_program('gdctl') gdctl = find_program('gdctl')
get_state_tool = find_program('get-state.py') get_state_tool = find_program('get-state.py')