Score:3

How to auto close old/idle Files windows in Ubuntu 22.04?

fj flag

I constantly find myself having numerous Files windows open at the same time. Then I have to look at each window to find what I need. The Files window is the window that allows you to navigate through your directories and files.

Is there an OS or GNOME setting, application or script to auto close old/idle Files windows?

I could just do a right click and close on all the Files windows, but my goal is to auto close old Files windows while the newer Files windows remain.

FedKad avatar
cn flag
You can write a script that will run continuously on the background, monitoring the "nautilus" processes and killing all of them except the newest one.
Chris A. avatar
fj flag
Great tip! I found a solution now. Thank you FedKad!
Score:2
fj flag

After FedKad's comment, I was able to create a python script that closes Nautilus windows after N seconds. Now my desktop cleans itself, i.e. old and numerous Nautilus windows are auto closed.

This works for me on Ubuntu 22.04.

Below is my python script, along with some comments on how to configure cron to auto run this python script every N seconds.

#!/usr/bin/env python3
import subprocess
import os
import re
import datetime
import json

# Sources:
# https://askubuntu.com/questions/860212/close-nautilus-window-automatically-after-drive-is-removed
# https://stackoverflow.com/questions/32483997/get-current-date-time-and-compare-with-other-date
# https://stackoverflow.com/questions/25325882/python-time-difference-in-if-statements
# https://stackoverflow.com/a/72069726/13003478
# https://superuser.com/questions/190801/linux-wmctrl-cannot-open-display-when-session-initiated-via-sshscreen

# Instructions
# ##############################################################################
# 1) Adjust variable_n_allowed_life_of_window_in_seconds to your liking and save the file
# 2) Use cron to configure this script to run every M seconds

# Expected result:
# After every M seconds, any window older than variable_n_allowed_life_of_window_in_seconds
# should be closed. The exception is if the window is actively being used, e.g. the window is
# above all other windows or is displayed vertically on the side.


# Instructions to configure with cron
# ##############################################################################
# 1) Run this script 1 time and determine what the DISPLAY and XAUTHORITY variables are. Comment or remove when no longer needed.
# print(os.environ.get("DISPLAY"))
# print(os.environ.get("XAUTHORITY"))
# 2) In cron, you can configure the python script as follows:

# */1 * * * * export DISPLAY=:1 && export XAUTHORITY\=/run/user/1000/gdm/Xauthority && python3 /home/apricot/Applications/auto_close_old_nautilus_windows.py >>/home/apricot/Applications/script.log 2>>/home/apricot/Applications/script.log
# @reboot    export DISPLAY=:1 && export XAUTHORITY\=/run/user/1000/gdm/Xauthority && python3 /home/apricot/Applications/auto_close_old_nautilus_windows.py >>/home/apricot/Applications/script.log 2>>/home/apricot/Applications/script.log

# The line above is configured to run my python script every 1 minute. You can change to 5 minutes with: */5 * * * *
#    If the DISPLAY and XAUTHORITY are not properly set, you will get this error.
# Cannot open display.
# Traceback (most recent call last):
#   File "/home/usera/Applications/auto_close_old_nautilus_windows.py", line 55, in <module>
#     wlist1 = [l.strip() for l in get(["wmctrl", "-lp"]).splitlines() if nautpid in l]
# AttributeError: 'NoneType' object has no attribute 'splitlines'

# Variables
# ##############################################################################
# Any window older than variable_n_allowed_life_of_window_in_seconds will be closed
variable_n_allowed_life_of_window_in_seconds = 300 # 5 minutes (5 min. * 60 sec. = 300 sec.)

location_to_store_json = '/home/apricot/Applications/20221028_auto_close_old_nautilus_windows/'
name_of_json_file = 'nautilus_windows_recently_opened.json'

# Main Logic
# ##############################################################################
os.chdir(location_to_store_json)


def get(cmd):
    try:
        return subprocess.check_output(cmd).decode('utf-8').strip()
    except subprocess.CalledProcessError:
        pass


