12

Is it possible to assign default keyboard language for specific applications? I know that there is an option to always start an application with a default keyboard language but this is not what I want.

I'm looking for something similar to "fixed workspaces" which you can set using CompizConfig (You set Chrome to always open at X1Y1, terminal at X2Y1 etc). Something where I would set Chrome: Czech, terminal: English, Spotify: English, ...

grongor
  • 171
  • 1
  • 7
  • 2
    In "Text Entry" there is the option "Allow different sources for each window". Possibly a step in the right direction for you. – Gunnar Hjalmarsson Aug 02 '15 at 14:10
  • Yes, thank you, I know about that. This is the necessary step I would say but it unfortunately doesn't what I want to achieve. Thanks anyway. – grongor Aug 02 '15 at 20:02
  • There's a scripting way. You only need those 3 apps or there's others ? Depending on how flexible you want the script to be , it might take me more or less time to write it – Sergiy Kolodyazhnyy Aug 04 '15 at 23:54
  • @JacobVlijm Sounds like another good job for python ;) – Sergiy Kolodyazhnyy Aug 05 '15 at 01:54
  • Posted an answer. Let me know what you think – Sergiy Kolodyazhnyy Aug 05 '15 at 02:17
  • Would an edited version of this one: http://askubuntu.com/a/637334/72216 be what you are looking for? I could relatively easily make it work for any (and multiple) applications. – Jacob Vlijm Aug 09 '15 at 10:28
  • Also: could you try its functionality (currently with gnome-terminal, but could be made to work with all kinds of applications)? The script allows setting (and remembering) language while the application is in front. – Jacob Vlijm Aug 09 '15 at 10:40
  • http://manpages.ubuntu.com/manpages/natty/man1/xxkb.1.html . this may help you bro –  Aug 10 '15 at 09:03
  • @RonnieNissan seems nice, but outdated. Doesn't work on 14.04. – Jacob Vlijm Aug 10 '15 at 09:14
  • @GRoNGoR did you notice my question upstairs? I did some tests and I can make it work quite well. I think however Serg's answer is pretty nice and I will only post an alternative if setting language per application without editing the code (just set the language when an application is frontmost and it will be remembered on the next run) is adding something for you. If that is the case, let me know soon, some work has yet to be done. – Jacob Vlijm Aug 10 '15 at 16:57
  • OK, since I edited the code, I might as well post it anyway :) – Jacob Vlijm Aug 10 '15 at 21:51

3 Answers3

6

Introduction

The script bellow sets the language for each user-defined program, according to the position of that language in the language menu. For instance, if my order is: English (1), Chinese(2), and Russian(3), I can set Firefox to have language 2, terminal to have language 1, and LibreOffice to have language 3.

The script comes in two parts: part one is the actual script that does the job, the second script serves as a controlling element. The idea is to run the language setting script as a start-up application and whenever you need to manually change language - double click the shortcut to the controller script.

Pre-requisites

  1. Install wmctrl program with sudo apt-get install wmctrl.

Script

#!/bin/sh
# Author: Serg Kolo
# Date: August 4, 2015
# Description: This script forces assigned input languages
#              for specific windows
# Version:2

# Use this part to set programs and their respective languages
# PROG_CLASS or a running window can be found with the
# wmctrl -lx command
# If you want to add another program to the list, 
# follow the template PROG_CLASS_num=window.Class
# and bellow that $LANGnum=num


PROG_CLASS_1=gedit.Gedit
LANG1=2

PROG_CLASS_2=gnome-terminal-server.Gnome-terminal
LANG2=0

PROG_CLASS_3=Navigator.Firefox
LANG3=1


# While loop below gets the job done. 
# If you need to send languages for more programs -  copy  
# the first entry and replace  $PROG_CLASS_1 with $PROG_CLASS_num
# where num is respective number of a program
# Replace $LANGnum with the respective language number. After the "="
# post positional number of the language you want to use. 
# Remember the count starts from 0

