summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorFlorian Bruhin <me@the-compiler.org>2020-05-27 15:02:44 +0200
committerFlorian Bruhin <me@the-compiler.org>2020-05-27 15:18:13 +0200
commit85aee23639bb78c07151618af031317c9ecd3408 (patch)
tree839b86464da56fba903185ca6249bffd0aeecc8b
parent70625c3f8718124137e087b52db66983cf4df0ef (diff)
downloadqutebrowser-85aee23639bb78c07151618af031317c9ecd3408.tar.gz
qutebrowser-85aee23639bb78c07151618af031317c9ecd3408.zip
Add dark mode settings
Closes #5394 See #2377
-rw-r--r--doc/changelog.asciidoc3
-rw-r--r--doc/help/settings.asciidoc163
-rw-r--r--qutebrowser/config/configdata.yml163
-rw-r--r--qutebrowser/config/configinit.py89
-rw-r--r--scripts/dev/misc_checks.py2
-rw-r--r--tests/unit/config/test_configinit.py116
6 files changed, 534 insertions, 2 deletions
diff --git a/doc/changelog.asciidoc b/doc/changelog.asciidoc
index d6e42e2b6..a6aead789 100644
--- a/doc/changelog.asciidoc
+++ b/doc/changelog.asciidoc
@@ -41,6 +41,9 @@ Added
- New `colors.contextmenu.disabled.{fg,bg}` settings to customize colors for
disabled items in the context menu.
- New line selection mode (`:toggle-selection --line`), bound to `Shift-V` in caret mode.
+- New `colors.webpage.darkmode.*` settings to control Chromium's dark mode.
+ Note that those settings only work with QtWebEngine on Qt >= 5.14 and require
+ a restart of qutebrowser.
Changed
~~~~~~~
diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc
index 4c20126c3..3787238a9 100644
--- a/doc/help/settings.asciidoc
+++ b/doc/help/settings.asciidoc
@@ -111,6 +111,15 @@
|<<colors.tabs.selected.odd.bg,colors.tabs.selected.odd.bg>>|Background color of selected odd tabs.
|<<colors.tabs.selected.odd.fg,colors.tabs.selected.odd.fg>>|Foreground color of selected odd tabs.
|<<colors.webpage.bg,colors.webpage.bg>>|Background color for webpages if unset (or empty to use the theme's color).
+|<<colors.webpage.darkmode.algorithm,colors.webpage.darkmode.algorithm>>|Which algorithm to use for modifying how colors are rendered with darkmode.
+|<<colors.webpage.darkmode.contrast,colors.webpage.darkmode.contrast>>|Contrast for dark mode.
+|<<colors.webpage.darkmode.enabled,colors.webpage.darkmode.enabled>>|Render all web contents using a dark theme.
+|<<colors.webpage.darkmode.grayscale.all,colors.webpage.darkmode.grayscale.all>>|Render all colors as grayscale.
+|<<colors.webpage.darkmode.grayscale.images,colors.webpage.darkmode.grayscale.images>>|Desaturation factor for images in dark mode.
+|<<colors.webpage.darkmode.policy.images,colors.webpage.darkmode.policy.images>>|Which images to apply dark mode to.
+|<<colors.webpage.darkmode.policy.page,colors.webpage.darkmode.policy.page>>|Which pages to apply dark mode to.
+|<<colors.webpage.darkmode.threshold.background,colors.webpage.darkmode.threshold.background>>|Threshold for inverting background elements with dark mode.
+|<<colors.webpage.darkmode.threshold.text,colors.webpage.darkmode.threshold.text>>|Threshold for inverting text with dark mode.
|<<colors.webpage.prefers_color_scheme_dark,colors.webpage.prefers_color_scheme_dark>>|Force `prefers-color-scheme: dark` colors for websites.
|<<completion.cmd_history_max_items,completion.cmd_history_max_items>>|Number of commands to save in the command history.
|<<completion.delay,completion.delay>>|Delay (in milliseconds) before updating completions after typing a character.
@@ -1534,6 +1543,160 @@ Type: <<types,QtColor>>
Default: +pass:[white]+
+[[colors.webpage.darkmode.algorithm]]
+=== colors.webpage.darkmode.algorithm
+Which algorithm to use for modifying how colors are rendered with darkmode.
+This setting requires a restart.
+
+Type: <<types,String>>
+
+Valid values:
+
+ * +lightness-cielab+: Modify colors by converting them to CIELAB color space and inverting the L value.
+ * +lightness-hsl+: Modify colors by converting them to the HSL color space and inverting the lightness (i.e. the "L" in HSL).
+ * +brightness-rgb+: Modify colors by subtracting each of r, g, and b from their maximum value.
+
+Default: +pass:[lightness-cielab]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.contrast]]
+=== colors.webpage.darkmode.contrast
+Contrast for dark mode.
+This only has an effect when `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or `brightness-rgb`.
+This setting requires a restart.
+
+Type: <<types,Float>>
+
+Default: +pass:[0.0]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.enabled]]
+=== colors.webpage.darkmode.enabled
+Render all web contents using a dark theme.
+Example configurations from Chromium's `chrome://flags`:
+
+- "With simple HSL/CIELAB/RGB-based inversion": Set
+ `colors.webpage.darkmode.algorithm` accordingly.
+
+- "With selective image inversion": Set
+ `colors.webpage.darkmode.policy.images` to `smart`.
+
+- "With selective inversion of non-image elements": Set
+ `colors.webpage.darkmode.threshold.text` to 150 and
+ `colors.webpage.darkmode.threshold.background` to 205.
+
+- "With selective inversion of everything": Combines the two variants
+ above.
+This setting requires a restart.
+
+Type: <<types,Bool>>
+
+Default: +pass:[false]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.grayscale.all]]
+=== colors.webpage.darkmode.grayscale.all
+Render all colors as grayscale.
+This only has an effect when `colors.webpage.darkmode.algorithm` is set to `lightness-hsl` or `brightness-rgb`.
+This setting requires a restart.
+
+Type: <<types,Bool>>
+
+Default: +pass:[false]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.grayscale.images]]
+=== colors.webpage.darkmode.grayscale.images
+Desaturation factor for images in dark mode.
+If set to 0, images are left as-is. If set to 1, images are completely grayscale. Values between 0 and 1 desaturate the colors accordingly.
+This setting requires a restart.
+
+Type: <<types,Float>>
+
+Default: +pass:[0.0]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.policy.images]]
+=== colors.webpage.darkmode.policy.images
+Which images to apply dark mode to.
+This setting requires a restart.
+
+Type: <<types,String>>
+
+Valid values:
+
+ * +always+: Apply dark mode filter to all images.
+ * +never+: Never apply dark mode filter to any images.
+ * +smart+: Apply dark mode based on image content.
+
+Default: +pass:[never]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.policy.page]]
+=== colors.webpage.darkmode.policy.page
+Which pages to apply dark mode to.
+This setting requires a restart.
+
+Type: <<types,String>>
+
+Valid values:
+
+ * +always+: Apply dark mode filter to all frames, regardless of content.
+ * +smart+: Apply dark mode filter to frames based on background color.
+
+Default: +pass:[smart]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.threshold.background]]
+=== colors.webpage.darkmode.threshold.background
+Threshold for inverting background elements with dark mode.
+Background elements with brightness above this threshold will be inverted, and below it will be left as in the original, non-dark-mode page. Set to 256 to never invert the color or to 0 to always invert it.
+Note: This behavior is the opposite of `colors.webpage.darkmode.threshold.text`!
+This setting requires a restart.
+
+Type: <<types,Int>>
+
+Default: +pass:[0]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
+[[colors.webpage.darkmode.threshold.text]]
+=== colors.webpage.darkmode.threshold.text
+Threshold for inverting text with dark mode.
+Text colors with brightness below this threshold will be inverted, and above it will be left as in the original, non-dark-mode page. Set to 256 to always invert text color or to 0 to never invert text color.
+This setting requires a restart.
+
+Type: <<types,Int>>
+
+Default: +pass:[256]+
+
+On QtWebEngine, this setting requires Qt 5.14 or newer.
+
+On QtWebKit, this setting is unavailable.
+
[[colors.webpage.prefers_color_scheme_dark]]
=== colors.webpage.prefers_color_scheme_dark
Force `prefers-color-scheme: dark` colors for websites.
diff --git a/qutebrowser/config/configdata.yml b/qutebrowser/config/configdata.yml
index 46b745089..dc49e4cdd 100644
--- a/qutebrowser/config/configdata.yml
+++ b/qutebrowser/config/configdata.yml
@@ -2612,6 +2612,169 @@ colors.webpage.prefers_color_scheme_dark:
QtWebEngine: Qt 5.14
QtWebKit: false
+## dark mode
+
+# darkModeClassifierType is not exposed, as the icon classifier isn't actually
+# implemented in Chromium:
+#
+# https://source.chromium.org/chromium/chromium/src/+/master:third_party/blink/renderer/platform/graphics/dark_mode_icon_classifier.cc
+
+colors.webpage.darkmode.enabled:
+ default: false
+ type: Bool
+ desc: >-
+ Render all web contents using a dark theme.
+
+ Example configurations from Chromium's `chrome://flags`:
+
+
+ - "With simple HSL/CIELAB/RGB-based inversion": Set
+ `colors.webpage.darkmode.algorithm` accordingly.
+
+ - "With selective image inversion": Set
+ `colors.webpage.darkmode.policy.images` to `smart`.
+
+ - "With selective inversion of non-image elements": Set
+ `colors.webpage.darkmode.threshold.text` to 150 and
+ `colors.webpage.darkmode.threshold.background` to 205.
+
+ - "With selective inversion of everything": Combines the two variants
+ above.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.algorithm:
+ default: lightness-cielab
+ desc: "Which algorithm to use for modifying how colors are rendered with
+ darkmode."
+ type:
+ name: String
+ valid_values:
+ - lightness-cielab: Modify colors by converting them to CIELAB color
+ space and inverting the L value.
+ - lightness-hsl: Modify colors by converting them to the HSL color space
+ and inverting the lightness (i.e. the "L" in HSL).
+ - brightness-rgb: Modify colors by subtracting each of r, g, and b from
+ their maximum value.
+ # kSimpleInvertForTesting is not exposed, as it's equivalent to
+ # kInvertBrightness without gamma correction, and only available for
+ # Chromium's automated tests
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.contrast:
+ default: 0.0
+ type:
+ name: Float
+ minval: -1.0
+ maxval: 1.0
+ desc: >-
+ Contrast for dark mode.
+
+ This only has an effect when `colors.webpage.darkmode.algorithm` is set to
+ `lightness-hsl` or `brightness-rgb`.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.policy.images:
+ default: never
+ type:
+ name: String
+ valid_values:
+ - always: Apply dark mode filter to all images.
+ - never: Never apply dark mode filter to any images.
+ - smart: Apply dark mode based on image content.
+ desc: Which images to apply dark mode to.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.policy.page:
+ default: smart
+ type:
+ name: String
+ valid_values:
+ - always: Apply dark mode filter to all frames, regardless of content.
+ - smart: Apply dark mode filter to frames based on background color.
+ desc: Which pages to apply dark mode to.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.threshold.text:
+ default: 256
+ type:
+ name: Int
+ minval: 0
+ maxval: 256
+ desc: >-
+ Threshold for inverting text with dark mode.
+
+ Text colors with brightness below this threshold will be inverted, and
+ above it will be left as in the original, non-dark-mode page. Set to 256
+ to always invert text color or to 0 to never invert text color.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.threshold.background:
+ default: 0
+ type:
+ name: Int
+ minval: 0
+ maxval: 256
+ desc: >-
+ Threshold for inverting background elements with dark mode.
+
+ Background elements with brightness above this threshold will be inverted,
+ and below it will be left as in the original, non-dark-mode page. Set to
+ 256 to never invert the color or to 0 to always invert it.
+
+ Note: This behavior is the opposite of
+ `colors.webpage.darkmode.threshold.text`!
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.grayscale.all:
+ default: false
+ type: Bool
+ desc: >-
+ Render all colors as grayscale.
+
+ This only has an effect when `colors.webpage.darkmode.algorithm` is set to
+ `lightness-hsl` or `brightness-rgb`.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
+colors.webpage.darkmode.grayscale.images:
+ default: 0.0
+ type:
+ name: Float
+ minval: 0.0
+ maxval: 1.0
+ desc: >-
+ Desaturation factor for images in dark mode.
+
+ If set to 0, images are left as-is. If set to 1, images are completely
+ grayscale. Values between 0 and 1 desaturate the colors accordingly.
+ restart: true
+ backend:
+ QtWebEngine: Qt 5.14
+ QtWebKit: false
+
# emacs: '
## fonts
diff --git a/qutebrowser/config/configinit.py b/qutebrowser/config/configinit.py
index b15225210..3c80cfe1b 100644
--- a/qutebrowser/config/configinit.py
+++ b/qutebrowser/config/configinit.py
@@ -199,6 +199,90 @@ def qt_args(namespace: argparse.Namespace) -> typing.List[str]:
return argv
+def _darkmode_settings() -> typing.Iterator[typing.Tuple[str, str]]:
+ """Get necessary blink settings to configure dark mode for QtWebEngine."""
+ if not config.val.colors.webpage.darkmode.enabled:
+ return
+
+ # Mapping from a colors.webpage.darkmode.algorithm setting value to
+ # Chromium's DarkModeInversionAlgorithm enum values.
+ algorithms = {
+ # 0: kOff (not exposed)
+ # 1: kSimpleInvertForTesting (not exposed)
+ 'brightness-rgb': 2, # kInvertBrightness
+ 'lightness-hsl': 3, # kInvertLightness
+ 'lightness-cielab': 4, # kInvertLightnessLAB
+ }
+
+ # Mapping from a colors.webpage.darkmode.policy.images setting value to
+ # Chromium's DarkModeImagePolicy enum values.
+ image_policies = {
+ 'always': 0, # kFilterAll
+ 'never': 1, # kFilterNone
+ 'smart': 2, # kFilterSmart
+ }
+
+ # Mapping from a colors.webpage.darkmode.policy.page setting value to
+ # Chromium's DarkModePagePolicy enum values.
+ page_policies = {
+ 'always': 0, # kFilterAll
+ 'smart': 1, # kFilterByBackground
+ }
+
+ bools = {
+ True: 'true',
+ False: 'false',
+ }
+
+ _setting_description_type = typing.Tuple[
+ str, # qutebrowser option name
+ str, # darkmode setting name
+ # Mapping from the config value to a string (or something convertable
+ # to a string) which gets passed to Chromium.
+ typing.Optional[typing.Mapping[typing.Any, typing.Union[str, int]]],
+ ]
+ if qtutils.version_check('5.15', compiled=False):
+ settings = [
+ ('enabled', 'Enabled', bools),
+ ('algorithm', 'InversionAlgorithm', algorithms),
+ ] # type: typing.List[_setting_description_type]
+ mandatory_setting = 'enabled'
+ else:
+ settings = [
+ ('algorithm', '', algorithms),
+ ]
+ mandatory_setting = 'algorithm'
+
+ settings += [
+ ('contrast', 'Contrast', None),
+ ('policy.images', 'ImagePolicy', image_policies),
+ ('policy.page', 'PagePolicy', page_policies),
+ ('threshold.text', 'TextBrightnessThreshold', None),
+ ('threshold.background', 'BackgroundBrightnessThreshold', None),
+ ('grayscale.all', 'Grayscale', bools),
+ ('grayscale.images', 'ImageGrayscale', None),
+ ]
+
+ for setting, key, mapping in settings:
+ # To avoid blowing up the commandline length, we only pass modified
+ # settings to Chromium, as our defaults line up with Chromium's.
+ # However, we always pass enabled/algorithm to make sure dark mode gets
+ # actually turned on.
+ value = config.instance.get(
+ 'colors.webpage.darkmode.' + setting,
+ fallback=setting == mandatory_setting)
+ if isinstance(value, usertypes.Unset):
+ continue
+
+ if mapping is not None:
+ value = mapping[value]
+
+ # FIXME: This is "forceDarkMode" starting with Chromium 83
+ prefix = 'darkMode'
+
+ yield prefix + key, str(value)
+
+
def _qtwebengine_args(namespace: argparse.Namespace) -> typing.Iterator[str]:
"""Get the QtWebEngine arguments to use based on the config."""
if not qtutils.version_check('5.11', compiled=False):
@@ -224,6 +308,11 @@ def _qtwebengine_args(namespace: argparse.Namespace) -> typing.Iterator[str]:
yield '--enable-logging'
yield '--v=1'
+ blink_settings = list(_darkmode_settings())
+ if blink_settings:
+ yield '--blink-settings=' + ','.join('{}={}'.format(k, v)
+ for k, v in blink_settings)
+
settings = {
'qt.force_software_rendering': {
'software-opengl': None,
diff --git a/scripts/dev/misc_checks.py b/scripts/dev/misc_checks.py
index 24c3a1ddc..6bf411bba 100644
--- a/scripts/dev/misc_checks.py
+++ b/scripts/dev/misc_checks.py
@@ -91,7 +91,7 @@ def check_spelling():
'[Mm]ininum', '[Rr]esett?ed', '[Rr]ecieved', '[Rr]egularily',
'[Uu]nderlaying', '[Ii]nexistant', '[Ee]lipsis', 'commiting',
'existant', '[Rr]esetted', '[Ss]imilarily', '[Ii]nformations',
- '[Aa]n [Uu][Rr][Ll]'}
+ '[Aa]n [Uu][Rr][Ll]', '[Tt]reshold'}
# Words which look better when splitted, but might need some fine tuning.
words |= {'[Ww]ebelements', '[Mm]ouseevent', '[Kk]eysequence',
diff --git a/tests/unit/config/test_configinit.py b/tests/unit/config/test_configinit.py
index 731c62a66..25c6ce85e 100644
--- a/tests/unit/config/test_configinit.py
+++ b/tests/unit/config/test_configinit.py
@@ -28,7 +28,7 @@ import pytest
from qutebrowser import qutebrowser
from qutebrowser.config import (config, configexc, configfiles, configinit,
configdata, configtypes)
-from qutebrowser.utils import objreg, usertypes
+from qutebrowser.utils import objreg, usertypes, version
from helpers import utils
@@ -705,6 +705,120 @@ class TestQtArgs:
assert ('--force-dark-mode' in args) == added
+ def test_blink_settings(self, config_stub, monkeypatch, parser):
+ monkeypatch.setattr(configinit.objects, 'backend',
+ usertypes.Backend.QtWebEngine)
+ monkeypatch.setattr(configinit.qtutils, 'version_check',
+ lambda version, exact=False, compiled=True:
+ True)
+
+ config_stub.val.colors.webpage.darkmode.enabled = True
+
+ parsed = parser.parse_args([])
+ args = configinit.qt_args(parsed)
+
+ assert '--blink-settings=darkModeEnabled=true' in args
+
+
+class TestDarkMode:
+
+ @pytest.mark.parametrize('settings, new_qt, expected', [
+ # Disabled
+ ({}, True, []),
+ ({}, False, []),
+
+ # Enabled without customization
+ (
+ {'enabled': True},
+ True,
+ [('darkModeEnabled', 'true')]
+ ),
+ (
+ {'enabled': True},
+ False,
+ [('darkMode', '4')]
+ ),
+
+ # Algorithm
+ (
+ {'enabled': True, 'algorithm': 'brightness-rgb'},
+ True,
+ [('darkModeEnabled', 'true'),
+ ('darkModeInversionAlgorithm', '2')],
+ ),
+ (
+ {'enabled': True, 'algorithm': 'brightness-rgb'},
+ False,
+ [('darkMode', '2')],
+ ),
+
+ ])
+ @utils.qt514
+ def test_basics(self, config_stub, monkeypatch,
+ settings, new_qt, expected):
+ for k, v in settings.items():
+ config_stub.set_obj('colors.webpage.darkmode.' + k, v)
+ monkeypatch.setattr(configinit.qtutils, 'version_check',
+ lambda version, exact=False, compiled=True:
+ new_qt)
+
+ assert list(configinit._darkmode_settings()) == expected
+
+ @pytest.mark.parametrize('setting, value, exp_key, exp_val', [
+ ('contrast', -0.5,
+ 'darkModeContrast', '-0.5'),
+ ('policy.page', 'smart',
+ 'darkModePagePolicy', '1'),
+ ('policy.images', 'smart',
+ 'darkModeImagePolicy', '2'),
+ ('threshold.text', 100,
+ 'darkModeTextBrightnessThreshold', '100'),
+ ('threshold.background', 100,
+ 'darkModeBackgroundBrightnessThreshold', '100'),
+ ('grayscale.all', True,
+ 'darkModeGrayscale', 'true'),
+ ('grayscale.images', 0.5,
+ 'darkModeImageGrayscale', '0.5'),
+ ])
+ def test_customization(self, config_stub, monkeypatch,
+ setting, value, exp_key, exp_val):
+ config_stub.val.colors.webpage.darkmode.enabled = True
+ config_stub.set_obj('colors.webpage.darkmode.' + setting, value)
+ monkeypatch.setattr(configinit.qtutils, 'version_check',
+ lambda version, exact=False, compiled=True:
+ True)
+
+ expected = [('darkModeEnabled', 'true'), (exp_key, exp_val)]
+ assert list(configinit._darkmode_settings()) == expected
+
+ @utils.qt514
+ def test_new_chromium(self):
+ """Fail if we encounter an unknown Chromium version.
+
+ Dark mode in Chromium currently is undergoing various changes (as it's
+ relatively recent), and Qt 5.15 is supposed to update the underlying
+ Chromium at some point.
+
+ Make this test fail deliberately with newer Chromium versions, so that
+ we can test whether dark mode still works manually, and adjust if not.
+ """
+ assert version._chromium_version() in [
+ 'unavailable', # QtWebKit
+ '77.0.3865.129', # Qt 5.14
+ '80.0.3987.163', # Qt 5.15
+ ]
+
+ def test_options(self, configdata_init):
+ """Make sure all darkmode options have the right attributes set."""
+ for name, opt in configdata.DATA.items():
+ if not name.startswith('colors.webpage.darkmode.'):
+ continue
+
+ backends = {'QtWebEngine': 'Qt 5.14', 'QtWebKit': False}
+ assert not opt.supports_pattern, name
+ assert opt.restart, name
+ assert opt.raw_backends == backends, name
+
@pytest.mark.parametrize('arg, confval, used', [
# overridden by commandline arg