Repository: qutebrowser/qutebrowser

Issue:
Add --target option of "private"
Including an option to --target for private would allow users to launch a qutebrowser private instance. Currently running qutebrowser ':open -p url' from console will spawn a normal instances and a private instance and is less than ideal. 


Discussion:

Resolving patch:
diff --git a/qutebrowser/app.py b/qutebrowser/app.py
--- a/qutebrowser/app.py
+++ b/qutebrowser/app.py
@@ -225,12 +225,14 @@ def _process_args(args):
 
     if not sessions.session_manager.did_load:
         log.init.debug("Initializing main window...")
-        if config.val.content.private_browsing and qtutils.is_single_process():
+        private = args.target == 'private-window'
+        if (config.val.content.private_browsing or
+                private) and qtutils.is_single_process():
             err = Exception("Private windows are unavailable with "
                             "the single-process process model.")
             error.handle_fatal_exc(err, args, 'Cannot start in private mode')
             sys.exit(usertypes.Exit.err_init)
-        window = mainwindow.MainWindow(private=None)
+        window = mainwindow.MainWindow(private=private)
         if not args.nowindow:
             window.show()
         q_app.setActiveWindow(window)
@@ -287,8 +289,10 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
                     ipc. If the --target argument was not specified, target_arg
                     will be an empty string.
     """
+    is_private = 'private-window' if target_arg == 'private-window' else None
     if via_ipc and not args:
-        win_id = mainwindow.get_window(via_ipc, force_window=True)
+        win_id = mainwindow.get_window(via_ipc, force_window=True,
+                                       force_target=is_private)
         _open_startpage(win_id)
         return
     win_id = None
@@ -301,7 +305,8 @@ def process_pos_args(args, via_ipc=False, cwd=None, target_arg=None):
             commandrunner.run_safely(cmd[1:])
         elif not cmd:
             log.init.debug("Empty argument")
-            win_id = mainwindow.get_window(via_ipc, force_window=True)
+            win_id = mainwindow.get_window(via_ipc, force_window=True,
+                                           force_target=is_private)
         else:
             if via_ipc and target_arg and target_arg != 'auto':
                 open_target = target_arg
diff --git a/qutebrowser/mainwindow/mainwindow.py b/qutebrowser/mainwindow/mainwindow.py
--- a/qutebrowser/mainwindow/mainwindow.py
+++ b/qutebrowser/mainwindow/mainwindow.py
@@ -26,7 +26,7 @@
 import typing
 
 from PyQt5.QtCore import (pyqtSignal, pyqtSlot, QRect, QPoint, QTimer, Qt,
-                          QCoreApplication, QEventLoop)
+                          QCoreApplication, QEventLoop, QByteArray, QObject)
 from PyQt5.QtWidgets import QWidget, QVBoxLayout, QApplication, QSizePolicy
 
 from qutebrowser.commands import runners
@@ -44,8 +44,11 @@
 win_id_gen = itertools.count(0)
 
 
-def get_window(via_ipc, force_window=False, force_tab=False,
-               force_target=None, no_raise=False):
+def get_window(via_ipc: bool,
+               force_window: bool = False,
+               force_tab: bool = False,
+               force_target: typing.Optional[bool] = None,
+               no_raise: bool = False):
     """Helper function for app.py to get a window id.
 
     Args:
@@ -70,9 +73,9 @@ def get_window(via_ipc, force_window=False, force_tab=False,
     # Apply any target overrides, ordered by precedence
     if force_target is not None:
         open_target = force_target
-    if force_window:
+    if force_window and open_target != 'private-window':
         open_target = 'window'
-    if force_tab and open_target == 'window':
+    if force_tab and open_target in {'window', 'private-window'}:
         # Command sent via IPC
         open_target = 'tab-silent'
 
@@ -80,13 +83,15 @@ def get_window(via_ipc, force_window=False, force_tab=False,
     should_raise = False
 
     # Try to find the existing tab target if opening in a tab
-    if open_target != 'window':
+    if open_target not in {'window', 'private-window'}:
         window = get_target_window()
-        should_raise = open_target not in ['tab-silent', 'tab-bg-silent']
+        should_raise = open_target not in {'tab-silent', 'tab-bg-silent'}
+
+    is_private = open_target == 'private-window'
 
     # Otherwise, or if no window was found, create a new one
     if window is None:
-        window = MainWindow(private=None)
+        window = MainWindow(private=is_private)
         window.show()
         should_raise = True
 
@@ -132,7 +137,6 @@ def get_target_window():
 
 
 class MainWindow(QWidget):
-
     """The main window of qutebrowser.
 
     Adds all needed components to a vbox, initializes sub-widgets and connects
@@ -162,7 +166,10 @@ class MainWindow(QWidget):
         }
     """
 
-    def __init__(self, *, private, geometry=None, parent=None):
+    def __init__(self, *,
+                 private: bool,
+                 geometry: typing.Optional[QByteArray] = None,
+                 parent: typing.Optional[QObject] = None) -> None:
         """Create a new main window.
 
         Args:
@@ -177,6 +184,7 @@ def __init__(self, *, private, geometry=None, parent=None):
         from qutebrowser.mainwindow.statusbar import bar
 
         self.setAttribute(Qt.WA_DeleteOnClose)
+        self._commandrunner = None
         self._overlays = []  # type: typing.MutableSequence[_OverlayInfoType]
         self.win_id = next(win_id_gen)
         self.registry = objreg.ObjectRegistry()
diff --git a/qutebrowser/qutebrowser.py b/qutebrowser/qutebrowser.py
--- a/qutebrowser/qutebrowser.py
+++ b/qutebrowser/qutebrowser.py
@@ -77,7 +77,7 @@ def get_argparser():
                         action='store_true')
     parser.add_argument('--target', choices=['auto', 'tab', 'tab-bg',
                                              'tab-silent', 'tab-bg-silent',
-                                             'window'],
+                                             'window', 'private-window'],
                         help="How URLs should be opened if there is already a "
                              "qutebrowser instance running.")
     parser.add_argument('--backend', choices=['webkit', 'webengine'],


Test patch:
diff --git a/tests/end2end/features/invoke.feature b/tests/end2end/features/invoke.feature
--- a/tests/end2end/features/invoke.feature
+++ b/tests/end2end/features/invoke.feature
@@ -36,6 +36,21 @@ Feature: Invoking a new process
               - history:
                 - url: http://localhost:*/data/search.html
 
+    Scenario: Using new_instance_open_target = private-window
+        When I set new_instance_open_target to private-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
+            - private: True
+              tabs:
+              - history:
+                - url: http://localhost:*/data/search.html
+
     Scenario: Using new_instance_open_target_window = last-opened
         When I set new_instance_open_target to tab
         And I set new_instance_open_target_window to last-opened