while [ 1 ];do
  WM_CLASS=$(wmctrl -lx | awk -v search=$(printf %x $(xdotool getactivewindow)) '{ if($1~search) print $3 }' )
  CURRENT=$(gsettings get org.gnome.desktop.input-sources current| awk '{print $2}')
    case  $WM_CLASS in

      $PROG_CLASS_1)  
        if [ $CURRENT -ne  $LANG1 ];then
          gsettings set org.gnome.desktop.input-sources current $LANG1
        fi
      ;;

      $PROG_CLASS_2) 
        if [ $CURRENT -ne  $LANG2 ];then
          gsettings set org.gnome.desktop.input-sources current $LANG2
        fi  
      ;;

       $PROG_CLASS_3) 
         if [ $CURRENT -ne  $LANG3 ];then
           gsettings set org.gnome.desktop.input-sources current $LANG3
         fi  
        ;;
    esac

    sleep 0.250

done

Controller Script

#!/bin/sh
# set -x
# Author: Serg Kolo
# Date: August 8, 2015
# Description: Controller script for set-lang.sh script
# Allows pausing and resuming execution of set-lang.sh
STATUS=$(ps -o stat -p $(pgrep -o  set-lang.sh) | awk '{getline;print }')

case $STATUS in
    T) kill -CONT $(pgrep set-lang.sh) 
       notify-send 'RESUMED'
    ;;

    S) kill -STOP $(pgrep set-lang.sh)
       notify-send 'STOPED'
    ;;

    *) exit ;;
esac 

Launcher (.desktop) file for set-lang.sh script

[Desktop Entry]
Name=set-lang.sh
Comment=Script to set languages
Exec=/home/yourusername/bin/set-lang.sh
Type=Application
StartupNotify=true
Terminal=false

Launcher (.desktop) file for set-lang-controller.sh

[Desktop Entry]
Name=lang-control
Comment=Shortcut to controlling script
Exec=/home/yourusername/bin/set-lang-control.sh
Type=Application
StartupNotify=true
Terminal=false

Making the script work

  1. Create a folder in your home directory called bin. You can do it in the file manager or use the command mkdir $HOME/bin in the terminal
  2. In the bin folder create two files: set-lang.sh and set-lang-control.sh. Save script to set-lang.sh and controller script to set-lang-control.sh. Make both scripts executable with sudo chmod +x $HOME/bin/set-lang-control.sh $HOME/bin/set-lang.sh
  3. Create two .desktop files. One is set-lang.desktop. Must be placed in the hidden .config/autostart directory. Second one is set-lang-controller.desktop, may be placed in your bin folder. Next drag and pin to the launcher the set-lang-controller.desktop file. This will become the shortcut for temporarily stopping and resuming the script execution.

NOTE that the line Exec= must be altered to have your actual user name in the path to script (because that's your actual home directory). For example, mine would be Exec=/home/serg/bin/set-lang.sh

Explanation and customization:

The script itself runs in an infinite while loop and checks the current active window. If the current active window matches one of the options in the case structure, we switch to the appropriate language. To avoid constant setting, each part of the case structure has if statement that checks if the language has already been set to the desired value.

The double clicking on the launcher for set-lang-controller.sh will check the status of the set-lang.sh script; if the script is running - it will be paused, and if the script is paused it will be resumed. A notification will be shown with appropriate message.

In order to customize the script, you can open desired application(s), run wmctrl -lx and note the third column - the window class. Sample output:

$ wmctrl -lx | awk '$4="***" {print}'                                                                                                            
0x02c00007 0 gnome-terminal-server.Gnome-terminal *** Terminal
0x03a0000a 0 desktop_window.Nautilus *** Desktop
0x04a00002 0 N/A *** XdndCollectionWindowImp
0x04a00005 0 N/A *** unity-launcher
0x04a00008 0 N/A *** unity-panel
0x04a0000b 0 N/A *** unity-dash
0x04a0000c 0 N/A *** Hud
0x012000a6 0 Navigator.Firefox *** unity - Assign default keyboard language per-application - Ask Ubuntu - Mozilla Firefox

Select the appropriate window classes for each program. Next, go to the part of the script that allows customization and add two entries for PROG_CLASS and LANG. Next add the appropriate entry in the case structure.

For instance, if I want to add, LibreOffice's Writer, I open LibreOffice Writer window, go to terminal and run wmctrl -lx. It will tell me that the Writer window has class libreoffice.libreoffice-writer. Next I will go to the script, add PROG_CLASS_4=libreoffice.libreoffice-writer and LANG4=3 in the appropriate area. Notice matching number 4. Next, go to case structure, and add the following entry between last ;; and esac :

$PROG_CLASS_4) 
  if [ $CURRENT -ne  $LANG4 ];then
    gsettings set org.gnome.desktop.input-sources current $LANG4
  fi  
