From c2cc28a72b4d5b1b4b37b44561d3ae74163b3dc1 Mon Sep 17 00:00:00 2001 From: Niklas Haas Date: Thu, 11 Aug 2016 23:05:28 +0200 Subject: [PATCH 1/3] Add new-instance-open-target.window = first-opened Fixes #1060. In the process of adding this, I also decided to rewrite mainwindow.get_window() for clarity (and also because flake8 was warning about complexity). Also adds some tests to the new-instance-target mechanism, in particular a specific test for the issue in question. --- CHANGELOG.asciidoc | 2 +- qutebrowser/config/configdata.py | 6 +- qutebrowser/mainwindow/mainwindow.py | 79 +++++++++------- qutebrowser/utils/objreg.py | 8 +- tests/end2end/features/conftest.py | 11 ++- tests/end2end/features/invoke.feature | 108 ++++++++++++++++++++++ tests/end2end/features/test_invoke_bdd.py | 30 ++++++ tests/end2end/fixtures/quteprocess.py | 30 +++--- 8 files changed, 223 insertions(+), 51 deletions(-) create mode 100644 tests/end2end/features/invoke.feature create mode 100644 tests/end2end/features/test_invoke_bdd.py diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index 50a4086e3..966dbcadb 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -62,7 +62,7 @@ Changed the most recently focused (instead of the last opened) window. This can be configured with the new `new-instance-open-target.window` setting. It can also be set to `last-visible` to show the pages in the most recently - visible window. + visible window, or `first-opened` to use the first (oldest) available window. - Word hints now are more clever about getting the element text from some elements. - Completions for `:help` and `:bind` now also show hidden commands - The `:buffer` completion now also filters using the first column (id). diff --git a/qutebrowser/config/configdata.py b/qutebrowser/config/configdata.py index 959e00dc7..843e8fe64 100644 --- a/qutebrowser/config/configdata.py +++ b/qutebrowser/config/configdata.py @@ -230,8 +230,10 @@ def data(readonly=False): ('new-instance-open-target.window', SettingValue(typ.String( valid_values=typ.ValidValues( - ('last-opened', "Open new tabs in the last opened " - "window."), + ('first-opened', "Open new tabs in the first (oldest) " + "opened window."), + ('last-opened', "Open new tabs in the last (newest) " + "opened window."), ('last-focused', "Open new tabs in the most recently " "focused window."), ('last-visible', "Open new tabs in the most recently " diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 496245b8c..6f5204aa4 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -54,44 +54,61 @@ def get_window(via_ipc, force_window=False, force_tab=False, """ if force_window and force_tab: raise ValueError("force_window and force_tab are mutually exclusive!") + if not via_ipc: # Initial main window return 0 - window_to_raise = None + + open_target = config.get('general', 'new-instance-open-target') + + # Apply any target overrides, ordered by precedence if force_target is not None: open_target = force_target - else: - open_target = config.get('general', 'new-instance-open-target') - if (open_target == 'window' or force_window) and not force_tab: + if force_window: + open_target = 'window' + if force_tab and open_target == 'window': + open_target = 'tab' # for lack of a better default + + window = None + raise_window = False + + # Try to find the existing tab target if opening in a tab + if open_target != 'window': + window = get_target_window() + raise_window = open_target not in ['tab-silent', 'tab-bg-silent'] + + # Otherwise, or if no window was found, create a new one + if window is None: window = MainWindow() window.show() - win_id = window.win_id - window_to_raise = window - else: - try: - win_mode = config.get('general', 'new-instance-open-target.window') - if win_mode == 'last-focused': - window = objreg.last_focused_window() - elif win_mode == 'last-opened': - window = objreg.last_window() - elif win_mode == 'last-visible': - window = objreg.last_visible_window() - except objreg.NoWindow: - # There is no window left, so we open a new one - window = MainWindow() - window.show() - win_id = window.win_id - window_to_raise = window - win_id = window.win_id - if open_target not in ['tab-silent', 'tab-bg-silent']: - window_to_raise = window - if window_to_raise is not None: - window_to_raise.setWindowState( - window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive) - window_to_raise.raise_() - window_to_raise.activateWindow() - QApplication.instance().alert(window_to_raise) - return win_id + raise_window = True + + if raise_window: + window.setWindowState(window.windowState() & ~Qt.WindowMinimized) + window.setWindowState(window.windowState() | Qt.WindowActive) + window.raise_() + window.activateWindow() + QApplication.instance().alert(window) + + return window.win_id + + +def get_target_window(): + """Get the target window for new tabs, or None if none exist.""" + try: + win_mode = config.get('general', 'new-instance-open-target.window') + if win_mode == 'last-focused': + return objreg.last_focused_window() + elif win_mode == 'first-opened': + return objreg.window_by_index(0) + elif win_mode == 'last-opened': + return objreg.window_by_index(-1) + elif win_mode == 'last-visible': + return objreg.last_visible_window() + else: + raise ValueError("Invalid win_mode {}".format(win_mode)) + except objreg.NoWindow: + return None class MainWindow(QWidget): diff --git a/qutebrowser/utils/objreg.py b/qutebrowser/utils/objreg.py index 5fc91c3b8..d7728379e 100644 --- a/qutebrowser/utils/objreg.py +++ b/qutebrowser/utils/objreg.py @@ -298,13 +298,13 @@ def last_focused_window(): try: return get('last-focused-main-window') except KeyError: - return last_window() + return window_by_index(-1) -def last_window(): - """Get the last opened window object.""" +def window_by_index(idx): + """Get the Nth opened window object.""" if not window_registry: raise NoWindow() else: - key = sorted(window_registry)[-1] + key = sorted(window_registry)[idx] return window_registry[key] diff --git a/tests/end2end/features/conftest.py b/tests/end2end/features/conftest.py index de9275213..dd4621930 100644 --- a/tests/end2end/features/conftest.py +++ b/tests/end2end/features/conftest.py @@ -156,15 +156,18 @@ def open_path(quteproc, path): """Open a URL. If used like "When I open ... in a new tab", the URL is opened in a new - tab. With "... in a new window", it's opened in a new window. + tab. With "... in a new window", it's opened in a new window. With + "... as a URL", it's opened according to new-instance-open-target. """ new_tab = False new_window = False + as_url = False wait = True new_tab_suffix = ' in a new tab' new_window_suffix = ' in a new window' do_not_wait_suffix = ' without waiting' + as_url_suffix = ' as a URL' if path.endswith(new_tab_suffix): path = path[:-len(new_tab_suffix)] @@ -172,12 +175,16 @@ def open_path(quteproc, path): elif path.endswith(new_window_suffix): path = path[:-len(new_window_suffix)] new_window = True + elif path.endswith(as_url_suffix): + path = path[:-len(as_url_suffix)] + as_url = True if path.endswith(do_not_wait_suffix): path = path[:-len(do_not_wait_suffix)] wait = False - quteproc.open_path(path, new_tab=new_tab, new_window=new_window, wait=wait) + quteproc.open_path(path, new_tab=new_tab, new_window=new_window, + as_url=as_url, wait=wait) @bdd.when(bdd.parsers.parse("I set {sect} -> {opt} to {value}")) diff --git a/tests/end2end/features/invoke.feature b/tests/end2end/features/invoke.feature new file mode 100644 index 000000000..915f4a3af --- /dev/null +++ b/tests/end2end/features/invoke.feature @@ -0,0 +1,108 @@ +Feature: Invoking a new process + Simulate what happens when running qutebrowser with an existing instance + + Background: + Given I clean up open tabs + + Scenario: Using new-instance-open-target = tab + When I set general -> new-instance-open-target to tab + And I open data/title.html + And I open data/search.html as a URL + Then the following tabs should be open: + - data/title.html + - data/search.html (active) + + Scenario: Using new-instance-open-target = tab-bg + When I set general -> new-instance-open-target to tab-bg + And I open data/title.html + And I open data/search.html as a URL + Then the following tabs should be open: + - data/title.html (active) + - data/search.html + + Scenario: Using new-instance-open-target = window + When I set general -> new-instance-open-target to window + And I open data/title.html + And I open data/search.html as a URL + Then the session should look like: + windows: + - tabs: + - history: + - url: about:blank + - url: http://localhost:*/data/title.html + - tabs: + - history: + - url: http://localhost:*/data/search.html + + Scenario: Using new-instance-open-target.window = last-opened + When I set general -> new-instance-open-target to tab + And I set general -> new-instance-open-target.window to last-opened + And I open data/title.html + And I open data/search.html in a new window + And I open data/hello.txt as a URL + Then the session should look like: + windows: + - tabs: + - history: + - url: about:blank + - url: http://localhost:*/data/title.html + - tabs: + - history: + - url: http://localhost:*/data/search.html + - history: + - url: http://localhost:*/data/hello.txt + + Scenario: Using new-instance-open-target.window = first-opened + When I set general -> new-instance-open-target to tab + And I set general -> new-instance-open-target.window to first-opened + And I open data/title.html + And I open data/search.html in a new window + And I open data/hello.txt as a URL + Then the session should look like: + windows: + - tabs: + - history: + - url: about:blank + - url: http://localhost:*/data/title.html + - history: + - url: http://localhost:*/data/hello.txt + - tabs: + - history: + - url: http://localhost:*/data/search.html + + # issue #1060 + + Scenario: Using target.window = first-opened after tab-detach + When I set general -> new-instance-open-target to tab + And I set general -> new-instance-open-target.window to first-opened + And I open data/title.html + And I open data/search.html in a new tab + And I run :tab-detach + And I open data/hello.txt as a URL + Then the session should look like: + windows: + - tabs: + - history: + - url: about:blank + - url: http://localhost:*/data/title.html + - history: + - url: http://localhost:*/data/hello.txt + - tabs: + - history: + - url: http://localhost:*/data/search.html + + Scenario: Opening a new qutebrowser instance with no parameters + When I set general -> new-instance-open-target to tab + And I set general -> startpage to about:blank + And I open data/title.html + And I spawn a new window + And I wait until about:blank is loaded + Then the session should look like: + windows: + - tabs: + - history: + - url: about:blank + - url: http://localhost:*/data/title.html + - tabs: + - history: + - url: about:blank diff --git a/tests/end2end/features/test_invoke_bdd.py b/tests/end2end/features/test_invoke_bdd.py new file mode 100644 index 000000000..86faf8107 --- /dev/null +++ b/tests/end2end/features/test_invoke_bdd.py @@ -0,0 +1,30 @@ +# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et: + +# Copyright 2016 Florian Bruhin (The Compiler) +# +# This file is part of qutebrowser. +# +# qutebrowser is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# qutebrowser is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with qutebrowser. If not, see . + +import pytest_bdd as bdd +bdd.scenarios('invoke.feature') + + +@bdd.when(bdd.parsers.parse("I spawn a new window")) +def invoke_with(quteproc): + """Spawn a new window via IPC call.""" + quteproc.log_summary("Create a new window") + quteproc.send_ipc([], target_arg='window') + quteproc.wait_for(category='init', module='app', + function='_open_startpage', message='Opening startpage') diff --git a/tests/end2end/fixtures/quteprocess.py b/tests/end2end/fixtures/quteprocess.py index 6469ccf37..7fbe808da 100644 --- a/tests/end2end/fixtures/quteprocess.py +++ b/tests/end2end/fixtures/quteprocess.py @@ -365,6 +365,15 @@ class QuteProc(testprocess.Process): finally: super().after_test() + def send_ipc(self, commands, target_arg=''): + """Send a raw command to the running IPC socket.""" + time.sleep(self._delay / 1000) + + assert self._ipc_socket is not None + ipc.send_to_running_instance(self._ipc_socket, commands, target_arg) + self.wait_for(category='ipc', module='ipc', function='on_ready_read', + message='Read from socket *') + def send_cmd(self, command, count=None, invalid=False, *, escape=True): """Send a command to the running qutebrowser instance. @@ -379,18 +388,13 @@ class QuteProc(testprocess.Process): summary += ' (count {})'.format(count) self.log_summary(summary) - assert self._ipc_socket is not None - - time.sleep(self._delay / 1000) - if escape: command = command.replace('\\', r'\\') if count is not None: command = ':{}:{}'.format(count, command.lstrip(':')) - ipc.send_to_running_instance(self._ipc_socket, [command], - target_arg='') + self.send_ipc([command]) if not invalid: self.wait_for(category='commands', module='command', function='run', message='command called: *') @@ -418,18 +422,22 @@ class QuteProc(testprocess.Process): yield self.set_setting(sect, opt, old_value) - def open_path(self, path, *, new_tab=False, new_window=False, port=None, - https=False, wait=True): + def open_path(self, path, *, new_tab=False, new_window=False, as_url=False, + port=None, https=False, wait=True): """Open the given path on the local webserver in qutebrowser.""" url = self.path_to_url(path, port=port, https=https) - self.open_url(url, new_tab=new_tab, new_window=new_window, wait=wait) + self.open_url(url, new_tab=new_tab, new_window=new_window, + as_url=as_url, wait=wait) - def open_url(self, url, *, new_tab=False, new_window=False, wait=True): + def open_url(self, url, *, new_tab=False, new_window=False, as_url=False, + wait=True): """Open the given url in qutebrowser.""" if new_tab and new_window: raise ValueError("new_tab and new_window given!") - if new_tab: + if as_url: + self.send_cmd(url, invalid=True) + elif new_tab: self.send_cmd(':open -t ' + url) elif new_window: self.send_cmd(':open -w ' + url) From 00b59eedf79b6692e0e28a7972b8b73820449a0b Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 17 Aug 2016 12:02:09 +0200 Subject: [PATCH 2/3] Update docs --- README.asciidoc | 2 +- doc/help/settings.asciidoc | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/README.asciidoc b/README.asciidoc index 2c416f4e4..147e9fa03 100644 --- a/README.asciidoc +++ b/README.asciidoc @@ -166,10 +166,10 @@ Contributors, sorted by the number of commits in descending order: * Thorsten Wißmann * Austin Anderson * Jimmy +* Niklas Haas * Alexey "Averrin" Nabrodov * avk * ZDarian -* Niklas Haas * Milan Svoboda * John ShaggyTwoDope Jenkins * Peter Vilim diff --git a/doc/help/settings.asciidoc b/doc/help/settings.asciidoc index 08d0dd51e..e70f06b6b 100644 --- a/doc/help/settings.asciidoc +++ b/doc/help/settings.asciidoc @@ -454,7 +454,8 @@ Which window to choose when opening links as new tabs. Valid values: - * +last-opened+: Open new tabs in the last opened window. + * +first-opened+: Open new tabs in the first (oldest) opened window. + * +last-opened+: Open new tabs in the last (newest) opened window. * +last-focused+: Open new tabs in the most recently focused window. * +last-visible+: Open new tabs in the most recently visible window. From 781e7554e407331241d9d4784880c7c8169509b2 Mon Sep 17 00:00:00 2001 From: Florian Bruhin Date: Wed, 17 Aug 2016 12:03:55 +0200 Subject: [PATCH 3/3] Set open target for force_tab to tab-silent Fixes #1328 --- CHANGELOG.asciidoc | 2 ++ qutebrowser/mainwindow/mainwindow.py | 3 ++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.asciidoc b/CHANGELOG.asciidoc index ba92841fb..9cbfea251 100644 --- a/CHANGELOG.asciidoc +++ b/CHANGELOG.asciidoc @@ -79,6 +79,8 @@ Changed - Counts can now be used with special keybindings (e.g. with modifiers). This was already implemented for v0.7.0 originally, but got reverted because it caused some issues and then never re-applied. +- Sending a command to an existing instance (via "qutebrowser :reload") now + doesn't mark it as urgent anymore. Deprecated ~~~~~~~~~~ diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py index 6f5204aa4..6bb35735c 100644 --- a/qutebrowser/mainwindow/mainwindow.py +++ b/qutebrowser/mainwindow/mainwindow.py @@ -67,7 +67,8 @@ def get_window(via_ipc, force_window=False, force_tab=False, if force_window: open_target = 'window' if force_tab and open_target == 'window': - open_target = 'tab' # for lack of a better default + # Command sent via IPC + open_target = 'tab-silent' window = None raise_window = False