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))