def decide_to_keep_this_window(unique_identifier):
    # Get the output of this: xprop -id 0x03030e9a | grep "_NET_WM_STATE(ATOM)"
    arguments_a = ['xprop', '-id', unique_identifier]
    output_a = get(arguments_a)

    # If the window is currently being looked at, e.g. the state is _NET_WM_STATE_MAXIMIZED_VERT,
    # do not close the window. Keep the window opened.
    # _NET_WM_STATE_MODAL, ATOM
    # _NET_WM_STATE_STICKY, ATOM
    # _NET_WM_STATE_MAXIMIZED_VERT, ATOM        # Keep the window
    # _NET_WM_STATE_MAXIMIZED_HORZ, ATOM        # Keep the window
    # _NET_WM_STATE_SHADED, ATOM
    # _NET_WM_STATE_SKIP_TASKBAR, ATOM
    # _NET_WM_STATE_SKIP_PAGER, ATOM
    # _NET_WM_STATE_HIDDEN, ATOM
    # _NET_WM_STATE_FULLSCREEN, ATOM           # Keep the window
    # _NET_WM_STATE_ABOVE, ATOM                # Keep the window
    # _NET_WM_STATE_BELOW, ATOM
    # _NET_WM_STATE_DEMANDS_ATTENTION, ATOM

    # Per the source below, .match only works from the start, so you need to add '^.*' to find a match
    # Source: https://stackoverflow.com/a/17680674/13003478
    pattern = '^.*(_NET_WM_STATE_MAXIMIZED_VERT|_NET_WM_STATE_MAXIMIZED_HORZ|_NET_WM_STATE_FULLSCREEN|_NET_WM_STATE_ABOVE)'
    pattern_b = '^.*_NET_WM_STATE_HIDDEN'

    keep_window = False

    if output_a is not None:
        if re.match(pattern, output_a, re.IGNORECASE):
            keep_window = True

        if re.match(pattern_b, output_a, re.IGNORECASE):
            keep_window = False

    return keep_window


nautpid = get(['pgrep', 'nautilus'])
connected1 = [item_b for item_b in get('lsblk').splitlines() if 'media' in item_b]
wlist1 = []
if nautpid is not None:
    wlist1 = [item_b.strip() for item_b in get(['wmctrl', '-lp']).splitlines() if nautpid in item_b]


# Read in the JSON if it exists
current_windows = []

if os.path.exists(name_of_json_file) is True:
    with open(name_of_json_file) as json_file:
        current_windows = json.load(json_file)

previous_list_of_unique_identifiers = []

for item_a in current_windows:
    previous_list_of_unique_identifiers.append(item_a['unique_identifier'])

# For any new nautilus windows, store the unique identifier and the creation time
for item_a in wlist1:
    date_and_time_window_detected = str(datetime.datetime.now())
    date_and_time_window_detected = datetime.datetime.strptime(date_and_time_window_detected, '%Y-%m-%d %H:%M:%S.%f')

    new_string = re.sub(r'\s+', ' ', item_a)
    entries_a = new_string.split(' ')

    # Only add new windows to the list
    if (entries_a[0] in previous_list_of_unique_identifiers) is False:
        current_windows.append({
            'unique_identifier': entries_a[0], 
            'pid': entries_a[2], 
            'creation_time': str(date_and_time_window_detected), 
            'windows_was_terminated': False
        })

# If the window is being actively used, reset its creation date and avoid appearing too old.
for i, item_a in enumerate(current_windows):
    window_is_active = decide_to_keep_this_window(item_a['unique_identifier'])

    if window_is_active is True:
        date_and_time_window_detected = str(datetime.datetime.now())
        date_and_time_window_detected = datetime.datetime.strptime(date_and_time_window_detected, '%Y-%m-%d %H:%M:%S.%f')
        current_windows[i]['creation_time'] = str(date_and_time_window_detected)

# For windows that are too old, close the window
list_of_processes_to_close = []

for i, item_a in enumerate(current_windows):
    time_when_created = item_a['creation_time']
    time_when_created_date_object = datetime.datetime.strptime(time_when_created, '%Y-%m-%d %H:%M:%S.%f')

    current_time_date_object = datetime.datetime.now()
    total = current_time_date_object - time_when_created_date_object

    if total.seconds > variable_n_allowed_life_of_window_in_seconds:
        list_of_processes_to_close.append(item_a['unique_identifier'])
        arguments_a = ['wmctrl', '-ic', item_a['unique_identifier']]
        subprocess.Popen(arguments_a)
        current_windows[i]['windows_was_terminated'] = True

# Keep only the non-terminated windows
filtered = filter(lambda item_a: item_a['windows_was_terminated'] is False, current_windows)
current_windows = list(filtered)

# Dump the information of the windows to a JSON file
with open(name_of_json_file, 'w') as convert_file:
    convert_file.write(json.dumps(current_windows))
I sit in a Tesla and translated this thread with Ai:

mangohost

Post an answer

Most people don’t grasp that asking a lot of questions unlocks learning and improves interpersonal bonding. In Alison’s studies, for example, though people could accurately recall how many questions had been asked in their conversations, they didn’t intuit the link between questions and liking. Across four studies, in which participants were engaged in conversations themselves or read transcripts of others’ conversations, people tended not to realize that question asking would influence—or had influenced—the level of amity between the conversationalists.