1

What I want to achieve:

  • when I run application from start menu, app starts (if app not running).
  • If an app is already running, then don't create another instance, just show the previous running app window.

What I've tried:

  • created a .txt file in a directory, write 'running' & 'not running' into the file while opening & exiting the window. And checking the file contents at very start. For ex: When starting app: 1.) Check the file content, 2.) If file content is 'running', show warning & exit the app 3.) If file content is 'not running', write 'running' into the file & start app 4.) Write 'not running' into the file on exit.

Issues I'm facing:

  • This method doesn't feel like the right way to achieve this.

  • If file item says 'not running', app shows warning and exits. While I want it to show already running instance.

  • File items not updated to 'not running' when app exits because of some error in code (because that part is never reached due to error)

Can anybody please help me out regarding this?

Ecto Ruseff
  • 141
  • 1
  • 9

2 Answers2

3

You can achieve something similar with the win32gui module. To install it type into CMD pip install pywin32. Now this is the code:

from win32 import win32gui
import sys

def windowEnumerationHandler(hwnd, top_windows):
    top_windows.append((hwnd, win32gui.GetWindowText(hwnd)))

top_windows = []
win32gui.EnumWindows(windowEnumerationHandler, top_windows)
for i in top_windows:
    if "Program" in i[1]: #CHANGE PROGRAM TO THE NAME OF YOUR WINDOW
        win32gui.ShowWindow(i[0],5)
        win32gui.SetForegroundWindow(i[0])
        sys.exit()


from PyQt5.QtWidgets import QApplication, QWidget

def main():

    #YOUR PROGRAM GOES HERE

    app = QApplication(sys.argv)

    w = QWidget()
    w.setGeometry(500, 500, 500, 500)
    w.setWindowTitle('Simple')
    w.show()

    sys.exit(app.exec_())


if __name__ == '__main__':
    main()

Basically, at the beginning, the program gets the name of every open window. If the window name is equal to the name of the program, then it brings the program to the front and closes the program. If not, then it opens a new program.

PyWin32 link

Stack Overflow get a list of every open window

halfer
  • 19,824
  • 17
  • 99
  • 186
The Pilot Dude
  • 2,091
  • 2
  • 6
  • 24
  • Can you please some of the documentation links in the answer. – Ecto Ruseff Jan 14 '21 at 12:15
  • There aren't many links but I hope these links clear things up – The Pilot Dude Jan 14 '21 at 12:20
  • is win32gui comes built-in with python? Coz I can't find it Edit: This is the third party lib, can be installed via `pip install pywin32` – Ecto Ruseff Jan 14 '21 at 12:58
  • No you need to download win32gui with the command `pip install win32gui` as stated above – The Pilot Dude Jan 14 '21 at 13:03
  • are win32gui and pywin32 same? or....? because you used win32gui, and provided the link for pywin32. Also, Last update to win32gui was in 2017 (seems like not in active development), While pywin32 is still in development. Are they same or different? – Ecto Ruseff Jan 14 '21 at 13:06
  • I'm pretty sure that they are the same – The Pilot Dude Jan 14 '21 at 13:08
  • You used EnumWindows to get hwnd to get all the top level windows. I don't know much about hwnd. Is there anyway to get just the hwnd of my pyqt window, using its Process id or the window title? – Ecto Ruseff Jan 17 '21 at 10:15
0

This solution doesn't require you to install win32 or any other module, works with PyQt5 package itself.

import sys

from PyQt5 import QtWidgets
from PyQt5.QtCore import QSystemSemaphore, QSharedMemory

from ui import window_ui  # .py file compiled from .ui file


class LoginWindow(QtWidgets.QMainWindow):
    def __init__(self):
        super().__init__()

        self.ui = window_ui.Ui_MainWindow()  # call to init ui
        self.ui.setupUi(self)


def launch():
    app = QtWidgets.QApplication(sys.argv)  # create app instance at top, to able to show QMessageBox is required
    window_id = 'pingidapplication'
    shared_mem_id = 'pingidsharedmem'
    semaphore = QSystemSemaphore(window_id, 1)
    semaphore.acquire()  # Raise the semaphore, barring other instances to work with shared memory

    if sys.platform != 'win32':
        # in linux / unix shared memory is not freed when the application terminates abnormally,
        # so you need to get rid of the garbage
        nix_fix_shared_mem = QSharedMemory(shared_mem_id)
        if nix_fix_shared_mem.attach():
            nix_fix_shared_mem.detach()

    shared_memory = QSharedMemory(shared_mem_id)

    if shared_memory.attach():  # attach a copy of the shared memory, if successful, the application is already running
        is_running = True
    else:
        shared_memory.create(1)  # allocate a shared memory block of 1 byte
        is_running = False

    semaphore.release()

    if is_running:  # if the application is already running, show the warning message
        QtWidgets.QMessageBox.warning(None, 'Application already running',
                                      'One instance of the application is already running.')
        return

    # normal process of creating & launching MainWindow
    window = LoginWindow()
    window.show()
    sys.exit(app.exec_())


if __name__ == '__main__':
    launch()

This solution I converted from a C++ code posted on this website originally.

Tuhin Mitra
  • 555
  • 7
  • 19