Add game-based onboard profile switching and detector management
This commit is contained in:
parent
7520c9cc28
commit
f2b3fc5764
|
|
@ -0,0 +1,164 @@
|
||||||
|
import json
|
||||||
|
import logging
|
||||||
|
import os
|
||||||
|
import shutil
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
import time
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import psutil
|
||||||
|
|
||||||
|
CONFIG_PATH = Path.home() / ".config" / "solaar" / "game_dpi_profiles.json"
|
||||||
|
POLL_SECONDS = 1.0
|
||||||
|
DEBOUNCE_REQUIRED = 2
|
||||||
|
COOLDOWN_SECONDS = 5.0
|
||||||
|
PROFILE_CHOICES = {f"profile {i}": f"Profile {i}" for i in range(1, 6)}
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format="%(message)s")
|
||||||
|
logger = logging.getLogger("solaar-game-profile-detector")
|
||||||
|
|
||||||
|
|
||||||
|
def _load_config():
|
||||||
|
try:
|
||||||
|
return json.loads(CONFIG_PATH.read_text())
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _alive_pids(pids):
|
||||||
|
alive = set()
|
||||||
|
for pid in pids:
|
||||||
|
if psutil.pid_exists(pid):
|
||||||
|
alive.add(pid)
|
||||||
|
return alive
|
||||||
|
|
||||||
|
|
||||||
|
def _iter_processes():
|
||||||
|
for proc in psutil.process_iter(["pid", "name", "cmdline"]):
|
||||||
|
try:
|
||||||
|
info = proc.info
|
||||||
|
name = (info.get("name") or "").lower()
|
||||||
|
cmdline = [part.lower() for part in (info.get("cmdline") or [])]
|
||||||
|
yield info.get("pid"), name, cmdline
|
||||||
|
except (psutil.NoSuchProcess, psutil.AccessDenied):
|
||||||
|
continue
|
||||||
|
|
||||||
|
|
||||||
|
def _match_alias(alias, proc_name, cmdline):
|
||||||
|
alias = alias.lower()
|
||||||
|
joined = " ".join(cmdline)
|
||||||
|
if alias == "minecraft":
|
||||||
|
return proc_name == "java" and "mojang" in joined
|
||||||
|
if alias == proc_name:
|
||||||
|
return True
|
||||||
|
if alias in proc_name:
|
||||||
|
return True
|
||||||
|
return alias in joined
|
||||||
|
|
||||||
|
|
||||||
|
def _find_target(config):
|
||||||
|
for device_name, device_cfg in config.items():
|
||||||
|
profiles = device_cfg.get("profiles", [])
|
||||||
|
for profile in profiles:
|
||||||
|
if profile.get("is_default"):
|
||||||
|
continue
|
||||||
|
aliases = profile.get("aliases", [])
|
||||||
|
if not aliases:
|
||||||
|
continue
|
||||||
|
matched_pids = set()
|
||||||
|
for pid, proc_name, cmdline in _iter_processes():
|
||||||
|
if any(_match_alias(alias, proc_name, cmdline) for alias in aliases):
|
||||||
|
matched_pids.add(pid)
|
||||||
|
if matched_pids:
|
||||||
|
return {
|
||||||
|
"device_name": device_name,
|
||||||
|
"profile_name": profile.get("name") or profile.get("onboard_profile"),
|
||||||
|
"onboard_profile": PROFILE_CHOICES.get(profile.get("onboard_profile", "").lower(), profile.get("onboard_profile", "Profile 1")),
|
||||||
|
"matched_pids": matched_pids,
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _default_for_device(config, device_name):
|
||||||
|
profiles = config.get(device_name, {}).get("profiles", [])
|
||||||
|
for profile in profiles:
|
||||||
|
if profile.get("is_default"):
|
||||||
|
return {
|
||||||
|
"device_name": device_name,
|
||||||
|
"profile_name": profile.get("name") or profile.get("onboard_profile"),
|
||||||
|
"onboard_profile": PROFILE_CHOICES.get(profile.get("onboard_profile", "").lower(), profile.get("onboard_profile", "Profile 1")),
|
||||||
|
}
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def _solaar_cmd():
|
||||||
|
return shutil.which("solaar") or str(Path.home() / ".local" / "bin" / "solaar")
|
||||||
|
|
||||||
|
|
||||||
|
def _apply_profile(binding):
|
||||||
|
cmd = [_solaar_cmd(), "config", binding["device_name"], "onboard_profiles", binding["onboard_profile"]]
|
||||||
|
subprocess.run(cmd, check=True, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
last_applied = None
|
||||||
|
active_game = None
|
||||||
|
last_switch = 0.0
|
||||||
|
pending_key = None
|
||||||
|
pending_hits = 0
|
||||||
|
|
||||||
|
while True:
|
||||||
|
config = _load_config()
|
||||||
|
now = time.monotonic()
|
||||||
|
|
||||||
|
if active_game is not None:
|
||||||
|
alive = _alive_pids(active_game.get("matched_pids", set()))
|
||||||
|
if alive:
|
||||||
|
active_game["matched_pids"] = alive
|
||||||
|
time.sleep(POLL_SECONDS)
|
||||||
|
continue
|
||||||
|
default_binding = _default_for_device(config, active_game["device_name"])
|
||||||
|
if default_binding and default_binding != last_applied and now - last_switch >= COOLDOWN_SECONDS:
|
||||||
|
_apply_profile(default_binding)
|
||||||
|
logger.info(f"reverted {default_binding['device_name']} to {default_binding['onboard_profile']}")
|
||||||
|
last_applied = default_binding
|
||||||
|
last_switch = now
|
||||||
|
active_game = None
|
||||||
|
pending_key = None
|
||||||
|
pending_hits = 0
|
||||||
|
time.sleep(POLL_SECONDS)
|
||||||
|
continue
|
||||||
|
|
||||||
|
target = _find_target(config)
|
||||||
|
if target is None:
|
||||||
|
pending_key = None
|
||||||
|
pending_hits = 0
|
||||||
|
time.sleep(POLL_SECONDS)
|
||||||
|
continue
|
||||||
|
|
||||||
|
key = (target["device_name"], target["onboard_profile"])
|
||||||
|
if key == pending_key:
|
||||||
|
pending_hits += 1
|
||||||
|
else:
|
||||||
|
pending_key = key
|
||||||
|
pending_hits = 1
|
||||||
|
|
||||||
|
if pending_hits >= DEBOUNCE_REQUIRED and target != last_applied and now - last_switch >= COOLDOWN_SECONDS:
|
||||||
|
_apply_profile(target)
|
||||||
|
logger.info(f"activated {target['device_name']} -> {target['onboard_profile']} for {target['profile_name']}")
|
||||||
|
active_game = target
|
||||||
|
last_applied = {
|
||||||
|
"device_name": target["device_name"],
|
||||||
|
"profile_name": target["profile_name"],
|
||||||
|
"onboard_profile": target["onboard_profile"],
|
||||||
|
}
|
||||||
|
last_switch = now
|
||||||
|
pending_key = None
|
||||||
|
pending_hits = 0
|
||||||
|
|
||||||
|
time.sleep(POLL_SECONDS)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
import json
|
||||||
|
import subprocess
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import gi
|
||||||
|
|
||||||
|
from solaar.i18n import _
|
||||||
|
|
||||||
|
gi.require_version("Gtk", "3.0")
|
||||||
|
from gi.repository import Gtk
|
||||||
|
|
||||||
|
CONFIG_PATH = Path.home() / ".config" / "solaar" / "game_dpi_profiles.json"
|
||||||
|
SERVICE_PATH = Path.home() / ".config" / "systemd" / "user" / "solaar-game-profile-detector.service"
|
||||||
|
PROFILE_CHOICES = [f"Profile {i}" for i in range(1, 6)]
|
||||||
|
SERVICE_TEMPLATE = """[Unit]
|
||||||
|
Description=Solaar Game Profile Detector
|
||||||
|
After=graphical-session.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=%h/.local/bin/solaar-game-profile-detector
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
def _run_systemctl(*args):
|
||||||
|
return subprocess.run(["systemctl", "--user", *args], capture_output=True, text=True)
|
||||||
|
|
||||||
|
|
||||||
|
def _load_config():
|
||||||
|
try:
|
||||||
|
return json.loads(CONFIG_PATH.read_text())
|
||||||
|
except Exception:
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
|
def _save_config(data):
|
||||||
|
CONFIG_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
CONFIG_PATH.write_text(json.dumps(data, indent=2, sort_keys=True) + "\n")
|
||||||
|
|
||||||
|
|
||||||
|
def _device_key(device):
|
||||||
|
return getattr(device, "name", None) or getattr(device, "codename", None) or "Unknown Device"
|
||||||
|
|
||||||
|
|
||||||
|
def _service_status_text():
|
||||||
|
active = _run_systemctl("is-active", "solaar-game-profile-detector.service")
|
||||||
|
enabled = _run_systemctl("is-enabled", "solaar-game-profile-detector.service")
|
||||||
|
return f"{_('Status')}: {active.stdout.strip() or active.stderr.strip() or 'unknown'} / {_('Autostart')}: {enabled.stdout.strip() or enabled.stderr.strip() or 'disabled'}"
|
||||||
|
|
||||||
|
|
||||||
|
def _service_enabled():
|
||||||
|
result = _run_systemctl("is-enabled", "solaar-game-profile-detector.service")
|
||||||
|
return result.returncode == 0 and result.stdout.strip() == "enabled"
|
||||||
|
|
||||||
|
|
||||||
|
class _GameProfilesDialog(Gtk.Dialog):
|
||||||
|
def __init__(self, parent, device):
|
||||||
|
super().__init__(title=_("Game DPI Profiles"), transient_for=parent, flags=0)
|
||||||
|
self.device = device
|
||||||
|
self.set_modal(True)
|
||||||
|
self.set_default_size(760, 420)
|
||||||
|
self.add_button(_("Cancel"), Gtk.ResponseType.CANCEL)
|
||||||
|
self.add_button(_("Save"), Gtk.ResponseType.OK)
|
||||||
|
|
||||||
|
area = self.get_content_area()
|
||||||
|
area.set_spacing(10)
|
||||||
|
area.set_margin_top(10)
|
||||||
|
area.set_margin_bottom(10)
|
||||||
|
area.set_margin_start(10)
|
||||||
|
area.set_margin_end(10)
|
||||||
|
|
||||||
|
intro = Gtk.Label(label=_("Bind running games to onboard mouse profiles. Default profile is restored when the matched game exits."))
|
||||||
|
intro.set_xalign(0)
|
||||||
|
intro.set_line_wrap(True)
|
||||||
|
area.pack_start(intro, False, False, 0)
|
||||||
|
|
||||||
|
service_frame = Gtk.Frame(label=_("Background detector"))
|
||||||
|
service_box = Gtk.Box.new(Gtk.Orientation.VERTICAL, 8)
|
||||||
|
service_box.set_border_width(8)
|
||||||
|
self.status_label = Gtk.Label(label=_service_status_text())
|
||||||
|
self.status_label.set_xalign(0)
|
||||||
|
service_box.pack_start(self.status_label, False, False, 0)
|
||||||
|
|
||||||
|
self.autostart = Gtk.CheckButton(label=_("Start detector on login"))
|
||||||
|
self.autostart.set_active(_service_enabled())
|
||||||
|
service_box.pack_start(self.autostart, False, False, 0)
|
||||||
|
|
||||||
|
btn_box = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6)
|
||||||
|
for label, callback in [
|
||||||
|
(_("Install/repair service"), self._install_service),
|
||||||
|
(_("Start detector"), self._start_service),
|
||||||
|
(_("Stop detector"), self._stop_service),
|
||||||
|
(_("Refresh status"), self._refresh_status),
|
||||||
|
]:
|
||||||
|
btn = Gtk.Button(label=label)
|
||||||
|
btn.connect("clicked", callback)
|
||||||
|
btn_box.pack_start(btn, False, False, 0)
|
||||||
|
service_box.pack_start(btn_box, False, False, 0)
|
||||||
|
service_frame.add(service_box)
|
||||||
|
area.pack_start(service_frame, False, False, 0)
|
||||||
|
|
||||||
|
self.store = Gtk.ListStore(str, str, str, bool)
|
||||||
|
self._load_rows()
|
||||||
|
|
||||||
|
tree = Gtk.TreeView(model=self.store)
|
||||||
|
tree.set_hexpand(True)
|
||||||
|
tree.set_vexpand(True)
|
||||||
|
self.tree = tree
|
||||||
|
|
||||||
|
name_renderer = Gtk.CellRendererText()
|
||||||
|
name_renderer.set_property("editable", True)
|
||||||
|
name_renderer.connect("edited", self._on_name_edited)
|
||||||
|
tree.append_column(Gtk.TreeViewColumn(_("Name"), name_renderer, text=0))
|
||||||
|
|
||||||
|
profile_renderer = Gtk.CellRendererCombo()
|
||||||
|
profile_renderer.set_property("editable", True)
|
||||||
|
profile_renderer.set_property("has-entry", False)
|
||||||
|
combo_model = Gtk.ListStore(str)
|
||||||
|
for choice in PROFILE_CHOICES:
|
||||||
|
combo_model.append([choice])
|
||||||
|
profile_renderer.set_property("model", combo_model)
|
||||||
|
profile_renderer.set_property("text-column", 0)
|
||||||
|
profile_renderer.connect("edited", self._on_profile_edited)
|
||||||
|
tree.append_column(Gtk.TreeViewColumn(_("Onboard profile"), profile_renderer, text=1))
|
||||||
|
|
||||||
|
aliases_renderer = Gtk.CellRendererText()
|
||||||
|
aliases_renderer.set_property("editable", True)
|
||||||
|
aliases_renderer.connect("edited", self._on_aliases_edited)
|
||||||
|
tree.append_column(Gtk.TreeViewColumn(_("Game aliases"), aliases_renderer, text=2))
|
||||||
|
|
||||||
|
default_renderer = Gtk.CellRendererToggle()
|
||||||
|
default_renderer.connect("toggled", self._on_default_toggled)
|
||||||
|
tree.append_column(Gtk.TreeViewColumn(_("Desktop default"), default_renderer, active=3))
|
||||||
|
|
||||||
|
scroller = Gtk.ScrolledWindow()
|
||||||
|
scroller.set_policy(Gtk.PolicyType.AUTOMATIC, Gtk.PolicyType.AUTOMATIC)
|
||||||
|
scroller.add(tree)
|
||||||
|
area.pack_start(scroller, True, True, 0)
|
||||||
|
|
||||||
|
row_buttons = Gtk.Box.new(Gtk.Orientation.HORIZONTAL, 6)
|
||||||
|
add_btn = Gtk.Button(label=_("Add binding"))
|
||||||
|
add_btn.connect("clicked", self._add_row)
|
||||||
|
remove_btn = Gtk.Button(label=_("Remove selected"))
|
||||||
|
remove_btn.connect("clicked", self._remove_selected)
|
||||||
|
row_buttons.pack_start(add_btn, False, False, 0)
|
||||||
|
row_buttons.pack_start(remove_btn, False, False, 0)
|
||||||
|
area.pack_start(row_buttons, False, False, 0)
|
||||||
|
|
||||||
|
self.show_all()
|
||||||
|
|
||||||
|
def _device_config(self):
|
||||||
|
data = _load_config()
|
||||||
|
key = _device_key(self.device)
|
||||||
|
return data, key, data.get(key, {})
|
||||||
|
|
||||||
|
def _load_rows(self):
|
||||||
|
self.store.clear()
|
||||||
|
_data, _key, device_cfg = self._device_config()
|
||||||
|
profiles = device_cfg.get("profiles", [])
|
||||||
|
if not profiles:
|
||||||
|
self.store.append([_("Desktop"), "Profile 1", "", True])
|
||||||
|
self.store.append([_("Minecraft"), "Profile 2", "minecraft", False])
|
||||||
|
return
|
||||||
|
for profile in profiles:
|
||||||
|
self.store.append([
|
||||||
|
profile.get("name", ""),
|
||||||
|
profile.get("onboard_profile", "Profile 1"),
|
||||||
|
",".join(profile.get("aliases", [])),
|
||||||
|
bool(profile.get("is_default", False)),
|
||||||
|
])
|
||||||
|
|
||||||
|
def _refresh_status(self, *_args):
|
||||||
|
self.status_label.set_text(_service_status_text())
|
||||||
|
self.autostart.set_active(_service_enabled())
|
||||||
|
|
||||||
|
def _install_service(self, *_args):
|
||||||
|
SERVICE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
||||||
|
SERVICE_PATH.write_text(SERVICE_TEMPLATE)
|
||||||
|
_run_systemctl("daemon-reload")
|
||||||
|
self._refresh_status()
|
||||||
|
|
||||||
|
def _start_service(self, *_args):
|
||||||
|
self._apply_autostart_choice()
|
||||||
|
_run_systemctl("start", "solaar-game-profile-detector.service")
|
||||||
|
self._refresh_status()
|
||||||
|
|
||||||
|
def _stop_service(self, *_args):
|
||||||
|
_run_systemctl("stop", "solaar-game-profile-detector.service")
|
||||||
|
self._refresh_status()
|
||||||
|
|
||||||
|
def _apply_autostart_choice(self):
|
||||||
|
if self.autostart.get_active():
|
||||||
|
_run_systemctl("enable", "solaar-game-profile-detector.service")
|
||||||
|
else:
|
||||||
|
_run_systemctl("disable", "solaar-game-profile-detector.service")
|
||||||
|
|
||||||
|
def _add_row(self, *_args):
|
||||||
|
self.store.append([_("New profile"), "Profile 1", "", False])
|
||||||
|
|
||||||
|
def _remove_selected(self, *_args):
|
||||||
|
selection = self.tree.get_selection()
|
||||||
|
model, treeiter = selection.get_selected()
|
||||||
|
if treeiter is not None:
|
||||||
|
model.remove(treeiter)
|
||||||
|
|
||||||
|
def _on_name_edited(self, _renderer, path, text):
|
||||||
|
self.store[path][0] = text
|
||||||
|
|
||||||
|
def _on_profile_edited(self, _renderer, path, text):
|
||||||
|
self.store[path][1] = text
|
||||||
|
|
||||||
|
def _on_aliases_edited(self, _renderer, path, text):
|
||||||
|
self.store[path][2] = text
|
||||||
|
|
||||||
|
def _on_default_toggled(self, _renderer, path):
|
||||||
|
for idx, row in enumerate(self.store):
|
||||||
|
row[3] = str(idx) == path
|
||||||
|
|
||||||
|
def save(self):
|
||||||
|
self._apply_autostart_choice()
|
||||||
|
data, key, _device_cfg = self._device_config()
|
||||||
|
profiles = []
|
||||||
|
has_default = False
|
||||||
|
for row in self.store:
|
||||||
|
name = row[0].strip()
|
||||||
|
onboard_profile = row[1].strip() or "Profile 1"
|
||||||
|
aliases = [a.strip().lower() for a in row[2].split(",") if a.strip()]
|
||||||
|
is_default = bool(row[3])
|
||||||
|
has_default = has_default or is_default
|
||||||
|
profiles.append({
|
||||||
|
"name": name or onboard_profile,
|
||||||
|
"onboard_profile": onboard_profile,
|
||||||
|
"aliases": aliases,
|
||||||
|
"is_default": is_default,
|
||||||
|
})
|
||||||
|
if profiles and not has_default:
|
||||||
|
profiles[0]["is_default"] = True
|
||||||
|
data[key] = {"profiles": profiles}
|
||||||
|
_save_config(data)
|
||||||
|
|
||||||
|
|
||||||
|
def show(parent, device):
|
||||||
|
if device is None:
|
||||||
|
return
|
||||||
|
dialog = _GameProfilesDialog(parent, device)
|
||||||
|
response = dialog.run()
|
||||||
|
if response == Gtk.ResponseType.OK:
|
||||||
|
dialog.save()
|
||||||
|
dialog.destroy()
|
||||||
|
|
@ -34,6 +34,7 @@ from solaar.i18n import ngettext
|
||||||
from . import action
|
from . import action
|
||||||
from . import config_panel
|
from . import config_panel
|
||||||
from . import diversion_rules
|
from . import diversion_rules
|
||||||
|
from . import game_profiles
|
||||||
from . import icons
|
from . import icons
|
||||||
from .about import about
|
from .about import about
|
||||||
from .common import ui_async
|
from .common import ui_async
|
||||||
|
|
@ -327,6 +328,14 @@ def _create_window_layout():
|
||||||
bottom_buttons_box.add(quit_button)
|
bottom_buttons_box.add(quit_button)
|
||||||
about_button = _new_button(_("About %s") % NAME, "help-about", _SMALL_BUTTON_ICON_SIZE, clicked=about.show)
|
about_button = _new_button(_("About %s") % NAME, "help-about", _SMALL_BUTTON_ICON_SIZE, clicked=about.show)
|
||||||
bottom_buttons_box.add(about_button)
|
bottom_buttons_box.add(about_button)
|
||||||
|
game_profiles_button = _new_button(
|
||||||
|
_("Game DPI Profiles"),
|
||||||
|
"applications-games",
|
||||||
|
_SMALL_BUTTON_ICON_SIZE,
|
||||||
|
tooltip=_("Configure automatic onboard profile switching for games"),
|
||||||
|
clicked=lambda *_trigger: game_profiles.show(_window, _find_selected_device()),
|
||||||
|
)
|
||||||
|
bottom_buttons_box.add(game_profiles_button)
|
||||||
diversion_button = _new_button(
|
diversion_button = _new_button(
|
||||||
_("Rule Editor"), "", _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: diversion_rules.show_window(_model)
|
_("Rule Editor"), "", _SMALL_BUTTON_ICON_SIZE, clicked=lambda *_trigger: diversion_rules.show_window(_model)
|
||||||
)
|
)
|
||||||
|
|
@ -378,6 +387,16 @@ def _find_selected_device_id():
|
||||||
return _model.get_value(item, Column.PATH), _model.get_value(item, Column.NUMBER)
|
return _model.get_value(item, Column.PATH), _model.get_value(item, Column.NUMBER)
|
||||||
|
|
||||||
|
|
||||||
|
def _device_supports_game_profiles(device):
|
||||||
|
if not device or getattr(device, "kind", None) is None:
|
||||||
|
return False
|
||||||
|
settings = getattr(device, "settings", None) or []
|
||||||
|
for setting in settings:
|
||||||
|
if getattr(setting, "name", None) == "onboard_profiles":
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
# triggered by changing selection in the tree
|
# triggered by changing selection in the tree
|
||||||
def _device_selected(selection):
|
def _device_selected(selection):
|
||||||
model, item = selection.get_selected()
|
model, item = selection.get_selected()
|
||||||
|
|
@ -755,6 +774,14 @@ def _update_info_panel(device, full=False):
|
||||||
_details.set_visible(False)
|
_details.set_visible(False)
|
||||||
_info.set_visible(False)
|
_info.set_visible(False)
|
||||||
_empty.set_visible(True)
|
_empty.set_visible(True)
|
||||||
|
for child in _window.get_children():
|
||||||
|
vbox = child
|
||||||
|
if hasattr(vbox, "get_children"):
|
||||||
|
for sub in vbox.get_children():
|
||||||
|
if isinstance(sub, Gtk.HButtonBox):
|
||||||
|
for btn in sub.get_children():
|
||||||
|
if btn.get_tooltip_text() == _("Configure automatic onboard profile switching for games"):
|
||||||
|
btn.set_sensitive(False)
|
||||||
return
|
return
|
||||||
|
|
||||||
# a receiver must be valid
|
# a receiver must be valid
|
||||||
|
|
@ -777,6 +804,25 @@ def _update_info_panel(device, full=False):
|
||||||
_info._title.set_sensitive(is_online)
|
_info._title.set_sensitive(is_online)
|
||||||
_update_device_panel(device, _info._device, _info._buttons, full)
|
_update_device_panel(device, _info._device, _info._buttons, full)
|
||||||
|
|
||||||
|
for child in _window.get_children():
|
||||||
|
pass
|
||||||
|
button = None
|
||||||
|
for child in _window.get_children():
|
||||||
|
vbox = child
|
||||||
|
if hasattr(vbox, "get_children"):
|
||||||
|
for sub in vbox.get_children():
|
||||||
|
if isinstance(sub, Gtk.HButtonBox):
|
||||||
|
for btn in sub.get_children():
|
||||||
|
if btn.get_tooltip_text() == _("Configure automatic onboard profile switching for games"):
|
||||||
|
button = btn
|
||||||
|
break
|
||||||
|
if button:
|
||||||
|
break
|
||||||
|
if button:
|
||||||
|
break
|
||||||
|
if button is not None:
|
||||||
|
button.set_sensitive(_device_supports_game_profiles(device))
|
||||||
|
|
||||||
_empty.set_visible(False)
|
_empty.set_visible(False)
|
||||||
_info.set_visible(True)
|
_info.set_visible(True)
|
||||||
|
|
||||||
|
|
|
||||||
1
setup.py
1
setup.py
|
|
@ -92,6 +92,7 @@ setup(
|
||||||
entry_points={
|
entry_points={
|
||||||
"console_scripts": [
|
"console_scripts": [
|
||||||
"solaar = solaar.gtk:main",
|
"solaar = solaar.gtk:main",
|
||||||
|
"solaar-game-profile-detector = solaar.game_profile_detector:main",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
[Unit]
|
||||||
|
Description=Solaar Game Profile Detector
|
||||||
|
After=graphical-session.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
Type=simple
|
||||||
|
ExecStart=%h/.local/bin/solaar-game-profile-detector
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=2
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=default.target
|
||||||
Loading…
Reference in New Issue