;;

Again, notice the $ sign and matching number 4.

Also, if the script is running as an autostart item and you want to stop it temporarily to customize it, use pkill set-lang.sh and resume with nohup set-lang.sh > /dev/null 2&>1 &

Small note: another way to find out the window class for a program (that stuff that goes before single round bracket in the case structure) is to use this xprop and awk oneliner : xprop | awk '/WM_CLASS/ {gsub(/"/," "); print $3"."$5}

Sergiy Kolodyazhnyy
  • 105,154
  • 20
  • 279
  • 497
  • Thank you, this is a pretty good start. The only (but major) problem is that I won't be able to switch the language in those applications. The script will simply override my settings all the time. I would like to be able to switch the language in those windows too. So maybe add some checking for application pid and force language only the first time or something like that? – grongor Aug 05 '15 at 09:16
  • I would also prefer that this script would be event-driven, not a loop-based. It's not a big deal but I would really appreciate if you could supply a small how-to to get this working with dbus or something (if it is possible). – grongor Aug 05 '15 at 09:17
  • @GRoNGoR so I've thought about it for a bit, and so far I don't see a way to pause the script except kill -STOP $(pgrep set-lang.sh) and then resume the script with kill -CONT $(pgrep set-lang.sh). That way you can pause it, switch languages normally, and then resume. – Sergiy Kolodyazhnyy Aug 08 '15 at 08:46
  • A quick thought that I had is that perhaps using a keyboard shortcut for the two commands might be the way to do it, but I haven't tested it yet, just a theory so far. Will test tomorrow once I get enough sleep and report back :) Let me know if this would be an acceptable idea – Sergiy Kolodyazhnyy Aug 08 '15 at 09:02
  • So what I came up with is to make a second script and create a launcher shortcut for it, so that you can double click the shortcut and make the script pause, alternate language however you want in those windows, and double click again to resume the script. Will post later tonight – Sergiy Kolodyazhnyy Aug 08 '15 at 20:31
  • Updated my answer, please review – Sergiy Kolodyazhnyy Aug 09 '15 at 02:10
  • 1
    +1 for the great and thorough work! Two tiny things I'd like to mention: executables in ~/bin do not need the path (nor the extension, since it is in $PATH after a re-log in, see: https://lintian.debian.org/tags/script-with-language-extension.html ), and you could combine stop/start in one .desktop file; either in a quicklist or a toggle function. looks pretty perfect to me. – Jacob Vlijm Aug 09 '15 at 11:05
  • Would a script that changes the default keyboard language and then load the app do what you want? You would need one for each app that needs a language other than your desktop default. If the script can't return the keyboard language to your boot default when it closes, you will need one to set the default keyboard language to the language you normally use or boot to. – Buck Aug 09 '15 at 12:34
  • Wow! I must have known you would come up with a solution to an impossible problem! – Fabby Aug 10 '15 at 11:14
  • @Fabby nothing is impossible with the power of shell scripting ! :D – Sergiy Kolodyazhnyy Aug 10 '15 at 11:16
  • Script edited, made easier to customize ( more user friendly ). Please review ! – Sergiy Kolodyazhnyy Aug 10 '15 at 22:43
  • Thank you for your great work, I've assigned you the bounty. I will look into it closer as soon as I get some free time. – grongor Aug 11 '15 at 10:09
1

You can install gxneur for that by running

sudo apt-get install gxneur

This software can automatically switch layouts, but it is not perfect with that.

But it has very nice tools to set up manual layout switches.

You can do exactly what you want. To set specific layouts for specific applications.

