Repository: qutebrowser/qutebrowser

Issue:
qt.environ setting
Sometimes it can be useful to set environment variables for qutebrowser only. This can be done in `config.py` already, but having a setting would make things a bit easier (also to test via `--temp-basedir -s`.

In the config, this would be of `type: Dict` with `keytype: String` and `valtype: String`, as well as `restart: true` set (perhaps also `none_ok: true` so that `None` can be used as a value to remove a previously set variable).

Then qutebrowser would apply those in `qutebrowser/config/qtargs.py` or so, similar to how other settings (e.g. `config.val.qt.force_platform`) are already mapped to environment variables


Discussion:
Hello @The-Compiler , can you assign me this issue? I'd like to contribute and am just starting out with open source.
@StutiV Sure, please go ahead and don't hesitate to ask if you get stuck somewhere! :+1: 
@The-Compiler can you elaborate the issue a bit? It'd be really helpful. Thank you!
So, the aim of this is to be able to set [environment variables](https://en.wikipedia.org/wiki/Environment_variable) via the qutebrowser config. For example, if you run this (on Linux/macOS, see the Wikipedia article for Windows):

```con
QT_SCALE_FACTOR=4 qutebrowser --temp-basedir
```

you should be able to start qutebrowser with a much bigger user interface.

The idea is now that this can be specified via the qutebrowser configuration instead, which you can test using something like:

```con
qutebrowser --temp-basedir -s qt.environ '{"QT_SCALE_FACTOR": "4"}'
```

(once this is actually implemented).

To implement it, roughly those steps are needed:

1) Add a new setting to `qutebrowser/config/configdata.yml` (see my notes above and e.g. the `qt.args` setting for some inspiration)
2) Handle the setting in `qutebrowser/config/qtargs.py`, probably best in `init_envvars`.
3) In `tests/unit/config/test_qtargs.py`, adjust the parametrization for the `test_env_vars` function to add a test for the new `qt.environ` setting
4) Finally, also handle the case where a value has been set to `None`/`null` in the config, so that a user can set that to *remove* an existing environment variable - for example:

```con
QT_SCALE_FACTOR=4 qutebrowser --temp-basedir -s qt.environ '{"QT_SCALE_FACTOR": null}'
```

should result in qutebrowser *not* scaling up the UI.
5) Add another test or two for that - make sure you cover both the cases where the variable is already set (so there's something to delete) and where it isn't set - you can use pytest's [monkeypatch](https://docs.pytest.org/en/stable/monkeypatch.html) to do so.
@StutiV Did you intend to unassign yourself?
Is this still being worked on by @StutiV ? I'll give implementing it a shot if not.
I have no idea, but since they unassigned themselves and didn't answer anymore, I guess not... Please go ahead!

Resolving patch:
diff --git a/qutebrowser/config/qtargs.py b/qutebrowser/config/qtargs.py
--- a/qutebrowser/config/qtargs.py
+++ b/qutebrowser/config/qtargs.py
@@ -259,3 +259,9 @@ def init_envvars() -> None:
                    if qtutils.version_check('5.14', compiled=False)
                    else 'QT_AUTO_SCREEN_SCALE_FACTOR')
         os.environ[env_var] = '1'
+
+    for var, val in config.val.qt.environ.items():
+        if val is None and var in os.environ:
+            del os.environ[var]
+        elif val is not None:
+            os.environ[var] = val


Test patch:
diff --git a/tests/unit/config/test_qtargs.py b/tests/unit/config/test_qtargs.py
--- a/tests/unit/config/test_qtargs.py
+++ b/tests/unit/config/test_qtargs.py
@@ -428,6 +428,47 @@ def test_env_vars(self, monkeypatch, config_stub,
 
         assert os.environ[envvar] == expected
 
+    @pytest.mark.parametrize('init_val, config_val', [
+        (   # Test changing a set variable
+            {'QT_SCALE_FACTOR': '2'},
+            {'QT_SCALE_FACTOR': '4'},
+        ),
+        (   # Test setting an unset variable
+            {'QT_SCALE_FACTOR': None},
+            {'QT_SCALE_FACTOR': '3'},
+        ),
+        (   # Test unsetting a variable which is set
+            {'QT_SCALE_FACTOR': '3'},
+            {'QT_SCALE_FACTOR': None},
+        ),
+        (   # Test unsetting a variable which is unset
+            {'QT_SCALE_FACTOR': None},
+            {'QT_SCALE_FACTOR': None},
+        ),
+        (   # Test setting multiple variables
+            {'QT_SCALE_FACTOR': '0', 'QT_PLUGIN_PATH': '/usr/bin', 'QT_NEWVAR': None},
+            {'QT_SCALE_FACTOR': '3', 'QT_PLUGIN_PATH': '/tmp/', 'QT_NEWVAR': 'newval'},
+        )
+    ])
+    def test_environ_settings(self, monkeypatch, config_stub,
+                              init_val, config_val):
+        """Test setting environment variables using qt.environ."""
+        for var, val in init_val.items():
+            if val is None:
+                monkeypatch.setenv(var, '0')
+                monkeypatch.delenv(var, raising=False)
+            else:
+                monkeypatch.setenv(var, val)
+
+        config_stub.val.qt.environ = config_val
+        qtargs.init_envvars()
+
+        for var, result in config_val.items():
+            if result is None:
+                assert var not in os.environ
+            else:
+                assert os.environ[var] == result
+
     @pytest.mark.parametrize('new_qt', [True, False])
     def test_highdpi(self, monkeypatch, config_stub, new_qt):
         """Test HighDPI environment variables.