diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 9b3081639..0dfe89115 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -11,6 +11,7 @@ include: meson-options: -Dxwayland_initfd=enabled -Dprofiler=true + -Dbash_completion=false build-sysext: before_script: @@ -124,6 +125,7 @@ variables: zenity python3-dbusmock gnome-desktop-testing + python3-argcomplete ruff FDO_DISTRIBUTION_EXEC: | @@ -194,8 +196,6 @@ variables: mkdir -p /opt/mutter cp build/src/tests/kvm/bzImage /opt/mutter/bzImage - dnf install -y python3-argcomplete - git clone https://github.com/arighi/virtme-ng.git cd virtme-ng git fetch --tags diff --git a/meson.build b/meson.build index bea1f9e07..e53431f32 100644 --- a/meson.build +++ b/meson.build @@ -378,6 +378,7 @@ have_kvm_tests = false have_tty_tests = false have_installed_tests = false have_x11_tests = false +have_bash_completion = get_option('bash_completion') if have_tests 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('debug', get_option('debug'), section: 'Build Configuration') +summary('Bash completion', have_bash_completion, section: 'Shell integration') + summary('OpenGL', have_gl, section: 'Rendering APIs') summary('GLES2', have_gles2, section: 'Rendering APIs') summary('EGL', have_egl, section: 'Rendering APIs') diff --git a/meson.options b/meson.options index 31f72e9c9..b47b97267 100644 --- a/meson.options +++ b/meson.options @@ -224,3 +224,9 @@ option('fonts', value: true, description: 'Enable font rendering integration using Pango' ) + +option('bash_completion', + type: 'boolean', + value: true, + description: 'Integrate bash completion for gdctl' +) diff --git a/tools/gdctl b/tools/gdctl index 005e0b0d4..caa670eb9 100755 --- a/tools/gdctl +++ b/tools/gdctl @@ -1,11 +1,13 @@ #!/usr/bin/env python3 import argparse +import argcomplete import sys from dataclasses import dataclass from gi.repository import GLib, Gio from enum import Enum, Flag +from argcomplete.completers import BaseCompleter, SuppressCompleter NAME = "org.gnome.Mutter.DisplayConfig" INTERFACE = "org.gnome.Mutter.DisplayConfig" @@ -1130,6 +1132,70 @@ class GdctlParser(argparse.ArgumentParser): 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__": parser = GdctlParser( description="Display control utility", @@ -1196,7 +1262,7 @@ if __name__ == "__main__": choices=[str(layout_mode) for layout_mode in list(LayoutMode)], type=str, action=AppendToGlobal, - ) + ).completer = LayoutModeCompleter() set_parser.add_argument( "-L", "--logical-monitor", @@ -1216,8 +1282,9 @@ if __name__ == "__main__": dest="monitors", metavar="CONNECTOR", action=SubGroupAction, + nargs=1, help="Configure monitor", - ) + ).completer = MonitorCompleter() monitor_parser = set_parser.add_argument_group( "monitor", "Monitor options (pass after --monitor)", @@ -1229,7 +1296,7 @@ if __name__ == "__main__": action=AppendToSubGroup, help="Monitor mode", type=str, - ) + ).completer = MonitorModeCompleter() logical_monitor_parser.add_argument( "--primary", "-p", @@ -1245,7 +1312,7 @@ if __name__ == "__main__": action=AppendToGroup, help="Logical monitor scale", type=float, - ) + ).completer = ScaleCompleter() logical_monitor_parser.add_argument( "--transform", "-t", @@ -1253,7 +1320,7 @@ if __name__ == "__main__": help="Apply viewport transform", choices=[str(transform) for transform in list(Transform)], type=str, - ) + ).completer = TransformCompleter() logical_monitor_parser.add_argument( "--x", "-x", @@ -1274,28 +1341,38 @@ if __name__ == "__main__": metavar="CONNECTOR", help="Place right of other monitor", type=str, - ) + ).completer = MonitorCompleter() logical_monitor_parser.add_argument( "--left-of", action=AppendToGroup, metavar="CONNECTOR", help="Place left of other monitor", type=str, - ) + ).completer = MonitorCompleter() logical_monitor_parser.add_argument( "--above", action=AppendToGroup, metavar="CONNECTOR", help="Place above other monitor", type=str, - ) + ).completer = MonitorCompleter() logical_monitor_parser.add_argument( "--below", action=AppendToGroup, metavar="CONNECTOR", help="Place below other monitor", 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() diff --git a/tools/meson.build b/tools/meson.build index 763507c2e..3aebe4764 100644 --- a/tools/meson.build +++ b/tools/meson.build @@ -3,5 +3,30 @@ install_data( 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') get_state_tool = find_program('get-state.py')