Pilot6
  • 90,100
  • 91
  • 213
  • 324
0

The script below is an edited version of this one, that was posted as a similar solution for a single application (gnome-terminal) a while ago.

The script was partially rewritten for this question, to be able to use it for automatically setting languages for multiple applications.

How to use in practice; automatically remember set language per application

If the script is started for the first time, the current language is assumed to be the default language, which is remembered in a hidden file: ~/.lang_set.

Then the usage is simple: if you run e.g. gedit in the front most window, and set a different language, that language is automatically connected to gedit from then on, until you change the language again with gedit in front.

The number of specific languages/applications is in principle unlimited; just run the application and set the language with the application window in front. The application will be automatically set and remembered without editing the code.

Event driven?

Although the script does run in a loop, the consumption of resources is minimal. Event driven would mean that the event is a change in the frontmost window. I see no other option than to "spy" for the front most window than to check in a loop.

The script

#!/usr/bin/env python3
import subprocess
import os
import time

key = [
    "org.gnome.desktop.input-sources",
    "gsettings get ", "gsettings set ",
    " sources", " current",
    ]

prev = os.environ["HOME"]+"/.lang_set"

def get(cmd):
    return subprocess.check_output(["/bin/bash", "-c", cmd]).decode("utf-8")

def get_front():
    # produce the frontmost application
    try:
        focus = str(hex(int(get("xdotool getwindowfocus").strip())))
        front = focus[:2]+"0"+focus[2:]
        cmd = "wmctrl -lp"
        w_list = subprocess.check_output(["/bin/bash", "-c",cmd]).decode("utf-8")
        w_match = [w for w in w_list.splitlines() if front in w][0].split()[2]
        application = get("ps -p "+w_match+" -o comm=").strip()
        # fix for 15.04, where the process name of gnome-terminal varies
        application = "gnome-terminal" if "gnome-terminal" in application else application
        return application
    except subprocess.CalledProcessError:
        return None

def get_lang():
    # get the currently running language (output = string)
    curr_n = int(get(key[1]+key[0]+key[4]).strip().split()[-1])
    return str(eval(get(key[1]+key[0]+key[3]))[curr_n])

def read_prev(application):
    # reads the possibly set languages for general and specific use (list)
    if not os.path.exists(prev):
        currlang = get_lang()
        open(prev, "wt").write("default "+currlang+"\n"+application+" "+currlang)
        return [currlang, currlang]
    else:
        return [l.strip() for l in open(prev).readlines()]

def write_changed(application, lang):
    changelist = read_prev(front_2)
    try:
        match = changelist.index([l for l in changelist if application in l][0])
        changelist[match] = application+" "+lang+"\n"
    except IndexError:
        changelist.append(application+" "+lang+"\n")
    open(prev, "wt").write(("\n").join(changelist))

def set_lang(lang):
    # set the currently used language (input = string)
    lang_i = eval(get(key[1]+key[0]+key[3])).index(eval(lang))  
    cmd = key[2]+key[0]+key[4]+" "+str(lang_i)
    subprocess.Popen(["/bin/bash", "-c", cmd])

front_1 = get_front(); lang_1 = get_lang()

while True:
    time.sleep(1)
    front_2 = get_front(); lang_2 = get_lang()
    if front_2 != None:
        if front_2 != front_1:
            try:
                setlist = read_prev(front_2)
                match = [l for l in setlist if front_2 in l][0]
            except IndexError:
                match = [l for l in setlist if "default" in l][0]
                set_lang(match[match.find(" ")+1:])            
        elif lang_2 != lang_1:
            write_changed(front_2, lang_2)
    front_1 = front_2; lang_1 = lang_2

How to set up

  1. The script uses both xdotool and wmctrl:

    sudo apt-get install wmctrl xdotool
    
  2. Copy the script above into an empty file, save it as set_language.py

  3. Test-run it by the command:

    python3 /path/to/set_language.py
    
  4. If all works as expected, add it to Startup Applications: Add to Startup Applications: Dash > Startup Applications > Add. Add the command:

    python3 /path/to/set_language.py
    
Jacob Vlijm
  • 83,767