Внимание, утилита обновлена! Скрипт превратился в модульный комбайн для shell-конвейеров. Подробности и описание новых флагов (-X, -x, -S, -s и др.) ищите в UPD в конце статьи.
Уже давно весьма огорчаюсь тем, что в моём любимом Gpick отвалилась пипетка с тех пор, как KDE со своей Плазмой прочно переехали на Wayland. Если раньше я ещё мирился с GTK-шностью Gpick (про KDE, плазму и вяленого — поручик, молчать!), то теперь совсем взгрустнулось…
Началось всё с того, что я напрочь забыл, какие у меня в «Местах» цвета какие. Я же человек правильный, нормальные человеческие названия для переменных ведь не путь джедая, верно? Посмотрев на своё
@mixin theme { $color-01: rgba($col-36, $opacity-07); $color-02: rgba($col-36, $opacity-11); $color-03: rgba($col-36, $opacity-13); $color-04: rgba($col-36, $opacity-12); …
(и ещё такого на пару сотен строк только в одном файле), я прослезился, ударил стену головой и решил исправляться к лучшему. Заодно перейти на адекватные CSS-переменные, а не этот SCSS-хардкор. Но вот без пипетки это грустновато…
Итак, нужна, прости Господи, пипетка. Ставить и терпеть ещё и штатный kcolorpicker, а уж тем более, kcolorchooser (при живом-то, ну, почти, Gpick) в своём уютном Artix я не намерен. Ладно про настройки я молчу, в которых у KDE-шных поделий обычно по две штуки — «Показывать иконку справа вместо слева» и «Рассказывать анекдоты раз в час вместо раза в полчаса». Но функционал… Непосредственного нету, а посредственного на полгига.
hyprpicker, который, как написано в пояснялке, does not suck, не просто wlroots-compatible. Проблема в том, что это я сейчас «wlroots-incompatible». Что остаётся? Правильно. Велосипедить.
Стал пытаться думать мозгом. Ударился было в bash-скрипты, но довольно быстро работать там с DBus, асинхронностью порталов и прочими мониторами стало очень больно.
Сунулся было набросать простенькую утилитку на C++/Qt (последний оборот звучит, правда? ?) — стало ещё больнее. Ну, вы знаете. Для того, чтобы в дверь врезать новый замок, нам жизненно необходимо вокруг неё построить десятимиллионный мегаполис с инфраструктурой, аэропортом, полицией и трамвайной сетью. Не говоря уже о заведениях с преферансом и барышнями…
Короче, плюнул и наваял скрипт на Питоне (хоть я его и терпеть не могу). Получилось, имхо, просто прекрасно. При запуске даёт тебе пипетку, ждёт, пока жмакнешь ей где-нибудь на экране, и выдаёт на выход сразу пачку CSS-значений на любой вкус в таком виде:
#7e80ea rgb(126 128 234) rgb(49.4% 50.2% 91.8%) hsl(238 72% 70%)
Тут тебе и RGB, хоть в HEX, хоть в числах байтов, хоть в процентах, и HSL. А ежели запускать ещё и с соответствующими ключиками (описание см. в UPD в конце статьи; да, я допиливаю), то заодно копирует и/или выводит только нужные значения в буфер обмена и/или в stdout. Можно повесить на хоткеи хоть голым, хоть через какую-нибудь shell-трубу. Красота! ?
В общем, делюсь с городом и миром. Может, кому-то пригодится и сэкономит нервы и время:
#!/usr/bin/env python3 import sys import colorsys import subprocess import argparse from gi.repository import GLib, Gio parser = argparse.ArgumentParser( description="Wayland XDG-Portal Color Picker" ) group_output = parser.add_argument_group("Standard output") parser.add_argument( "-X", "--hex", action="store_true", help="HEX to output", ) parser.add_argument( "-S", "--hsl", action="store_true", help="HSL to output", ) parser.add_argument( "-B", "--rgb-bytes", action="store_true", help="RGB (bytes) to output", ) parser.add_argument( "-P", "--rgb-percentages", action="store_true", help="RGB (percentages) to output", ) group_copy = parser.add_argument_group("Copy to clipboard") parser.add_argument( "-x", "--copy-hex", action="store_true", help="HEX to clipboard", ) parser.add_argument( "-s", "--copy-hsl", action="store_true", help="HSL to clipboard", ) parser.add_argument( "-b", "--copy-rgb-bytes", action="store_true", help="RGB (bytes) to clipboard", ) parser.add_argument( "-p", "--copy-rgb-percentages", action="store_true", help="RGB (percentages) to clipboard", ) args = parser.parse_args() args_output = [ args.hex, args.hsl, args.rgb_bytes, args.rgb_percentages, ] args_copy = [ args.copy_hex, args.copy_hsl, args.copy_rgb_bytes, args.copy_rgb_percentages, ] loop = GLib.MainLoop() channels = None bus = Gio.bus_get_sync(Gio.BusType.SESSION, None) def on_signal_received( connection, sender_name, object_path, interface_name, signal_name, parameters, user_data, ): global channels try: code, results = parameters.unpack() if code == 0 and 'color' in results: channels = results['color'] except Exception as e: sys.stderr.write(f"Signal error: {e}\n") finally: loop.quit() def on_method_reply(connection, res): try: reply = connection.call_finish(res) request_path = reply.unpack()[0] bus.signal_subscribe( "org.freedesktop.portal.Desktop", "org.freedesktop.portal.Request", "Response", request_path, None, Gio.DBusSignalFlags.NONE, on_signal_received, None, ) except Exception as e: sys.stderr.write(f"Method error: {e}\n") loop.quit() bus.call( "org.freedesktop.portal.Desktop", "/org/freedesktop/portal/desktop", "org.freedesktop.portal.Screenshot", "PickColor", GLib.Variant("(sa{sv})", ("", {})), None, Gio.DBusSignalFlags.NONE, -1, None, on_method_reply, ) loop.run() if channels and len(channels) >= 3: r_f, g_f, b_f = channels[0], channels[1], channels[2] r_b, g_b, b_b = int(r_f * 255), int(g_f * 255), int(b_f * 255) r_p, g_p, b_p = r_f * 100, g_f * 100, b_f * 100 h_f, l_f, s_f = colorsys.rgb_to_hls(r_f, g_f, b_f) h, s, l = int(h_f * 360), int(s_f * 100), int(l_f * 100) color_map = { "hex": f"#{r_b:02x}{g_b:02x}{b_b:02x}", "rgb_bytes": f"rgb({r_b} {g_b} {b_b})", "rgb_percentages": f"rgb({r_p:.1f}% {g_p:.1f}% {b_p:.1f}%)", "hsl": f"hsl({h} {s}% {l}%)", } if not any(args_output): print("\n".join(color_map.values())) else: for key, value in color_map.items(): if getattr(args, key): print(value) if any(args_copy): lines = [] for key, value in color_map.items(): if getattr(args, f"copy_{key}"): lines.append(value) output = "\n".join(lines) try: subprocess.run( ['wl-copy', output], check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) except Exception: try: p = subprocess.Popen( ['xclip', '-selection', 'clipboard'], stdin=subprocess.PIPE, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, ) p.communicate(input=output.encode()) except Exception: pass sys.exit(0) else: sys.stderr.write("Cancelled by the user.\n") sys.exit(1)
А если лениво отсюда копировать, закоммитил в своём GitHub-овском репозитории других полезняшек: ds-colorpicker.
Вот. Пользуйтесь, и больше велосипедов богу СПО! ?
UPD: Почесал ещё репу и прокачал скрипт до полноценного комбайна. Теперь поддерживаются 8 флагов для выборочного вывода и копирования в буфер обмена конкретных форматов.
По умолчанию (без флагов) утилита теперь выводит все значения на выход. А вот с флагами теперь так. Они разделены на две группы по четыре. Никто никого не исключает, можно комбинировать как угодно.
Флаги вывода (stdout):
-X— HEX-значение-S— HSL-значение-B— RGB-значение (в числах байтов)-P— RGB-значение (в процентах)
Флаги копирования в буфер обмена:
-x— HEX-значение-s— HSL-значение-b— RGB-значение (в числах байтов)-p— RGB-значение (в процентах)
Как видите, флаги обеих групп отличаются друг от друга только регистром. Большие — для вывода в консольку/конвейер, маленькие — для копирования в буфер обмена.
То есть, теперь этот скрипт очень удобно вызывать в конвейерах, по хоткеям. Например, так:
echo "color: $(ds-colorpicker -B);" >> styles.css
Код в статье и репозитории обновлён. Все подробности по ключам — в ds-colorpicker --help.