Fix scaling of java-based applications for a high DPI screen
A major convenience upgrade would be to use a background script, automatically setting the resolution per application, while you can set different resolutions for different (multiple) applications at once.
That is exactly what the script below does.
An example of a default resolution of 1680x1050
:
Running gedit
, automatically changing to 640x480
:
Running gnome-terminal
, automatically changing to 1280x1024
:
When the application is closed, the resolution is automatically set back to 1680x1050
How to use
- Copy the script below into an empty file, save it as
set_resolution.py
In the head of the script, set your default resolution, in the line:
#--- set the default resolution below default = "1680x1050" #---
In the very same directory (folder), create a textfile, exactly named:
procsdata.txt
. In this textfile, set the desired application or process, followed by a space, followed by the desired resolution. One application or script per line, looking like:gedit 640x480 gnome-terminal 1280x1024 java 1280x1024
Run the script by the command:
python3 /path/to/set_resolution.py
Note
The script use pgrep -f <process>
, which catches all matches, including scripts. The possible downside is that it can cause name clashes when opening a file with the same name as the process.
If you run into issues like that, change:
matches.append([p, subprocess.check_output(["pgrep", "-f", p]).decode("utf-8")])
into:
matches.append([p, subprocess.check_output(["pgrep", p]).decode("utf-8")])
The script
#!/usr/bin/env python3
import subprocess
import os
import time
#--- set the default resolution below
default = "1680x1050"
#---
# read the datafile
curr_dir = os.path.dirname(os.path.abspath(__file__))
datafile = curr_dir+"/procsdata.txt"
procs_data = [l.split() for l in open(datafile).read().splitlines() if not l == "\n"]
procs = [pdata[0] for pdata in procs_data]
def check_matches():
# function to find possible running (listed) applications
matches = []
for p in procs:
try:
matches.append([p, subprocess.check_output(["pgrep", "-f", p]).decode("utf-8")])
except subprocess.CalledProcessError:
pass
match = matches[-1][0] if len(matches) != 0 else None
return match
matches1 = check_matches()
while True:
time.sleep(2)
matches2 = check_matches()
if matches2 == matches1:
pass
else:
if matches2 != None:
# a listed application started up since two seconds ago
resdata = [("x").join(item[1].split("x")) for item in \
procs_data if item[0] == matches2][0]
elif matches2 == None:
# none of the listed applications is running any more
resdata = default
subprocess.Popen(["xrandr", "-s", resdata])
matches1 = matches2
time.sleep(1)
Explanation
When the script starts, it reads the file in which you defined your applications and their corresponding desired screen resolutions.
It then keeps an eye on the running processes (running pgrep -f <process>
for each of the applications) and sets the resolution if the application starts up.
When pgrep -f <process>
does not produce output for any of the listed applications, it sets the resolution to "default".
Edit:
"Dynamic" version (as requested)
While the version above works with multiple listed applications, it only sets the resolution for one application at a time.
The version below can handle different applications with a different (required) resolution, running at the same time. The background script will keep track of what is the front most application, and will set the resolution accordingly. It also works fine with Alt+Tab.
Note that this behaviour might be annoying if you switch a lot between the desktop and listed applications; the frequent resolution switch might be too much.
differences in how to setup
The setup is pretty much the same, appart from the fact that this one uses wmctrl
and xdotool
:
sudo apt-get install wmctrl
sudo apt-get install xdotool
The script
#!/usr/bin/env python3
import subprocess
import os
import sys
import time
#--- set default resolution below
resolution = "1680x1050"
#---
curr_dir = os.path.dirname(os.path.abspath(__file__))
datafile = curr_dir+"/procsdata.txt"
applist = [l.split() for l in open(datafile).read().splitlines()]
apps = [item[0] for item in applist]
def get(cmd):
try:
return subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")
except subprocess.CalledProcessError:
pass
def get_pids():
# returns pids of listed applications; seems ok
runs = []
for item in apps:
pid = get("pgrep -f "+item)
if pid != None:
runs.append((item, pid.strip()))
return runs
def check_frontmost():
# returns data on the frontmost window; seems ok
frontmost = str(hex(int(get("xdotool getwindowfocus").strip())))
frontmost = frontmost[:2]+"0"+frontmost[2:]
try:
wlist = get("wmctrl -lpG").splitlines()
return [l for l in wlist if frontmost in l]
except subprocess.CalledProcessError:
pass
def front_pid():
# returns the frontmost pid, seems ok
return check_frontmost()[0].split()[2]
def matching():
# nakijken
running = get_pids(); frontmost = check_frontmost()
if all([frontmost != None, len(running) != 0]):
matches = [item[0] for item in running if item[1] == frontmost[0].split()[2]]
if len(matches) != 0:
return matches[0]
else:
pass
trigger1 = matching()
while True:
time.sleep(1)
trigger2 = matching()
if trigger2 != trigger1:
if trigger2 == None:
command = "xrandr -s "+resolution
else:
command = "xrandr -s "+[it[1] for it in applist if it[0] == trigger2][0]
subprocess.Popen(["/bin/bash", "-c", command])
print(trigger2, command)
trigger1 = trigger2
Notes
- Although I have it running for several hours without an error now, please test it thoroughly. If an error might occur, please leave a comment.
- The script -as it is- works on a single monitor setup.
Test adding to your java command line: -Dsun.java2d.uiScale=2.0
, or set it to a scale factor you want.
As a workaround
I created a bash script that changes the resolution to fullHD before it starts the application (in this examle Android Studio) and changes it back to 3200x1800 when the application quits:
sudo nano /usr/local/bin/studio
Enter this script:
#!/bin/bash
# set scaling to x1.0
gsettings set org.gnome.desktop.interface scaling-factor 1
gsettings set com.ubuntu.user-interface scale-factor "{'HDMI1': 8, 'eDP1': 8}"
xrandr -s 1920x1080
# call your program
/usr/share/android-studio/data/bin/studio.sh
# set scaling to x2.0
gsettings set org.gnome.desktop.interface scaling-factor 2
gsettings set com.ubuntu.user-interface scale-factor "{'HDMI1': 8, 'eDP1': 16}"
xrandr -s 3200x1800
and give it executeable rights:
sudo chmod +x /usr/local/bin/studio
Then you can start it with Alt+F1 studio
For other resize-factors that 2.0 see https://askubuntu.com/a/486611/34298
For easy switching zoom on and off in Firefox use the extension Zoom Menu Elements