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.
This commit is contained in:
parent
0058917b8d
commit
c2cc28a72b
@ -62,7 +62,7 @@ Changed
|
|||||||
the most recently focused (instead of the last opened) window. This can be
|
the most recently focused (instead of the last opened) window. This can be
|
||||||
configured with the new `new-instance-open-target.window` setting.
|
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
|
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.
|
- Word hints now are more clever about getting the element text from some elements.
|
||||||
- Completions for `:help` and `:bind` now also show hidden commands
|
- Completions for `:help` and `:bind` now also show hidden commands
|
||||||
- The `:buffer` completion now also filters using the first column (id).
|
- The `:buffer` completion now also filters using the first column (id).
|
||||||
|
@ -230,8 +230,10 @@ def data(readonly=False):
|
|||||||
('new-instance-open-target.window',
|
('new-instance-open-target.window',
|
||||||
SettingValue(typ.String(
|
SettingValue(typ.String(
|
||||||
valid_values=typ.ValidValues(
|
valid_values=typ.ValidValues(
|
||||||
('last-opened', "Open new tabs in the last opened "
|
('first-opened', "Open new tabs in the first (oldest) "
|
||||||
"window."),
|
"opened window."),
|
||||||
|
('last-opened', "Open new tabs in the last (newest) "
|
||||||
|
"opened window."),
|
||||||
('last-focused', "Open new tabs in the most recently "
|
('last-focused', "Open new tabs in the most recently "
|
||||||
"focused window."),
|
"focused window."),
|
||||||
('last-visible', "Open new tabs in the most recently "
|
('last-visible', "Open new tabs in the most recently "
|
||||||
|
@ -54,44 +54,61 @@ def get_window(via_ipc, force_window=False, force_tab=False,
|
|||||||
"""
|
"""
|
||||||
if force_window and force_tab:
|
if force_window and force_tab:
|
||||||
raise ValueError("force_window and force_tab are mutually exclusive!")
|
raise ValueError("force_window and force_tab are mutually exclusive!")
|
||||||
|
|
||||||
if not via_ipc:
|
if not via_ipc:
|
||||||
# Initial main window
|
# Initial main window
|
||||||
return 0
|
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:
|
if force_target is not None:
|
||||||
open_target = force_target
|
open_target = force_target
|
||||||
else:
|
if force_window:
|
||||||
open_target = config.get('general', 'new-instance-open-target')
|
open_target = 'window'
|
||||||
if (open_target == 'window' or force_window) and not force_tab:
|
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 = MainWindow()
|
||||||
window.show()
|
window.show()
|
||||||
win_id = window.win_id
|
raise_window = True
|
||||||
window_to_raise = window
|
|
||||||
else:
|
if raise_window:
|
||||||
try:
|
window.setWindowState(window.windowState() & ~Qt.WindowMinimized)
|
||||||
win_mode = config.get('general', 'new-instance-open-target.window')
|
window.setWindowState(window.windowState() | Qt.WindowActive)
|
||||||
if win_mode == 'last-focused':
|
window.raise_()
|
||||||
window = objreg.last_focused_window()
|
window.activateWindow()
|
||||||
elif win_mode == 'last-opened':
|
QApplication.instance().alert(window)
|
||||||
window = objreg.last_window()
|
|
||||||
elif win_mode == 'last-visible':
|
return window.win_id
|
||||||
window = objreg.last_visible_window()
|
|
||||||
except objreg.NoWindow:
|
|
||||||
# There is no window left, so we open a new one
|
def get_target_window():
|
||||||
window = MainWindow()
|
"""Get the target window for new tabs, or None if none exist."""
|
||||||
window.show()
|
try:
|
||||||
win_id = window.win_id
|
win_mode = config.get('general', 'new-instance-open-target.window')
|
||||||
window_to_raise = window
|
if win_mode == 'last-focused':
|
||||||
win_id = window.win_id
|
return objreg.last_focused_window()
|
||||||
if open_target not in ['tab-silent', 'tab-bg-silent']:
|
elif win_mode == 'first-opened':
|
||||||
window_to_raise = window
|
return objreg.window_by_index(0)
|
||||||
if window_to_raise is not None:
|
elif win_mode == 'last-opened':
|
||||||
window_to_raise.setWindowState(
|
return objreg.window_by_index(-1)
|
||||||
window.windowState() & ~Qt.WindowMinimized | Qt.WindowActive)
|
elif win_mode == 'last-visible':
|
||||||
window_to_raise.raise_()
|
return objreg.last_visible_window()
|
||||||
window_to_raise.activateWindow()
|
else:
|
||||||
QApplication.instance().alert(window_to_raise)
|
raise ValueError("Invalid win_mode {}".format(win_mode))
|
||||||
return win_id
|
except objreg.NoWindow:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class MainWindow(QWidget):
|
class MainWindow(QWidget):
|
||||||
|
@ -298,13 +298,13 @@ def last_focused_window():
|
|||||||
try:
|
try:
|
||||||
return get('last-focused-main-window')
|
return get('last-focused-main-window')
|
||||||
except KeyError:
|
except KeyError:
|
||||||
return last_window()
|
return window_by_index(-1)
|
||||||
|
|
||||||
|
|
||||||
def last_window():
|
def window_by_index(idx):
|
||||||
"""Get the last opened window object."""
|
"""Get the Nth opened window object."""
|
||||||
if not window_registry:
|
if not window_registry:
|
||||||
raise NoWindow()
|
raise NoWindow()
|
||||||
else:
|
else:
|
||||||
key = sorted(window_registry)[-1]
|
key = sorted(window_registry)[idx]
|
||||||
return window_registry[key]
|
return window_registry[key]
|
||||||
|
@ -156,15 +156,18 @@ def open_path(quteproc, path):
|
|||||||
"""Open a URL.
|
"""Open a URL.
|
||||||
|
|
||||||
If used like "When I open ... in a new tab", the URL is opened in a new
|
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_tab = False
|
||||||
new_window = False
|
new_window = False
|
||||||
|
as_url = False
|
||||||
wait = True
|
wait = True
|
||||||
|
|
||||||
new_tab_suffix = ' in a new tab'
|
new_tab_suffix = ' in a new tab'
|
||||||
new_window_suffix = ' in a new window'
|
new_window_suffix = ' in a new window'
|
||||||
do_not_wait_suffix = ' without waiting'
|
do_not_wait_suffix = ' without waiting'
|
||||||
|
as_url_suffix = ' as a URL'
|
||||||
|
|
||||||
if path.endswith(new_tab_suffix):
|
if path.endswith(new_tab_suffix):
|
||||||
path = path[:-len(new_tab_suffix)]
|
path = path[:-len(new_tab_suffix)]
|
||||||
@ -172,12 +175,16 @@ def open_path(quteproc, path):
|
|||||||
elif path.endswith(new_window_suffix):
|
elif path.endswith(new_window_suffix):
|
||||||
path = path[:-len(new_window_suffix)]
|
path = path[:-len(new_window_suffix)]
|
||||||
new_window = True
|
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):
|
if path.endswith(do_not_wait_suffix):
|
||||||
path = path[:-len(do_not_wait_suffix)]
|
path = path[:-len(do_not_wait_suffix)]
|
||||||
wait = False
|
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}"))
|
@bdd.when(bdd.parsers.parse("I set {sect} -> {opt} to {value}"))
|
||||||
|
108
tests/end2end/features/invoke.feature
Normal file
108
tests/end2end/features/invoke.feature
Normal file
@ -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
|
30
tests/end2end/features/test_invoke_bdd.py
Normal file
30
tests/end2end/features/test_invoke_bdd.py
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# vim: ft=python fileencoding=utf-8 sts=4 sw=4 et:
|
||||||
|
|
||||||
|
# Copyright 2016 Florian Bruhin (The Compiler) <mail@qutebrowser.org>
|
||||||
|
#
|
||||||
|
# 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 <http://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
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')
|
@ -365,6 +365,15 @@ class QuteProc(testprocess.Process):
|
|||||||
finally:
|
finally:
|
||||||
super().after_test()
|
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):
|
def send_cmd(self, command, count=None, invalid=False, *, escape=True):
|
||||||
"""Send a command to the running qutebrowser instance.
|
"""Send a command to the running qutebrowser instance.
|
||||||
|
|
||||||
@ -379,18 +388,13 @@ class QuteProc(testprocess.Process):
|
|||||||
summary += ' (count {})'.format(count)
|
summary += ' (count {})'.format(count)
|
||||||
self.log_summary(summary)
|
self.log_summary(summary)
|
||||||
|
|
||||||
assert self._ipc_socket is not None
|
|
||||||
|
|
||||||
time.sleep(self._delay / 1000)
|
|
||||||
|
|
||||||
if escape:
|
if escape:
|
||||||
command = command.replace('\\', r'\\')
|
command = command.replace('\\', r'\\')
|
||||||
|
|
||||||
if count is not None:
|
if count is not None:
|
||||||
command = ':{}:{}'.format(count, command.lstrip(':'))
|
command = ':{}:{}'.format(count, command.lstrip(':'))
|
||||||
|
|
||||||
ipc.send_to_running_instance(self._ipc_socket, [command],
|
self.send_ipc([command])
|
||||||
target_arg='')
|
|
||||||
if not invalid:
|
if not invalid:
|
||||||
self.wait_for(category='commands', module='command',
|
self.wait_for(category='commands', module='command',
|
||||||
function='run', message='command called: *')
|
function='run', message='command called: *')
|
||||||
@ -418,18 +422,22 @@ class QuteProc(testprocess.Process):
|
|||||||
yield
|
yield
|
||||||
self.set_setting(sect, opt, old_value)
|
self.set_setting(sect, opt, old_value)
|
||||||
|
|
||||||
def open_path(self, path, *, new_tab=False, new_window=False, port=None,
|
def open_path(self, path, *, new_tab=False, new_window=False, as_url=False,
|
||||||
https=False, wait=True):
|
port=None, https=False, wait=True):
|
||||||
"""Open the given path on the local webserver in qutebrowser."""
|
"""Open the given path on the local webserver in qutebrowser."""
|
||||||
url = self.path_to_url(path, port=port, https=https)
|
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."""
|
"""Open the given url in qutebrowser."""
|
||||||
if new_tab and new_window:
|
if new_tab and new_window:
|
||||||
raise ValueError("new_tab and new_window given!")
|
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)
|
self.send_cmd(':open -t ' + url)
|
||||||
elif new_window:
|
elif new_window:
|
||||||
self.send_cmd(':open -w ' + url)
|
self.send_cmd(':open -w ' + url)
|
||||||
|
Loading…
Reference in New Issue
Block a user