cheyan пре 1 година
комит
2526afd613

+ 7 - 0
README.md

@@ -0,0 +1,7 @@
+# Linux Mint 系统备份目录
+
+![image-20240103142447635](./assets/image-20240103142447635.png)
+
+此目录下存放的是被个性化修改过的系统文件
+
+通过`dotsyncrc`文件里的`sync`部分规定,每个被修改过的文件,都会在里面记录,并在同步的时候自动备份到此目录中。

BIN
assets/image-20240103142447635.png


+ 46 - 0
etc/default/grub

@@ -0,0 +1,46 @@
+# 如果您更改了此文件,请在之后运行 'update-grub' 命令以更新 /boot/grub/grub.cfg 文件。
+# 有关此文件中选项的完整文档,请参阅:
+# info -f grub -n 'Simple configuration'
+# sudo update-grub
+
+GRUB_DEFAULT="Ubuntu"  # 默认选择的操作系统是Ubuntu
+GRUB_TIMEOUT_STYLE="menu"  # 引导菜单的显示方式为隐藏
+GRUB_TIMEOUT="0"  # 引导菜单的显示时间为0秒,即不显示引导菜单
+GRUB_DISTRIBUTOR="`lsb_release -i -s 2> /dev/null || echo Debian`"  # 发行版标识,默认为Debian
+
+# quiet:将内核启动过程中的冗长输出信息减少到最小。这样可以减少启动时的屏幕显示信息,使启动更加安静和简洁。
+# splash:在启动过程中显示一个图形化的启动画面(也称为 plymouth)。这样可以提供更友好的启动体验,让用户感觉系统正在加载。
+# resume=UUID=fb26452f-70a7-4587-8364-e9d7eae26251:指定系统在恢复时使用的挂起设备,允许系统在重新启动后从先前保存的状态继续运行。
+
+#GRUB_CMDLINE_LINUX_DEFAULT="quiet splash resume=UUID=fb26452f-70a7-4587-8364-e9d7eae26251"  # Linux内核参数
+GRUB_CMDLINE_LINUX_DEFAULT="resume=UUID=fb26452f-70a7-4587-8364-e9d7eae26251"  # Linux内核参数
+GRUB_CMDLINE_LINUX="systemd.unified_cgroup_hierarchy=0"  # Linux内核参数
+GRUB_GFXPAYLOAD_LINUX="keep"  # 图形界面分辨率设置为保持原样
+
+# 取消注释以启用BadRAM过滤,根据需要进行修改
+# 这适用于Linux和从GRUB获取内存映射信息的任何内核(GNU Mach,FreeBSD的内核...)
+#GRUB_BADRAM="0x01234567,0xfefefefe,0x89abcdef,0xefefefef"
+
+# 取消注释以禁用图形终端(仅适用于grub-pc)
+#GRUB_TERMINAL="console"
+
+# 图形终端使用的分辨率
+# 请注意,您只能使用显卡支持的VBE模式
+# 您可以通过在真实的GRUB中运行 `vbeinfo' 命令来查看它们
+#GRUB_GFXMODE="640x480"
+
+# 取消注释以禁止GRUB将 "root=UUID=xxx" 参数传递给Linux
+#GRUB_DISABLE_LINUX_UUID="true"
+
+# 取消注释以禁用恢复模式菜单项的生成
+#GRUB_DISABLE_RECOVERY="true"
+
+# 取消注释以在GRUB启动时获得一个蜂鸣声
+#GRUB_INIT_TUNE="480 440 1"
+GRUB_GFXPAYLOAD_LINUX="keep"
+
+#GRUB_DISABLE_OS_PROBER="false"
+#GRUB_HIDDEN_TIMEOUT="0"
+GRUB_SAVEDEFAULT="false"  # 不保存上次选择的操作系统
+GRUB_THEME="/usr/share/grub/themes/Xenlism-Ubuntu/theme.txt"  # 引导菜单主题文件路径
+

+ 17 - 0
etc/fstab

@@ -0,0 +1,17 @@
+# /etc/fstab: static file system information.
+#
+# Use 'blkid' to print the universally unique identifier for a
+# device; this may be used with UUID= as a more robust way to name devices
+# that works even if disks are added and removed. See fstab(5).
+#
+# <file system> <mount point>   <type>  <options>       <dump>  <pass>
+# / was on /dev/nvme1n1p3 during installation
+UUID=c4b2be6f-d2e5-41b7-8a57-c0df24eb4a32 /               btrfs   defaults,subvol=@ 0       1
+# /boot/efi was on /dev/nvme0n1p1 during installation
+UUID=749A-61C5  /boot/efi       vfat    umask=0077      0       1
+# /home was on /dev/nvme1n1p3 during installation
+UUID=c4b2be6f-d2e5-41b7-8a57-c0df24eb4a32 /home           btrfs   defaults,subvol=@home 0       2
+# swap was on /dev/nvme1n1p2 during installation
+#UUID=9c6a05cc-679f-476e-b9bc-90dc6b4809c7 none            swap    sw              0       0
+
+UUID=fb26452f-70a7-4587-8364-e9d7eae26251 none            swap    sw              0       0

+ 448 - 0
etc/grub.d/00_header

@@ -0,0 +1,448 @@
+#! /bin/sh
+set -e
+
+# grub-mkconfig helper script.
+# Copyright (C) 2006,2007,2008,2009,2010  Free Software Foundation, Inc.
+#
+# GRUB is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# GRUB is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with GRUB.  If not, see <http://www.gnu.org/licenses/>.
+
+prefix="/usr"
+exec_prefix="/usr"
+datarootdir="/usr/share"
+grub_lang=`echo $LANG | cut -d . -f 1`
+grubdir="`echo "/boot/grub" | sed 's,//*,/,g'`"
+quick_boot="1"
+
+export TEXTDOMAIN=grub
+export TEXTDOMAINDIR="${datarootdir}/locale"
+
+. "$pkgdatadir/grub-mkconfig_lib"
+
+# Do this as early as possible, since other commands might depend on it.
+# (e.g. the `loadfont' command might need lvm or raid modules)
+for i in ${GRUB_PRELOAD_MODULES} ; do
+  echo "insmod $i"
+done
+
+if [ "x${GRUB_DEFAULT}" = "x" ] ; then GRUB_DEFAULT=0 ; fi
+if [ "x${GRUB_DEFAULT}" = "xsaved" ] ; then GRUB_DEFAULT='${saved_entry}' ; fi
+if [ "x${GRUB_TIMEOUT}" = "x" ] ; then GRUB_TIMEOUT=5 ; fi
+if [ "x${GRUB_GFXMODE}" = "x" ] ; then GRUB_GFXMODE=auto ; fi
+
+if [ "x${GRUB_DEFAULT_BUTTON}" = "x" ] ; then GRUB_DEFAULT_BUTTON="$GRUB_DEFAULT" ; fi
+if [ "x${GRUB_DEFAULT_BUTTON}" = "xsaved" ] ; then GRUB_DEFAULT_BUTTON='${saved_entry}' ; fi
+if [ "x${GRUB_TIMEOUT_BUTTON}" = "x" ] ; then GRUB_TIMEOUT_BUTTON="$GRUB_TIMEOUT" ; fi
+
+cat << EOF
+if [ -s \$prefix/grubenv ]; then
+  set have_grubenv=true
+  load_env
+fi
+EOF
+cat <<EOF
+if [ "\${initrdfail}" = 2 ]; then
+   set initrdfail=
+elif [ "\${initrdfail}" = 1 ]; then
+   set next_entry="\${prev_entry}"
+   set prev_entry=
+   save_env prev_entry
+   if [ "\${next_entry}" ]; then
+      set initrdfail=2
+   fi
+fi
+EOF
+if [ "x$GRUB_BUTTON_CMOS_ADDRESS" != "x" ]; then
+    cat <<EOF
+if cmostest $GRUB_BUTTON_CMOS_ADDRESS ; then
+   set default="${GRUB_DEFAULT_BUTTON}"
+elif [ "\${next_entry}" ] ; then
+   set default="\${next_entry}"
+   set next_entry=
+   save_env next_entry
+   set boot_once=true
+else
+   set default="${GRUB_DEFAULT}"
+fi
+EOF
+else
+    cat <<EOF
+if [ "\${next_entry}" ] ; then
+   set default="\${next_entry}"
+   set next_entry=
+   save_env next_entry
+   set boot_once=true
+else
+   set default="${GRUB_DEFAULT}"
+fi
+EOF
+fi
+cat <<EOF
+
+if [ x"\${feature_menuentry_id}" = xy ]; then
+  menuentry_id_option="--id"
+else
+  menuentry_id_option=""
+fi
+
+export menuentry_id_option
+
+if [ "\${prev_saved_entry}" ]; then
+  set saved_entry="\${prev_saved_entry}"
+  save_env saved_entry
+  set prev_saved_entry=
+  save_env prev_saved_entry
+  set boot_once=true
+fi
+
+function savedefault {
+  if [ -z "\${boot_once}" ]; then
+    saved_entry="\${chosen}"
+    save_env saved_entry
+  fi
+}
+EOF
+
+cat <<"EOF"
+function initrdfail {
+    if [ -n "${have_grubenv}" ]; then if [ -n "${partuuid}" ]; then
+      if [ -z "${initrdfail}" ]; then
+        set initrdfail=1
+        if [ -n "${boot_once}" ]; then
+          set prev_entry="${default}"
+          save_env prev_entry
+        fi
+      fi
+      save_env initrdfail
+    fi; fi
+}
+EOF
+
+if [ "$quick_boot" = 1 ]; then
+    cat <<EOF
+function recordfail {
+  set recordfail=1
+EOF
+
+  check_writable () {
+    abstractions="$(grub-probe --target=abstraction "${grubdir}")"
+    for abstraction in $abstractions; do
+      case "$abstraction" in
+        diskfilter | lvm)
+          cat <<EOF
+  # GRUB lacks write support for $abstraction, so recordfail support is disabled.
+EOF
+          return 1
+          ;;
+      esac
+    done
+
+    FS="$(grub-probe --target=fs "${grubdir}")"
+    case "$FS" in
+      btrfs | cpiofs | newc | odc | romfs | squash4 | tarfs | zfs)
+	cat <<EOF
+  # GRUB lacks write support for $FS, so recordfail support is disabled.
+EOF
+	return 1
+	;;
+    esac
+
+    cat <<EOF
+  if [ -n "\${have_grubenv}" ]; then if [ -z "\${boot_once}" ]; then save_env recordfail; fi; fi
+EOF
+  }
+
+  if ! check_writable; then
+    recordfail_broken=1
+  fi
+
+  cat <<EOF
+}
+EOF
+fi
+
+cat <<EOF
+function load_video {
+EOF
+if [ -n "${GRUB_VIDEO_BACKEND}" ]; then
+    cat <<EOF
+  insmod ${GRUB_VIDEO_BACKEND}
+EOF
+else
+# If all_video.mod isn't available load all modules available
+# with versions prior to introduction of all_video.mod
+cat <<EOF
+  if [ x\$feature_all_video_module = xy ]; then
+    insmod all_video
+  else
+    insmod efi_gop
+    insmod efi_uga
+    insmod ieee1275_fb
+    insmod vbe
+    insmod vga
+    insmod video_bochs
+    insmod video_cirrus
+  fi
+EOF
+fi
+cat <<EOF
+}
+
+EOF
+
+serial=0;
+gfxterm=0;
+for x in ${GRUB_TERMINAL_INPUT} ${GRUB_TERMINAL_OUTPUT}; do
+    if [ xserial = "x$x" ]; then
+	serial=1;
+    fi
+    if [ xgfxterm = "x$x" ]; then
+	gfxterm=1;
+    fi
+done
+
+if [ "x$serial" = x1 ]; then
+    if [ "x${GRUB_SERIAL_COMMAND}" = "x" ] ; then
+	grub_warn "$(gettext "Requested serial terminal but GRUB_SERIAL_COMMAND is unspecified. Default parameters will be used.")"
+	GRUB_SERIAL_COMMAND=serial
+    fi
+    echo "${GRUB_SERIAL_COMMAND}"
+fi
+
+if [ "x$gfxterm" = x1 ]; then
+    if [ -n "$GRUB_FONT" ] ; then
+       # Make the font accessible
+       prepare_grub_to_access_device `${grub_probe} --target=device "${GRUB_FONT}"`
+    cat << EOF
+if loadfont `make_system_path_relative_to_its_root "${GRUB_FONT}"` ; then
+EOF
+    else
+	for dir in "${pkgdatadir}" "`echo '/boot/grub' | sed "s,//*,/,g"`" /usr/share/grub ; do
+	    for basename in unicode unifont ascii; do
+		path="${dir}/${basename}.pf2"
+		if is_path_readable_by_grub "${path}" > /dev/null ; then
+		    font_path="${path}"
+		else
+		    continue
+		fi
+		break 2
+	    done
+	done
+	if [ -n "${font_path}" ] ; then
+    cat << EOF
+if [ x\$feature_default_font_path = xy ] ; then
+   font=unicode
+else
+EOF
+                # Make the font accessible
+		prepare_grub_to_access_device `${grub_probe} --target=device "${font_path}"`
+    cat << EOF
+    font="`make_system_path_relative_to_its_root "${font_path}"`"
+fi
+
+if loadfont \$font ; then
+EOF
+	    else
+    cat << EOF
+if loadfont unicode ; then
+EOF
+	    fi
+	fi
+
+    cat << EOF
+  set gfxmode=${GRUB_GFXMODE}
+  load_video
+  insmod gfxterm
+EOF
+
+# Gettext variables and module
+if [ "x${LANG}" != "xC" ] &&  [ "x${LANG}" != "x" ]; then
+  cat << EOF
+  set locale_dir=\$prefix/locale
+  set lang=${grub_lang}
+  insmod gettext
+EOF
+fi
+
+cat <<EOF
+fi
+EOF
+fi
+
+case x${GRUB_TERMINAL_INPUT} in
+  x)
+    # Just use the native terminal
+  ;;
+  x*)
+    cat << EOF
+terminal_input ${GRUB_TERMINAL_INPUT}
+EOF
+  ;;
+esac
+
+case x${GRUB_TERMINAL_OUTPUT} in
+  x)
+    # Just use the native terminal
+  ;;
+  x*)
+    cat << EOF
+terminal_output ${GRUB_TERMINAL_OUTPUT}
+EOF
+  ;;
+esac
+
+if [ "x$gfxterm" = x1 ]; then
+    if [ "x$GRUB_THEME" != x ] && [ -f "$GRUB_THEME" ] \
+	&& is_path_readable_by_grub "$GRUB_THEME"; then
+	gettext_printf "Found theme: %s\n" "$GRUB_THEME" >&2
+
+	prepare_grub_to_access_device `${grub_probe} --target=device "$GRUB_THEME"`
+	cat << EOF
+insmod gfxmenu
+EOF
+	themedir="`dirname "$GRUB_THEME"`"
+	for x in "$themedir"/*.pf2 "$themedir"/f/*.pf2; do
+	    if [ -f "$x" ]; then
+		cat << EOF
+loadfont (\$root)`make_system_path_relative_to_its_root $x`
+EOF
+	    fi
+	done
+	if [ x"`echo "$themedir"/*.jpg`" != x"$themedir/*.jpg" ] || [ x"`echo "$themedir"/*.jpeg`" != x"$themedir/*.jpeg" ]; then
+	    cat << EOF
+insmod jpeg
+EOF
+	fi
+	if [ x"`echo "$themedir"/*.png`" != x"$themedir/*.png" ]; then
+	    cat << EOF
+insmod png
+EOF
+	fi
+	if [ x"`echo "$themedir"/*.tga`" != x"$themedir/*.tga" ]; then
+	    cat << EOF
+insmod tga
+EOF
+	fi
+	    
+	cat << EOF
+set theme=(\$root)`make_system_path_relative_to_its_root $GRUB_THEME`
+export theme
+EOF
+    elif [ "x$GRUB_BACKGROUND" != x ] && [ -f "$GRUB_BACKGROUND" ] \
+	    && is_path_readable_by_grub "$GRUB_BACKGROUND"; then
+	gettext_printf "Found background: %s\n" "$GRUB_BACKGROUND" >&2
+	case "$GRUB_BACKGROUND" in 
+	    *.png)         reader=png ;;
+	    *.tga)         reader=tga ;;
+	    *.jpg|*.jpeg)  reader=jpeg ;;
+	    *)             gettext "Unsupported image format" >&2; echo >&2; exit 1 ;;
+	esac
+	prepare_grub_to_access_device `${grub_probe} --target=device "$GRUB_BACKGROUND"`
+	cat << EOF
+insmod $reader
+background_image -m stretch `make_system_path_relative_to_its_root "$GRUB_BACKGROUND"`
+EOF
+    fi
+fi
+
+make_timeout ()
+{
+    cat << EOF
+if [ "\${recordfail}" = 1 ] ; then
+  set timeout=${GRUB_RECORDFAIL_TIMEOUT:-3}
+else
+EOF
+    if [ "x${3}" != "x" ] ; then
+	timeout="${2}"
+	style="${3}"
+    elif [ "x${1}" != "x" ] && \
+	 ([ "$quick_boot" = 1 ] || [ "x${1}" != "x0" ]) ; then
+	# Handle the deprecated GRUB_HIDDEN_TIMEOUT scheme.
+	timeout="${1}"
+	if [ "x${2}" != "x0" ] ; then
+	    grub_warn "$(gettext "Setting GRUB_TIMEOUT to a non-zero value when GRUB_HIDDEN_TIMEOUT is set is no longer supported.")"
+	fi
+	if [ "x${GRUB_HIDDEN_TIMEOUT_QUIET}" = "xtrue" ] ; then
+	    style="hidden"
+	    verbose=
+	else
+	    style="countdown"
+	    verbose=" --verbose"
+	fi
+    else
+	# No hidden timeout, so treat as GRUB_TIMEOUT_STYLE=menu
+	timeout="${2}"
+	style="menu"
+    fi
+    cat << EOF
+  if [ x\$feature_timeout_style = xy ] ; then
+    set timeout_style=${style}
+    set timeout=${timeout}
+EOF
+    if [ "x${style}" = "xmenu" ] ; then
+	cat << EOF
+  # Fallback normal timeout code in case the timeout_style feature is
+  # unavailable.
+  else
+    set timeout=${timeout}
+EOF
+    else
+	cat << EOF
+  # Fallback hidden-timeout code in case the timeout_style feature is
+  # unavailable.
+  elif sleep${verbose} --interruptible ${timeout} ; then
+    set timeout=0
+EOF
+    fi
+    cat << EOF
+  fi
+fi
+EOF
+if [ "$recordfail_broken" = 1 ]; then
+  cat << EOF
+if [ \$grub_platform = efi ]; then
+  set timeout=${GRUB_RECORDFAIL_TIMEOUT:-3}
+  if [ x\$feature_timeout_style = xy ] ; then
+    set timeout_style=menu
+  fi
+fi
+EOF
+fi
+}
+
+if [ "x$GRUB_BUTTON_CMOS_ADDRESS" != "x" ]; then
+    cat <<EOF
+if cmostest $GRUB_BUTTON_CMOS_ADDRESS ; then
+EOF
+make_timeout "${GRUB_HIDDEN_TIMEOUT_BUTTON}" "${GRUB_TIMEOUT_BUTTON}" "${GRUB_TIMEOUT_STYLE_BUTTON}"
+echo else
+make_timeout "${GRUB_HIDDEN_TIMEOUT}" "${GRUB_TIMEOUT}" "${GRUB_TIMEOUT_STYLE}"
+echo fi
+else
+make_timeout "${GRUB_HIDDEN_TIMEOUT}" "${GRUB_TIMEOUT}" "${GRUB_TIMEOUT_STYLE}"
+fi
+
+if [ "x$GRUB_BUTTON_CMOS_ADDRESS" != "x" ] && [ "x$GRUB_BUTTON_CMOS_CLEAN" = "xyes" ]; then
+    cat <<EOF
+cmosclean $GRUB_BUTTON_CMOS_ADDRESS
+EOF
+fi
+
+# Play an initial tune
+if [ "x${GRUB_INIT_TUNE}" != "x" ] ; then
+  echo "play ${GRUB_INIT_TUNE}"
+fi
+
+if [ "x${GRUB_BADRAM}" != "x" ] ; then
+  echo "badram ${GRUB_BADRAM}"
+fi

+ 33 - 0
etc/grub.d/40_custom

@@ -0,0 +1,33 @@
+
+#!/bin/sh
+exec tail -n +3 $0
+# This file provides an easy way to add custom menu entries.  Simply type the
+# menu entries you want to add after this comment.  Be careful not to change
+# the 'exec tail' line above.
+
+menuentry "リアルタイム環境" {
+ set isofile="/@home/cheyan/Documents/ISO/linuxmint-cinnamon-64bit-edge.iso"
+ loopback loop (hd0,gpt3)/$isofile
+ linux (loop)/casper/vmlinuz boot=casper iso-scan/filename=$isofile quiet noeject nopromt spalsh --
+ initrd (loop)/casper/initrd.lz
+}
+
+#menuentry "Windows PE" {
+#  set isofile="/@home/cheyan/Documents/ISO/windows-pe.iso"
+#  loopback loop (hd0,gpt3)/$isofile
+#  iso9660_module (loop)
+#  set root=(loop)
+#  chainloader (loop)/EFI/boot/bootx64.efi
+#}
+#
+#menuentry "Windows PE2" {
+#  set isofile="/@home/cheyan/Documents/ISO/windows-pe.iso"
+#  linux16 (hd0,gpt3)/@home/cheyan/Documents/ISO/memdisk iso
+#  initrd16 (hd0,gpt3)$isofile
+#}
+#
+#menuentry "Windows PE3" {
+#  set isofile="/@home/cheyan/Documents/ISO/windows-pe.iso"
+#  linux16 /@home/cheyan/Documents/ISO/memdisk iso
+#  initrd16 (hd0,gpt3)$isofile
+#}

+ 622 - 0
etc/grub.d/41_snapshots-btrfs

@@ -0,0 +1,622 @@
+#! /usr/bin/env bash
+#
+# Written by: Antynea
+# BTC donation address: 1Lbvz244WA8xbpHek9W2Y12cakM6rDe5Rt
+# Github: https://github.com/Antynea/grub-btrfs
+#
+# Purpose:
+#   Improves Grub by adding "btrfs snapshots" to the Grub menu.
+#   You can boot your system on a "snapshot" from the Grub menu.
+#   Supports manual snapshots, snapper, timeshift ...
+#   Warning : booting on read-only snapshots can be tricky.
+#   (Read about it, https://github.com/Antynea/grub-btrfs#warning-booting-on-read-only-snapshots-can-be-tricky)
+#
+# What this script does:
+# - Automatically List snapshots existing on root partition (btrfs).
+# - Automatically Detect if "/boot" is in separate partition.
+# - Automatically Detect kernel, initramfs and intel/amd microcode in "/boot" directory on snapshots.
+# - Automatically Create corresponding "menuentry" in grub.cfg.
+# - Automatically detect the type/tags and descriptions/comments of snapper/timeshift snapshots.
+# - Automatically generate grub.cfg if you use the provided systemd service.
+#
+# Installation:
+# - Refer to https://github.com/Antynea/grub-btrfs#%EF%B8%8F-installation
+#
+# Customization:
+#  You have the possibility to modify many parameters in /etc/default/grub-btrfs/config.
+#  Read more here https://github.com/Antynea/grub-btrfs#installation- an in the manpage
+#  'man grub-btrfs'
+#
+# Automatically update Grub
+#  If you would like grub-btrfs menu to automatically update when a snapshot is created or deleted:
+#  - Refer to https://github.com/Antynea/grub-btrfs#-automatically-update-grub-upon-snapshot.
+#
+# Special thanks for assistance and contributions:
+# - My friends
+# - All contributors on Github
+#
+
+set -e
+
+sysconfdir="/etc"
+grub_btrfs_config="${sysconfdir}/default/grub-btrfs/config"
+
+[[ -f "$grub_btrfs_config" ]] && . "$grub_btrfs_config"
+[[ -f "${sysconfdir}/default/grub" ]] && . "${sysconfdir}/default/grub"
+
+## Error Handling
+print_error()
+{
+    local err_msg="$*"
+    local bug_report="If you think an error has occurred, please file a bug report at \"https://github.com/Antynea/grub-btrfs\""
+    printf "%s\n" "${err_msg}" "${bug_report}" >&2 ;
+    exit 0
+}
+
+# parse arguments
+while getopts :V-: opt; do
+    case "$opt" in
+        -)
+            case "${OPTARG}" in
+                version)
+                    printf "Version %s\n" "${GRUB_BTRFS_VERSION}" >&2 ;
+                    exit 0
+                    ;;
+            esac;;
+        V)
+            printf "Version %s\n" "${GRUB_BTRFS_VERSION}" >&2 ;
+            exit 0
+            ;;
+        *)
+            printf "Unknown flag, exiting...\n"
+            exit 0
+            ;;
+    esac
+done
+
+## Exit the script, if:
+[[ "${GRUB_BTRFS_DISABLE,,}" == "true" ]] && print_error "GRUB_BTRFS_DISABLE is set to true (default=false)"
+if ! type btrfs >/dev/null 2>&1; then print_error "btrfs-progs isn't installed"; fi
+[[ -f "${GRUB_BTRFS_MKCONFIG_LIB:-/usr/share/grub/grub-mkconfig_lib}" ]] && . "${GRUB_BTRFS_MKCONFIG_LIB:-/usr/share/grub/grub-mkconfig_lib}" || print_error "grub-mkconfig_lib couldn't be found"
+[[ "$(btrfs filesystem df / 2>&1)" == *"not a btrfs filesystem"* ]] && print_error "Root filesystem isn't btrfs"
+
+printf "Detecting snapshots ...\n" >&2 ;
+
+## Submenu name
+distro=$(awk -F "=" '/^NAME=/ {gsub(/"/, "", $2); print $2}' /etc/os-release)
+#submenuname=${GRUB_BTRFS_SUBMENUNAME:-"${distro:-Linux} snapshots"}
+submenuname=${GRUB_BTRFS_SUBMENUNAME:-"スナップショット"}
+## Limit snapshots to show in the Grub menu (default=50)
+limit_snap_show="${GRUB_BTRFS_LIMIT:-50}"
+## How to sort snapshots list
+btrfs_subvolume_sort="--sort=${GRUB_BTRFS_SUBVOLUME_SORT:-"-rootid"}"
+## Customize GRUB directory, where "grub.cfg" file is saved
+grub_directory=${GRUB_BTRFS_GRUB_DIRNAME:-"/boot/grub"}
+## Customize BOOT directory, where kernels/initrams/microcode is saved.
+boot_directory=${GRUB_BTRFS_BOOT_DIRNAME:-"/boot"}
+## Customize GRUB-BTRFS.cfg directory, where "grub-btrfs.cfg" file is saved
+grub_btrfs_directory=${GRUB_BTRFS_GBTRFS_DIRNAME:-${grub_directory}}
+## Customize directory where "grub-btrfs.cfg" file is searched for by grub
+grub_btrfs_search_directory=${GRUB_BTRFS_GBTRFS_SEARCH_DIRNAME:-"\${prefix}"}
+## Password protection management for submenu
+# Protection support for submenu (--unrestricted)
+case "${GRUB_BTRFS_DISABLE_PROTECTION_SUBMENU,,}" in
+    true)   unrestricted_access_submenu="--unrestricted ";;
+    *)      unrestricted_access_submenu=""
+esac
+# Authorized users (--users foo,bar)
+if [ -n "${GRUB_BTRFS_PROTECTION_AUTHORIZED_USERS}" ] ; then
+    protection_authorized_users="--users ${GRUB_BTRFS_PROTECTION_AUTHORIZED_USERS} "
+fi
+
+## Probe informations of Root and Boot devices
+# Probe info "Root partition"
+root_device=$(${grub_probe} --target=device /) # Root device
+root_uuid=$(${grub_probe} --device ${root_device} --target="fs_uuid" 2>/dev/null) # UUID of the root device
+root_uuid_subvolume=$(btrfs subvolume show / 2>/dev/null) || print_error "UUID of the root subvolume is not available"; # If UUID of root subvolume is not available, then exit
+root_uuid_subvolume=$(awk -F":" 'match($1, /(^[ \t]+UUID)/) {sub(/^[ \t]+/, "", $2); print $2}' <<< "$root_uuid_subvolume") # UUID of the root subvolume
+# Probe info "Boot partition"
+boot_device=$(${grub_probe} --target=device ${boot_directory}) # Boot device
+boot_uuid=$(${grub_probe} --device ${boot_device} --target="fs_uuid" 2>/dev/null) # UUID of the boot device
+boot_uuid_subvolume=$(btrfs subvolume show "$boot_directory" 2>/dev/null) || boot_uuid_subvolume=" UUID: $root_uuid_subvolume"; # If boot folder isn't a subvolume, then UUID=root_uuid_subvolume
+boot_uuid_subvolume=$(awk -F":" 'match($1, /(^[ \t]+UUID)/) {sub(/^[ \t]+/, "", $2); print $2}' <<< "$boot_uuid_subvolume") # UUID of the boot subvolume
+boot_hs=$(${grub_probe} --device ${boot_device} --target="hints_string" 2>/dev/null) # hints string
+boot_fs=$(${grub_probe} --device ${boot_device} --target="fs" 2>/dev/null) # Type filesystem of boot device
+
+## Parameters passed to the kernel
+kernel_parameters="$GRUB_CMDLINE_LINUX $GRUB_CMDLINE_LINUX_DEFAULT $GRUB_BTRFS_SNAPSHOT_KERNEL_PARAMETERS"
+## Mount point location
+grub_btrfs_mount_point=$(mktemp -dt grub-btrfs.XXXXXXXXXX)
+## Class for theme
+CLASS="--class snapshots --class gnu-linux --class gnu --class os"
+## save IFS
+oldIFS=$IFS
+## Detect uuid requirement (lvm,btrfs...)
+check_uuid_required() {
+if [ "${root_uuid}" = "" ] || [ "${GRUB_DISABLE_LINUX_UUID}" = "true" ] \
+    || ! test -e "/dev/disk/by-uuid/${root_uuid}" \
+    || ( test -e "${root_device}" && uses_abstraction "${root_device}" lvm ); then
+    LINUX_ROOT_DEVICE=${root_device}
+else
+    LINUX_ROOT_DEVICE=UUID=${root_uuid}
+fi
+}
+## Detect rootflags
+detect_rootflags()
+{
+    local fstabflags=$(grep -oE '^\s*[^#][[:graph:]]+\s+/\s+btrfs\s+[[:graph:]]+' "${grub_btrfs_mount_point}/${snap_dir_name_trim}/etc/fstab" \
+                        | sed -E 's/^.*[[:space:]]([[:graph:]]+)$/\1/;s/,?subvol(id)?=[^,$]+//g;s/^,//')
+    rootflags="rootflags=${fstabflags:+$fstabflags,}${GRUB_BTRFS_ROOTFLAGS:+$GRUB_BTRFS_ROOTFLAGS,}"
+}
+
+unmount_grub_btrfs_mount_point()
+{
+if [[ -d "$grub_btrfs_mount_point" ]]; then
+    local wait=true
+    local wait_max=0
+    printf "Unmount %s .." "$grub_btrfs_mount_point" >&2;
+    while $wait; do
+        if grep -qs "$grub_btrfs_mount_point" /proc/mounts; then
+            wait_max=$((1+wait_max))
+            if umount "$grub_btrfs_mount_point" >/dev/null 2>&1; then
+                wait=false # umount successful
+                printf " Success\n" >&2;
+            elif [[ $wait_max = 10 ]]; then
+                printf "\nWarning: Unable to unmount %s in %s\n" "$root_device" "$grub_btrfs_mount_point" >&2;
+                break;
+            else
+                printf "." >&2 ; # output to show that the script is alive
+                sleep 2 # wait 2 seconds before retry
+            fi
+        else
+            wait=false # not mounted
+            printf " Success\n" >&2;
+        fi
+    done
+    if [[ "$wait" != true ]]; then
+        if ! rm -d "$grub_btrfs_mount_point" >/dev/null 2>&1; then
+            printf "Unable to delete %s: Device or ressource is busy\n" "$grub_btrfs_mount_point" >&2;
+        fi
+    fi
+fi
+}
+
+## Create entry
+entry()
+{
+    echo "$@" >> "$grub_btrfs_directory/grub-btrfs.new"
+}
+
+## menu entries
+make_menu_entries()
+{
+## \" required for snap,kernels,init,microcode with space in their name
+    entry "submenu '${title_menu}' {
+    submenu '${title_submenu}' { echo }"
+    for k in "${name_kernel[@]}"; do
+        [[ ! -f "${boot_dir}"/"${k}" ]] && continue;
+        kversion=${k#*"-"}
+        for i in "${name_initramfs[@]}"; do
+            if [[ "${name_initramfs}" != "x" ]] ; then
+                # prefix_i=${i%%"-"*}
+                suffix_i=${i#*"-"}
+                # alt_suffix_i=${i##*"-"}
+                if   [ "${kversion}" = "${suffix_i}" ];                 then i="${i}";
+                elif [ "${kversion}.img" = "${suffix_i}" ];             then i="${i}";
+                elif [ "${kversion}-fallback.img" = "${suffix_i}" ];    then i="${i}";
+                elif [ "${kversion}.gz" = "${suffix_i}" ];              then i="${i}";
+                else continue;
+                fi
+                for u in "${name_microcode[@]}"; do
+                    if [[ "${name_microcode}" != "x" ]] ; then
+                    entry "
+    menuentry '  "${k}" & "${i}" & "${u}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {"
+                    else
+                    entry "
+    menuentry '  "${k}" & "${i}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {"
+                    fi
+                    entry "\
+        if [ x\$feature_all_video_module = xy ]; then
+        insmod all_video
+        fi
+        set gfxpayload=keep
+        insmod ${boot_fs}
+        if [ x\$feature_platform_search_hint = xy ]; then
+            search --no-floppy --fs-uuid  --set=root ${boot_hs} ${boot_uuid}
+        else
+            search --no-floppy --fs-uuid  --set=root ${boot_uuid}
+        fi
+        echo 'Loading Snapshot: "${snap_date_trim}" "${snap_dir_name_trim}"'
+        echo 'Loading Kernel: "${k}" ...'
+        linux \"${boot_dir_root_grub}/"${k}"\" root="${LINUX_ROOT_DEVICE}" ${kernel_parameters} ${rootflags}subvol=\""${snap_dir_name_trim}"\""
+                    if [[ "${name_microcode}" != "x" ]] ; then
+                        entry "\
+        echo 'Loading Microcode & Initramfs: "${u}" "${i}" ...'
+        initrd \"${boot_dir_root_grub}/"${u}"\" \"${boot_dir_root_grub}/"${i}"\""
+                    else
+                        entry "\
+        echo 'Loading Initramfs: "${i}" ...'
+        initrd \"${boot_dir_root_grub}/"${i}"\""
+                    fi
+                    entry "    }"
+                    count_warning_menuentries=$((1+count_warning_menuentries))
+                done
+            else
+                for u in "${name_microcode[@]}"; do
+                    if [[ "${name_microcode}" != "x" ]] ; then
+                    entry "
+    menuentry '  "${k}" & "${u}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {"
+                    else
+                    entry "
+    menuentry '  "${k}"' ${CLASS} "\$menuentry_id_option" 'gnulinux-snapshots-$boot_uuid' {"
+                    fi
+                    entry "\
+        if [ x\$feature_all_video_module = xy ]; then
+        insmod all_video
+        fi
+        set gfxpayload=keep
+        insmod ${boot_fs}
+        if [ x\$feature_platform_search_hint = xy ]; then
+            search --no-floppy --fs-uuid  --set=root ${boot_hs} ${boot_uuid}
+        else
+            search --no-floppy --fs-uuid  --set=root ${boot_uuid}
+        fi
+        echo 'Loading Snapshot: "${snap_date_trim}" "${snap_dir_name_trim}"'
+        echo 'Loading Kernel: "${k}" ...'
+        linux \"${boot_dir_root_grub}/"${k}"\" root="${LINUX_ROOT_DEVICE}" ${kernel_parameters} ${rootflags}subvol=\""${snap_dir_name_trim}"\""
+                    if [[ "${name_microcode}" != "x" ]] ; then
+                        entry "\
+        echo 'Loading Microcode: "${u}" ...'
+        initrd \"${boot_dir_root_grub}/"${u}"\""
+                    fi
+                    entry "    }"
+                    count_warning_menuentries=$((1+count_warning_menuentries))
+                done
+            fi
+        done
+    done
+    entry  "}"
+}
+
+## Trim a string from leading and trailing whitespaces
+trim() {
+    local var="$*"
+    var="${var#"${var%%[![:space:]]*}"}"
+    var="${var%"${var##*[![:space:]]}"}"
+    echo -n "$var"
+}
+
+## List of snapshots on filesystem
+snapshot_list()
+{
+    local snapper_info="info.xml"
+    local timeshift_info="info.json"
+    local date_snapshots=()
+    local path_snapshots=()
+    local type_snapshots=()
+    local description_snapshots=()
+    IFS=$'\n'
+    for snap in $(btrfs subvolume list -sa "${btrfs_subvolume_sort}" /); do # Parse btrfs snapshots
+        IFS=$oldIFS
+        snap=(${snap})
+        local path_snapshot=${snap[@]:13:${#snap[@]}}
+        if [ "$path_snapshot" = "DELETED" ]; then continue; fi # Discard deleted snapshots
+        [[ ${path_snapshot%%"/"*} == "<FS_TREE>" ]] && path_snapshot=${path_snapshot#*"/"} # Remove the "<FS_TREE>" string at the beginning of the path
+
+        # ignore specific path during run "grub-mkconfig"
+        if [ -n "${GRUB_BTRFS_IGNORE_SPECIFIC_PATH}" ] ; then
+            for isp in "${GRUB_BTRFS_IGNORE_SPECIFIC_PATH[@]}" ; do
+                [[ "${path_snapshot}" == "${isp}" ]] && continue 2;
+            done
+        fi
+        if [ -n "${GRUB_BTRFS_IGNORE_PREFIX_PATH}" ] ; then
+            for isp in "${GRUB_BTRFS_IGNORE_PREFIX_PATH[@]}" ; do
+                [[ "${path_snapshot}" == "${isp}"/* ]] && continue 2;
+            done
+        fi
+        [[ ! -d "$grub_btrfs_mount_point/$path_snapshot/boot" ]] && continue; # Discard snapshots without /boot folder
+
+        # Parse Snapper & timeshift informations
+        local type_snapshot="N/A"
+        local description_snapshot="N/A"
+        if [[ -s "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$snapper_info" ]] ; then
+            type_snapshot=$(awk -F"<|>" 'match($2, /^type/) {print $3}' "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$snapper_info") # search matching string beginning "type"
+            description_snapshot=$(awk -F"<|>" 'match($2, /^description/) {print $3}' "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$snapper_info") # search matching string beginning "description"
+        elif [[ -s "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$timeshift_info" ]] ; then
+            type_snapshot=$(awk -F" : " 'match($1, /^[ \t]+"tags"/) {gsub(/"|,/,"");print $2}' "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$timeshift_info") # search matching string beginning "tags"
+            description_snapshot=$(awk -F" : " 'match($1, /^[ \t]+"comments"/) {gsub(/"|,/,"");print $2}' "$grub_btrfs_mount_point/${path_snapshot%"/"*}/$timeshift_info") # search matching string beginning "comments"
+        fi
+        [[ -z "$type_snapshot" ]] && type_snapshot=("N/A")
+        [[ -z "$description_snapshot" ]] && description_snapshot=("N/A")
+
+        # ignore specific {type,tag,description} of snapshot during run "grub-mkconfig"
+        if [ -n "${GRUB_BTRFS_IGNORE_SNAPSHOT_TYPE}" ] ; then
+            for ist in "${GRUB_BTRFS_IGNORE_SNAPSHOT_TYPE[@]}" ; do
+                [[ "${type_snapshot}" == "${ist}" ]] && continue 2;
+            done
+        fi
+        if [ -n "${GRUB_BTRFS_IGNORE_SNAPSHOT_DESCRIPTION}" ] ; then
+            for isd in "${GRUB_BTRFS_IGNORE_SNAPSHOT_DESCRIPTION[@]}" ; do
+                [[ "${description_snapshot}" == "${isd}" ]] && continue 2;
+            done
+        fi
+
+        local date_snapshot="${snap[@]:10:2}"
+        date_snapshots+=("$date_snapshot")
+        path_snapshots+=("$path_snapshot")
+        type_snapshots+=("$type_snapshot")
+        description_snapshots+=("$description_snapshot")
+    done
+
+    # Find max length of a snapshot date, needed for pretty formatting
+    local max_date_length=0
+    for i in "${date_snapshots[@]}"; do
+        local length="${#i}"
+        [[ "$length" -gt "$max_date_length" ]] && max_date_length=$length
+    done
+
+    # Find max length of a snapshot name, needed for pretty formatting
+    local max_path_length=0
+    for i in "${path_snapshots[@]}"; do
+        local length="${#i}"
+        [[ "$length" -gt "$max_path_length" ]] && max_path_length=$length
+    done
+
+    # Find max length of a snapshot type, needed for pretty formatting
+    local max_type_length=0
+    for i in "${type_snapshots[@]}"; do
+        local length="${#i}"
+        [[ "$length" -gt "$max_type_length" ]] && max_type_length=$length
+    done
+
+    # Find max length of a snapshot description, needed for pretty formatting
+    local max_description_length=0
+    for i in "${description_snapshots[@]}"; do
+        local length="${#i}"
+        [[ "$length" -gt "$max_description_length" ]] && max_description_length=$length
+    done
+
+    for i in "${!path_snapshots[@]}"; do
+        printf -v entry "%-${max_date_length}s | %-${max_path_length}s | %-${max_type_length}s | %-${max_description_length}s |" "${date_snapshots[$i]}" "${path_snapshots[$i]}" "${type_snapshots[$i]}" "${description_snapshots[$i]}"
+        echo "$entry"
+    done
+
+    IFS=$oldIFS
+}
+
+## Parse snapshots in snapshot_list
+parse_snapshot_list()
+{
+    snap_date=" $(echo "$item" | cut -d'|' -f1)" # column_1, first space is necessary for pretty formatting
+    snap_date_trim="$(trim "$snap_date")"
+
+    snap_dir_name="$(echo "$item" | cut -d'|' -f2)" # column_2
+    snap_dir_name_trim="$(trim "$snap_dir_name")"
+    snap_snapshot="$snap_dir_name" # Used by "title_format" function
+
+    snap_type="$(echo "$item" | cut -d'|' -f3)" # column_3
+
+    snap_description="$(echo "$item" | cut -d'|' -f4)" # column_4
+}
+
+## Detect kernels in "boot_directory"
+detect_kernel()
+{
+    list_kernel=()
+    # Original kernel (auto-detect)
+    for okernel in  "${boot_dir}"/vmlinuz-* \
+                    "${boot_dir}"/vmlinux-* \
+                    "${boot_dir}"/kernel-* ; do
+        [[ ! -f "${okernel}" ]] && continue;
+        list_kernel+=("$okernel")
+    done
+
+    # Custom name kernel in "GRUB_BTRFS_NKERNEL"
+    if [ -n "${GRUB_BTRFS_NKERNEL}" ] ; then
+        for ckernel in "${boot_dir}/${GRUB_BTRFS_NKERNEL[@]}" ; do
+            [[ ! -f "${ckernel}" ]] && continue;
+            list_kernel+=("$ckernel")
+        done
+    fi
+}
+
+## Detect initramfs in "boot_directory"
+detect_initramfs()
+{
+    list_initramfs=()
+    # Original initramfs (auto-detect)
+    for oinitramfs in   "${boot_dir}"/initrd.img-* \
+                        "${boot_dir}"/initramfs-* \
+                        "${boot_dir}"/initrd-* ; do
+        [[ ! -f "${oinitramfs}" ]] && continue;
+        list_initramfs+=("$oinitramfs")
+    done
+
+    # Custom name initramfs in "GRUB_BTRFS_NINIT"
+    if [ -n "${GRUB_BTRFS_NINIT}" ] ; then
+        for cinitramfs in "${boot_dir}/${GRUB_BTRFS_NINIT[@]}" ; do
+            [[ ! -f "${cinitramfs}" ]] && continue;
+            list_initramfs+=("$cinitramfs")
+        done
+    fi
+    if [ -z "${list_initramfs}" ]; then list_initramfs=(x); fi
+}
+
+## Detect microcode in "boot_directory"
+detect_microcode()
+{
+    list_ucode=()
+    # Original intel/amd microcode (auto-detect)
+    # See "https://www.gnu.org/software/grub/manual/grub/html_node/Simple-configuration.html"
+    for oiucode in  "${boot_dir}"/intel-uc.img \
+                    "${boot_dir}"/intel-ucode.img \
+                    "${boot_dir}"/amd-uc.img \
+                    "${boot_dir}"/amd-ucode.img \
+                    "${boot_dir}"/early_ucode.cpio \
+                    "${boot_dir}"/microcode.cpio; do
+        [[ ! -f "${oiucode}" ]] && continue;
+        list_ucode+=("$oiucode")
+    done
+
+    # Custom name microcode in "GRUB_BTRFS_CUSTOM_MICROCODE"
+    if [ -n "${GRUB_BTRFS_CUSTOM_MICROCODE}" ] ; then
+        for cucode in "${boot_dir}/${GRUB_BTRFS_CUSTOM_MICROCODE[@]}" ; do
+            [[ ! -f "${cucode}" ]] && continue
+            list_ucode+=("$cucode")
+        done
+    fi
+    if [ -z "${list_ucode}" ]; then list_ucode=(x); fi
+}
+
+## Title format in Grub-menu
+declare -A title_column=( [date]=Date [snapshot]=Snapshot [type]=Type [description]=Description ) # Column title that appears in the header
+title_format()
+{
+    title_menu="|" # "|" is for visuals only
+    title_submenu="|" # "|" is for visuals only
+    [[ -z "${GRUB_BTRFS_TITLE_FORMAT}" ]] && GRUB_BTRFS_TITLE_FORMAT=("date" "snapshot" "type" "description"); # Default parameters
+    for key in "${!GRUB_BTRFS_TITLE_FORMAT[@]}"; do
+            [[ ${GRUB_BTRFS_TITLE_FORMAT[$key],,} != "${title_column[${GRUB_BTRFS_TITLE_FORMAT[$key]}],,}" ]] && continue; # User used wrong parameter
+            declare -n var="snap_${GRUB_BTRFS_TITLE_FORMAT[$key],,}" # $var is a indirect variable
+            if [[ "${#var}" -lt "${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}" ]]; then # Add extra spaces if length of $var is smaller than the length of column, needed for pretty formatting
+                printf -v var "%-$(((${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}-${#var})+${#var}))s" "${var}";
+            fi
+		    var="$(sed  "s/'//g"  <(echo "${var}"))"
+            title_menu+="${var}|"
+            title_submenu+=" $(trim "${var}") |"
+    done
+}
+# Adds a header to the grub-btrfs.cfg file
+header_menu()
+{
+    local header_entry=""
+    [[ -z "${GRUB_BTRFS_TITLE_FORMAT}" ]] && GRUB_BTRFS_TITLE_FORMAT=("date" "snapshot" "type" "description"); # Default parameters
+        for key in "${!GRUB_BTRFS_TITLE_FORMAT[@]}"; do
+            [[ ${GRUB_BTRFS_TITLE_FORMAT[$key],,} != "${title_column[${GRUB_BTRFS_TITLE_FORMAT[$key]}],,}" ]] && continue; # User used wrong parameter
+            declare -n var="snap_${GRUB_BTRFS_TITLE_FORMAT[$key],,}" # $var is a indirect variable
+            # Center alignment, needed for pretty formatting
+            local lenght_title_column_left=$((${#var}-${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}))
+            ((lenght_title_column_left%2)) && lenght_title_column_left=$((lenght_title_column_left+1));  # If the difference is an odd number, add an extra space
+            lenght_title_column_left=$((((lenght_title_column_left/2)+${#title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]})));
+            local lenght_title_column_right=$(((${#var}-lenght_title_column_left)+1)) #+1 is necessary for extra "|" character
+            header_entry+=$(printf "%${lenght_title_column_left}s%${lenght_title_column_right}s" "${title_column[${GRUB_BTRFS_TITLE_FORMAT[$key],,}]}" "|") # Final "|" is for visuals only
+        done
+    sed -i "1imenuentry '|${header_entry}' { echo }" "$grub_btrfs_directory/grub-btrfs.new" # First "|" is for visuals only
+}
+
+## List of kernels, initramfs and microcode in snapshots
+boot_bounded()
+{
+    # Initialize menu entries
+    IFS=$'\n'
+    for item in $(snapshot_list); do
+        [[ ${limit_snap_show} -le 0 ]] && break; # fix: limit_snap_show=0
+        IFS=$oldIFS
+        parse_snapshot_list
+        boot_dir="$grub_btrfs_mount_point/$snap_dir_name_trim$boot_directory"
+        detect_kernel
+        if [ -z "${list_kernel}" ]; then continue; fi
+        name_kernel=("${list_kernel[@]##*"/"}")
+        detect_initramfs
+        name_initramfs=("${list_initramfs[@]##*"/"}")
+        detect_microcode
+        name_microcode=("${list_ucode[@]##*"/"}")
+        detect_rootflags
+        title_format
+        boot_dir_root_grub="$(make_system_path_relative_to_its_root "${boot_dir}")" # convert "boot_directory" to root of GRUB (e.g /boot become /)
+        make_menu_entries
+        # show snapshot found during run "grub-mkconfig"
+        if [[ "${GRUB_BTRFS_SHOW_SNAPSHOTS_FOUND:-"true"}" = "true" ]]; then
+            printf "Found snapshot: %s\n" "$item" >&2 ;
+        fi
+        # Limit snapshots found during run "grub-mkconfig"
+        count_limit_snap=$((1+count_limit_snap))
+        [[ $count_limit_snap -ge $limit_snap_show ]] && break;
+    done
+    IFS=$oldIFS
+}
+
+boot_separate()
+{
+    boot_dir="${boot_directory}"
+    boot_dir_root_grub="$(make_system_path_relative_to_its_root "${boot_dir}")" # convert "boot_directory" to root of GRUB (e.g /boot become /)
+    detect_kernel
+    if [ -z "${list_kernel}" ]; then print_error "Kernels not found."; fi
+    name_kernel=("${list_kernel[@]##*"/"}")
+    detect_initramfs
+    name_initramfs=("${list_initramfs[@]##*"/"}")
+    detect_microcode
+    name_microcode=("${list_ucode[@]##*"/"}")
+
+    # Initialize menu entries
+    IFS=$'\n'
+    for item in $(snapshot_list); do
+        [[ ${limit_snap_show} -le 0 ]] && break; # fix: limit_snap_show=0
+        IFS=$oldIFS
+        parse_snapshot_list
+        detect_rootflags
+        title_format
+        make_menu_entries
+        # show snapshot found during run "grub-mkconfig"
+        if [[ "${GRUB_BTRFS_SHOW_SNAPSHOTS_FOUND:-"true"}" = "true" ]]; then
+            printf "Found snapshot: %s\n" "$item" >&2 ;
+        fi
+        # Limit snapshots found during run "grub-mkconfig"
+        count_limit_snap=$((1+count_limit_snap))
+        [[ $count_limit_snap -ge $limit_snap_show ]] && break;
+    done
+    IFS=$oldIFS
+}
+
+rm -f "$grub_btrfs_directory/grub-btrfs.new"
+true > "$grub_btrfs_directory/grub-btrfs.new" # Create a "grub-btrfs.new" file in "grub_btrfs_directory"
+# Create a backup of the "$grub_btrfs_directory/grub-btrfs.cfg" file if exist
+if [ -e "$grub_btrfs_directory/grub-btrfs.cfg" ]; then
+	mv -f "$grub_btrfs_directory/grub-btrfs.cfg" "$grub_btrfs_directory/grub-btrfs.cfg.bkp"
+fi
+# Create mount point then mounting
+[[ ! -d $grub_btrfs_mount_point ]] && mkdir -p "$grub_btrfs_mount_point"
+mount -o ro,subvolid=5 /dev/disk/by-uuid/"$root_uuid" "$grub_btrfs_mount_point/" > /dev/null
+trap "unmount_grub_btrfs_mount_point" EXIT # unmounting mount point on EXIT signal
+count_warning_menuentries=0 # Count menuentries
+count_limit_snap=0 # Count snapshots
+check_uuid_required
+# Detects if /boot is a separate partition
+[[ "${GRUB_BTRFS_OVERRIDE_BOOT_PARTITION_DETECTION,,}" == "true" ]] && printf "Override boot partition detection : enable \n" >&2 && boot_separate;
+if [[ "$root_uuid" != "$boot_uuid" ]] || [[ "$root_uuid_subvolume" != "$boot_uuid_subvolume" ]]; then boot_separate ; else boot_bounded ; fi
+# Make a submenu in GRUB (grub.cfg)
+cat << EOF
+if [ ! -e "${grub_btrfs_search_directory}/grub-btrfs.cfg" ]; then
+echo ""
+else
+submenu '${submenuname}' ${protection_authorized_users}${unrestricted_access_submenu}{
+    configfile "${grub_btrfs_search_directory}/grub-btrfs.cfg"
+}
+fi
+EOF
+# Show warn, menuentries exceeds 250 entries
+[[ $count_warning_menuentries -ge 250 ]] && printf "Generated %s total GRUB entries. You might experience issues loading snapshots menu in GRUB.\n" "${count_warning_menuentries}" >&2 ;
+# Show total found snapshots
+if [[ "${GRUB_BTRFS_SHOW_TOTAL_SNAPSHOTS_FOUND:-"true"}" = "true" && -n "${count_limit_snap}" && "${count_limit_snap}" != "0" ]]; then
+    printf "Found %s snapshot(s)\n" "${count_limit_snap}" >&2 ;
+fi
+# if no snapshot found, delete the "$grub_btrfs_directory/grub-btrfs.new" file and the "$grub_btrfs_directory/grub-btrfs.cfg.bkp" file and exit
+if [[ "${count_limit_snap}" = "0" || -z "${count_limit_snap}" ]]; then
+    rm -f "$grub_btrfs_directory/grub-btrfs.new" "$grub_btrfs_directory/grub-btrfs.cfg.bkp"
+    print_error "No snapshots found."
+fi
+# Move "grub-btrfs.new" to "grub-btrfs.cfg"
+header_menu
+if "${bindir}/${GRUB_BTRFS_SCRIPT_CHECK:-grub-script-check}" "$grub_btrfs_directory/grub-btrfs.new"; then
+    cat "$grub_btrfs_directory/grub-btrfs.new" > "$grub_btrfs_directory/grub-btrfs.cfg"
+    rm -f "$grub_btrfs_directory/grub-btrfs.new" "$grub_btrfs_directory/grub-btrfs.cfg.bkp"
+else
+if [ -e "$grub_btrfs_directory/grub-btrfs.cfg.bkp" ]; then
+        mv -f "$grub_btrfs_directory/grub-btrfs.cfg.bkp" "$grub_btrfs_directory/grub-btrfs.cfg"
+fi
+	print_error "Syntax errors were detected in generated ${grub_btrfs_directory}/grub-btrfs.new file. The old grub-btrfs.cfg file (if present) have been restored."
+fi
+
+# warn when this script is run but there is no entry in grub.cfg
+grep "snapshots-btrfs" "${grub_directory}/grub.cfg" >/dev/null 2>&1 || printf "\nWARNING: '%s' needs to run at least once to generate the snapshots (sub)menu entry in grub the main menu. \
+After that this script can run alone to generate the snapshot entries.\n\n" "${GRUB_BTRFS_MKCONFIG:-grub-mkconfig}" >&2 ;

+ 296 - 0
home/cheyan/Development/wwwroot/iamcheyan.com/app/pelican/content/ubuntu.md

@@ -0,0 +1,296 @@
+Title: ubuntu 安装后的相关配置记录
+Date: 2021-05-31
+Authors: 澈言
+Tags: ubuntu
+
+### ![screenshot_2023-12-31_22-01-03](./assets/screenshot_2023-12-31_22-01-03.png)
+
+### 语言支持
+
+    sudo apt install ttf-mscorefonts-installer
+    sudo apt install fonts-noto-cjk-extra
+    sudo apt install fonts-noto-cjk ttf-wqy-microhei
+    sudo apt install software-properties-gtk
+    sudo apt install language-pack-zh-hans-base
+    sudo apt install language-pack-ja
+
+###  rime
+    sudo apt install -y libboost-all-dev capnproto libgoogle-glog-dev libleveldb-dev librime-data liblua5.1-0-dev libmarisa-dev libopencc-dev libyaml-cpp-dev cmake git libgtest-dev ninja-build wget gcc g++   # 安装编译相关包
+    git clone  https://github.com/sbxlmdsl/librime
+    sudo apt install fcitx5 fcitx5-rime	fcitx5-material-color # fcitx5
+    sudo apt install ibus ibus-rime	# ibus
+    cd librime/ # 进入源码目录
+    make    # 编译器
+    sudo make install   # 安装
+
+##### 配置 Fcitx5
+
+存放路径:`$HOME/.local/share/fcitx5/rime/`
+
+```bash
+nano ~/.pam_environment
+
+GTK_IM_MODULE DEFAULT=fcitx
+QT_IM_MODULE  DEFAULT=fcitx
+XMODIFIERS    DEFAULT=@im=fcitx
+```
+
+##### 配置 iBus
+
+存放路径:`$HOME/.config/ibus/rime/`
+
+```bash
+nano ~/.bashrc
+
+export GTK_IM_MODULE=ibus
+export XMODIFIERS=@im=ibus
+export QT_IM_MODULE=ibus
+```
+
+###  卸载相关自带软件
+    sudo apt-get remove --purge l‘ibreoffice*’
+    sudo apt-get autoremove --purge ‘libreoffice*’
+
+###  常用软件
+	sudo apt install copyq freerdp2-x11 flameshot xclip xdotool xwininfo	# Applications
+	sudo apt-get install plank
+	
+	type -p curl >/dev/null || (sudo apt update && sudo apt install curl -y)	# gh	
+	curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
+	&& sudo chmod go+r /usr/share/keyrings/githubcli-archive-keyring.gpg \
+	&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null \
+	&& sudo apt update \
+	&& sudo apt install gh -y
+
+### neovim
+
+```bash
+sudo add-apt-repository ppa:neovim-ppa/unstable	# neovim
+sudo apt-get update
+sudo apt-get install neovim
+nvim --version
+
+sudo update-alternatives --install /usr/bin/vi vi /usr/bin/nvim 60	# 将 Neovim 设置为默认的 vi 或 vim 替代品
+sudo update-alternatives --config vi
+sudo update-alternatives --install /usr/bin/vim vim /usr/bin/nvim 60
+sudo update-alternatives --config vim
+
+if command -v curl >/dev/null 2>&1; then	# nvimdots
+    bash -c "$(curl -fsSL https://raw.githubusercontent.com/ayamir/nvimdots/HEAD/scripts/install.sh)"
+else
+    bash -c "$(wget -O- https://raw.githubusercontent.com/ayamir/nvimdots/HEAD/scripts/install.sh)"
+fi
+
+rm -rf ~/.config/nvim	# 卸载
+rm -rf ~/.local/share/nvim
+```
+
+### sublime text
+
+```
+wget -qO - https://download.sublimetext.com/sublimehq-pub.gpg | gpg --dearmor | sudo tee /etc/apt/trusted.gpg.d/sublimehq-archive.gpg > /dev/null
+echo "deb https://download.sublimetext.com/ apt/stable/" | sudo tee /etc/apt/sources.list.d/sublime-text.list
+sudo apt-get update
+sudo apt-get install sublime-text
+
+sudo apt-get install apt-transport-https	# 如果此操作失败,请确保将 apt 设置为使用 https 源
+```
+
+
+
+
+
+### 编译相关
+
+    sudo apt install -y libboost-all-dev capnproto libgoogle-glog-dev libleveldb-dev librime-data liblua5.1-0-dev libmarisa-dev libopencc-dev cmake git ninja-build wget gcc g++ libgtest-dev libyaml-cpp-dev   # 安装编译相关包
+
+### elementary 相关编译
+
+```bash
+sudo apt-get install valac
+sudo apt-get install libgee-0.8-dev
+sudo apt-get install libgtk-3-dev
+sudo apt-get install libgranite-dev
+sudo apt-get install libhandy-1-dev
+sudo apt-get install libvte-2.91-dev
+sudo apt-get install xvfb
+```
+
+###  Timeshift下brrfs分区下启用
+
+	sudo apt install timeshift
+	
+	sudo apt install build-essential git	# grub
+	mkdir -p ~/git
+	cd ~/git
+	git clone https://github.com/Antynea/grub-btrfs.git
+	cd grub-btrfs
+	sudo make install
+	sudo grub-mkconfig
+	sudo grub-btrfsd systemd instructions
+	
+	sudo apt install -y git make	# timeshift-autosnap-apt
+	git clone https://github.com/wmutschl/timeshift-autosnap-apt.git /home/$USER/timeshift-autosnap-apt
+	cd /home/$USER/timeshift-autosnap-apt
+	sudo make install
+	sudo systemctl start grub-btrfsd
+	sudo systemctl enable grub-btrfsd
+
+###  Jianguoyun && nextcloud
+    sudo apt-get install libglib2.0-dev libgtk2.0-dev libnautilus-extension-dev gvfs-bin python3-gi gir1.2-appindicator3-0.1 gir1.2-notify-0.7 gdebi gvfs-bin python3-gi gir1.2-appindicator3-0.1 gir1.2-notify-0.7
+    wget https://www.jianguoyun.com/static/exe/installer/ubuntu/nautilus_nutstore_amd64.deb
+    sudo gdebi nautilus_nutstore_amd64.deb
+    
+    sudo add-apt-repository ppa:nextcloud-devs/client	# nextcloud
+    sudo apt update
+    sudo apt install nextcloud-client
+
+### git
+
+```bash
+git config --global user.email "me@iamcheyan.com"
+git config --global user.name "cheyan"
+git config --global credential.helper store
+```
+
+###  v2raya for debian
+
+    curl -Ls https://mirrors.v2raya.org/go.sh | sudo bash
+    sudo systemctl disable v2ray --now	#Xray 需要替换服务为 xray
+    wget -qO - https://apt.v2raya.org/key/public-key.asc | sudo tee /etc/apt/trusted.gpg.d/v2raya.asc	#  添加公钥
+    echo "deb https://apt.v2raya.org/ v2raya main" | sudo tee /etc/apt/sources.list.d/v2raya.list	#  添加软件源
+    sudo apt update
+    sudo apt install v2ray v2raya
+    
+    sudo systemctl start v2raya.service
+    sudo systemctl enable v2raya.service
+
+###  v2raya for snap
+    sudo mv /etc/apt/preferences.d/10-tuxedo-snap  ~/nosnap.backup
+    sudo apt install snapd
+    snap install v2ray-core
+    snap install v2raya
+
+###  wechat
+    wget -O- https://deepin-wine.i-m.dev/setup.sh | sh
+    sudo apt-get install com.qq.weixin.deepin
+    sudo apt --fix-broken install
+
+###  gnome 相关
+	sudo apt install yaru-theme-gtk yaru-theme-icon
+	sudo apt install gedit gnome-terminal nautilus gnome-tweaks 
+	sudo apt remove gnome-games libgnome-games-support-1-3:amd64 libgnome-games-support-common	# 卸载游戏
+	sudo apt install deja-dup	# 备份工具
+
+### pop os 相关
+
+```
+sudo add-apt-repository ppa:system76/pop
+sudo apt update
+sudo apt install pop-icon-theme	# 图标
+sudo apt install pop-theme	# 主题
+```
+
+###  kvm
+
+    sudo apt install qemu-kvm libvirt-daemon-system libvirt-clients bridge-utils virtinst virt-manager
+    sudo adduser $USER libvirt	# 用户"cheyan"已经属于"libvirt"组
+    sudo adduser $USER kvm	# 用户"cheyan"已经属于"kvm"组
+    sudo virsh list --all	# 查看虚拟机列表
+    sudo virsh autostart win10 # 启用开机启动
+    sudo virsh autostart --disable win10	# 禁用开机启动
+
+
+###  zsh
+    bash ~/.dotfiles/dotsync/bin/dotsync -L
+    sudo apt install zsh zsh-autosuggestions zsh-syntax-highlighting autojump fish fzf fd-find thefuck
+    chsh -s /bin/zsh    #  将 Zsh 设置为默认 Shell
+    git clone https://github.com/zsh-users/zsh-syntax-highlighting.git ~/.oh-my-zsh/custom/plugins/zsh-syntax-highlighting
+    git clone https://github.com/zsh-users/zsh-autosuggestions ~/.oh-my-zsh/custom/plugins/zsh-autosuggestions
+    
+    rm -rf /home/cheyan/.oh-my-zsh
+    wget https://github.com/robbyrussell/oh-my-zsh/raw/master/tools/install.sh
+    bash ./install.sh   # 安装 Oh My Zsh 主题
+    
+    git clone --depth=1 https://github.com/romkatv/powerlevel10k.git ~/.oh-my-zsh/custom/themes/powerlevel10k
+    # 在 .zshrc 文件中设置`ZSH_THEME="powerlevel10k/powerlevel10k"`
+    p10k configure  # 安装和配置 powerlevel10k
+
+
+###  wine
+    sudo dpkg --add-architecture i386
+    sudo mkdir -pm755 /etc/apt/keyrings
+    sudo wget -O /etc/apt/keyrings/winehq-archive.key https://dl.winehq.org/wine-builds/winehq.key
+    sudo wget -NP /etc/apt/sources.list.d/ https://dl.winehq.org/wine-builds/ubuntu/dists/jammy/winehq-jammy.sources
+    sudo apt-get update
+    
+    sudo apt-get install --install-recommends winehq-stable	#  稳定分支
+    sudo apt install winehq-devel	# 开发版本
+    sudo apt install winehq-staging	# wine-staging
+    
+    wine --version
+
+###  vs code
+    sudo apt update
+    sudo apt install software-properties-common apt-transport-https curl
+    curl -sSL https://packages.microsoft.com/keys/microsoft.asc | sudo apt-key add -
+    sudo add-apt-repository "deb [arch=amd64] https://packages.microsoft.com/repos/vscode stable main"
+    sudo apt update
+    sudo apt install code
+
+###  chrome
+    sudo sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google-chrome.list' 
+    wget -q -O - https://dl.google.com/linux/linux_signing_key.pub | sudo apt-key add - 
+    sudo apt update 
+    sudo apt install google-chrome-stable 
+
+###  edge
+    curl https://packages.microsoft.com/keys/microsoft.asc | gpg --dearmor > microsoft.gpg
+    sudo install -o root -g root -m 644 microsoft.gpg /etc/apt/trusted.gpg.d/
+    sudo sh -c 'echo "deb [arch=amd64] https://packages.microsoft.com/repos/edge stable main" > /etc/apt/sources.list.d/microsoft-edge-dev.list'
+    sudo rm microsoft.gpg
+    sudo apt update && sudo apt install microsoft-edge-stable
+    sudo rm /etc/apt/sources.list.d/microsoft-edge-dev.list
+
+###  fsearch
+    sudo add-apt-repository ppa:christian-boxdoerfer/fsearch-stable
+    sudo apt update
+    sudo apt install fsearch
+
+
+###  flatpak
+    flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo  #  添加源
+    flatpak install flathub com.github.gmg137.netease-cloud-music-gtk   # 网易云音乐
+    flatpak install flathub io.github.fastrizwaan.WineZGUI	# wineZGUI
+    flatpak install com.github.hluk.copyq	# copyq
+
+### rofi
+
+```bash
+sudo apt-get install rofi
+```
+
+### 编译支持中文输入法的rofi
+
+```bash
+git clone https://github.com/davatorium/rofi.git
+sudo apt install libxcb1-dev libstartup-notification0-dev# 库
+```
+
+#### 修改imdkit下的value值为true
+
+````bash
+nano meson_options.txt 
+option('imdkit', type: 'boolean', value: true, description: 'IMDKit support')
+
+meson setup build
+ninja -C build
+ninja -C build install
+
+/home/cheyan/APP/rofi/build/rofi -show
+````
+
+###  解决自动唤醒
+
+    cat /proc/acpi/wakeup   # 查看唤醒
+    sudo sh -c 'grep enabled /proc/acpi/wakeup | cut -f 1 -d " " | xargs -I {} sh -c "echo {} > /proc/acpi/wakeup"' # 禁用所有唤醒

+ 15 - 0
usr/lib/systemd/system-sleep/disable_automatic_wake-up

@@ -0,0 +1,15 @@
+#!/bin/sh
+
+# /usr/lib/systemd/system-sleep
+case $1 in
+    pre)
+        echo '禁用所有的 swap 分区'
+        swapoff -a
+        
+        echo '重新启用所有的 swap 分区'
+        swapon --all
+        sh -c 'grep enabled /proc/acpi/wakeup | cut -f 1 -d " " | xargs -I {} sh -c "echo {} > /proc/acpi/wakeup"' # 禁用所有唤醒
+        ;;
+    *)
+        ;;
+esac

+ 486 - 0
usr/share/cinnamon/js/ui/appSwitcher/appSwitcher.js

@@ -0,0 +1,486 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Lang = imports.lang;
+
+const Clutter = imports.gi.Clutter;
+const St = imports.gi.St;
+const Meta = imports.gi.Meta;
+const Mainloop = imports.mainloop;
+const Main = imports.ui.main;
+const Cinnamon = imports.gi.Cinnamon;
+
+const DISABLE_HOVER_TIMEOUT = 500; // milliseconds
+
+function sortWindowsByUserTime(win1, win2) {
+    let t1 = win1.get_user_time();
+    let t2 = win2.get_user_time();
+
+    this.minimizedAwareAltTab = global.settings.get_boolean("alttab-minimized-aware");
+    if (this.minimizedAwareAltTab) {
+        let m1 = win1.minimized;
+        let m2 = win2.minimized;
+        if (m1 == m2) {
+            return (t2 > t1) ? 1 : -1;
+        }
+        else {
+            return m1 ? 1 : -1;
+        }
+    }
+    else {
+        return (t2 > t1) ? 1 : -1;
+    }
+}
+
+function matchSkipTaskbar(win) {
+    return !win.is_skip_taskbar();
+}
+
+function matchWmClass(win) {
+    return win.get_wm_class() == this && !win.is_skip_taskbar();
+}
+
+function matchWorkspace(win) {
+    return win.get_workspace() == this && !win.is_skip_taskbar();
+}
+
+function primaryModifier(mask) {
+    if (mask == 0)
+        return 0;
+
+    let primary = 1;
+    while (mask > 1) {
+        mask >>= 1;
+        primary <<= 1;
+    }
+    return primary;
+}
+
+function getWindowsForBinding(binding) {
+    // Construct a list with all windows
+    let windows = [];
+    let windowActors = global.get_window_actors();
+    for (let i in windowActors)
+        windows.push(windowActors[i].get_meta_window());
+
+    windows = windows.filter(Main.isInteresting);
+    windows = windows.filter(w => w.get_monitor() === global.screen.get_current_monitor()) // 过滤当前显示器的窗口
+
+    switch (binding.get_name()) {
+        case 'switch-panels':
+        case 'switch-panels-backward':
+            // Switch between windows of all workspaces
+            windows = windows.filter(matchSkipTaskbar);
+            break;
+        case 'switch-group':
+        case 'switch-group-backward':
+            // Switch between windows of the same application
+            let focused = global.display.focus_window ? global.display.focus_window : windows[0];
+            windows = windows.filter(matchWmClass, focused.get_wm_class());
+            this._showAllWorkspaces = global.settings.get_boolean("alttab-switcher-show-all-workspaces");
+            if (!this._showAllWorkspaces) {
+                windows = windows.filter(matchWorkspace, global.workspace_manager.get_active_workspace());
+            }
+            break;
+        default:
+            // Switch between windows of current workspace
+            this._showAllWorkspaces = global.settings.get_boolean("alttab-switcher-show-all-workspaces");
+            if (!this._showAllWorkspaces) {
+                windows = windows.filter(matchWorkspace, global.workspace_manager.get_active_workspace());
+            }
+            break;
+    }
+
+    // Sort by user time
+    windows.sort(sortWindowsByUserTime);
+
+    return windows;
+}
+
+function AppSwitcher() {
+    this._init.apply(this, arguments);
+}
+
+AppSwitcher.prototype = {
+    _init: function (binding) {
+        this._initialDelayTimeoutId = null;
+        this._binding = binding;
+        this._windows = getWindowsForBinding(binding);
+
+        this._haveModal = false;
+        this._destroyed = false;
+        this._motionTimeoutId = 0;
+        this._currentIndex = this._windows.indexOf(global.display.focus_window);
+        if (this._currentIndex < 0) {
+            this._currentIndex = 0;
+        }
+        this._modifierMask = primaryModifier(binding.get_mask());
+
+        this._tracker = Cinnamon.WindowTracker.get_default();
+        this._windowManager = global.window_manager;
+
+        this._dcid = this._windowManager.connect('destroy', Lang.bind(this, this._windowDestroyed));
+        this._mcid = this._windowManager.connect('map', Lang.bind(this, this._activateSelected));
+
+        this._enforcePrimaryMonitor = global.settings.get_boolean("alttab-switcher-enforce-primary-monitor");
+        this._updateActiveMonitor();
+    },
+
+    _setupModal: function () {
+        this._haveModal = Main.pushModal(this.actor);
+        if (!this._haveModal) {
+            // Probably someone else has a pointer grab, try again with keyboard only
+            this._haveModal = Main.pushModal(this.actor, global.get_current_time(), Meta.ModalOptions.POINTER_ALREADY_GRABBED);
+        }
+        if (!this._haveModal)
+            this._failedGrabAction();
+        else {
+            // Initially disable hover so we ignore the enter-event if
+            // the switcher appears underneath the current pointer location
+            this._disableHover();
+
+            this.actor.connect('key-press-event', Lang.bind(this, this._keyPressEvent));
+            this.actor.connect('key-release-event', Lang.bind(this, this._keyReleaseEvent));
+            this.actor.connect('scroll-event', Lang.bind(this, this._scrollEvent));
+            this.actor.connect('button-press-event', Lang.bind(this, this.destroy));
+
+            // There's a race condition; if the user released Alt before
+            // we got the grab, then we won't be notified. (See
+            // https://bugzilla.gnome.org/show_bug.cgi?id=596695 for
+            // details) So we check now. (Have to do this after updating
+            // selection.)
+            let [x, y, mods] = global.get_pointer();
+            if (!(mods & this._modifierMask)) {
+                this._failedGrabAction();
+                return false;
+            }
+
+            // We delay showing the popup so that fast Alt+Tab users aren't
+            // disturbed by the popup briefly flashing.
+            let delay = global.settings.get_int("alttab-switcher-delay");
+            this._initialDelayTimeoutId = Mainloop.timeout_add(delay, Lang.bind(this, this._show));
+        }
+        return this._haveModal;
+    },
+
+    _popModal: function () {
+        if (this._haveModal) {
+            Main.popModal(this.actor);
+            this._haveModal = false;
+        }
+    },
+
+    _show: function () {
+        throw new Error("Abstract method _show not implemented");
+    },
+
+    _hide: function () {
+        throw new Error("Abstract method _hide not implemented");
+    },
+
+    _onDestroy: function () {
+        throw new Error("Abstract method _onDestroy not implemented");
+    },
+
+    _createList: function () {
+        throw new Error("Abstract method _createList not implemented");
+    },
+
+    _updateList: function () {
+        throw new Error("Abstract method _updateList not implemented");
+    },
+
+    _selectNext: function () {
+        throw new Error("Abstract method _selectNext not implemented");
+    },
+
+    _selectPrevious: function () {
+        throw new Error("Abstract method _selectPrevious not implemented");
+    },
+
+    _onWorkspaceSelected: function () {
+        throw new Error("Abstract method _onWorkspaceSelected not implemented");
+    },
+
+    _checkSwitchTime: function () {
+        return true;
+    },
+
+    _setCurrentWindow: function (window) {
+    },
+
+    _next: function () {
+        if (!this._windows)
+            return;
+
+        if (this._windows.length <= 1) {
+            this._currentIndex = 0;
+            this._updateList(0);
+        } else {
+            this.actor.set_reactive(false);
+            this._selectNext();
+            this.actor.set_reactive(true);
+        }
+        this._setCurrentWindow(this._windows[this._currentIndex]);
+    },
+
+    _previous: function () {
+        if (!this._windows)
+            return;
+
+        if (this._windows.length <= 1) {
+            this._currentIndex = 0;
+            this._updateList(0);
+        } else {
+            this.actor.set_reactive(false);
+            this._selectPrevious();
+            this.actor.set_reactive(true);
+        }
+        this._setCurrentWindow(this._windows[this._currentIndex]);
+    },
+
+    _select: function (index) {
+        if (!this._windows)
+            return;
+
+        this._currentIndex = index;
+        this._setCurrentWindow(this._windows[this._currentIndex]);
+    },
+
+    _updateActiveMonitor: function () {
+        this._activeMonitor = null;
+        if (!this._enforcePrimaryMonitor)
+            this._activeMonitor = Main.layoutManager.currentMonitor;
+        if (!this._activeMonitor)
+            this._activeMonitor = Main.layoutManager.primaryMonitor;
+
+        return this._activeMonitor;
+    },
+
+    _keyPressEvent: function (actor, event) {
+        let modifiers = Cinnamon.get_event_state(event);
+        let symbol = event.get_key_symbol();
+        let keycode = event.get_key_code();
+        // This relies on the fact that Clutter.ModifierType is the same as Gdk.ModifierType
+        let action = global.display.get_keybinding_action(keycode, modifiers);
+
+        this._disableHover();
+
+        // Switch workspace
+        if (modifiers & Clutter.ModifierType.CONTROL_MASK &&
+            (symbol === Clutter.KEY_Right || symbol === Clutter.KEY_Left)) {
+            if (this._switchWorkspace(symbol))
+                return true;
+        }
+
+        // Extra keys
+        switch (symbol) {
+            case Clutter.KEY_Escape:
+                // Esc -> Close switcher
+                this.destroy();
+                return true;
+
+            case Clutter.KEY_Return:
+            case Clutter.KEY_KP_Enter:
+                // Enter -> Select active window
+                this._activateSelected();
+                return true;
+
+            case Clutter.KEY_d:
+            case Clutter.KEY_D:
+                // D -> Show desktop
+                this._showDesktop();
+                return true;
+
+            case Clutter.KEY_Right:
+            case Clutter.KEY_Down:
+                // Right/Down -> navigate to next preview
+                if (this._checkSwitchTime())
+                    this._next();
+                return true;
+
+            case Clutter.KEY_Left:
+            case Clutter.KEY_Up:
+                // Left/Up -> navigate to previous preview
+                if (this._checkSwitchTime())
+                    this._previous();
+                return true;
+        }
+
+        // Default alt-tab
+        switch (action) {
+            case Meta.KeyBindingAction.SWITCH_GROUP:
+            case Meta.KeyBindingAction.SWITCH_WINDOWS:
+            case Meta.KeyBindingAction.SWITCH_PANELS:
+                if (this._checkSwitchTime()) {
+                    // shift -> backwards
+                    if (modifiers & Clutter.ModifierType.SHIFT_MASK)
+                        this._previous();
+                    else
+                        this._next();
+                }
+                return true;
+            case Meta.KeyBindingAction.SWITCH_GROUP_BACKWARD:
+            case Meta.KeyBindingAction.SWITCH_WINDOWS_BACKWARD:
+            case Meta.KeyBindingAction.SWITCH_PANELS_BACKWARD:
+                if (this._checkSwitchTime())
+                    this._previous();
+                return true;
+        }
+
+        return true;
+    },
+
+    _keyReleaseEvent: function (actor, event) {
+        let [x, y, mods] = global.get_pointer();
+        let state = mods & this._modifierMask;
+
+        if (state == 0) {
+            if (this._initialDelayTimeoutId !== 0)
+                this._currentIndex = (this._currentIndex + 1) % this._windows.length;
+            this._activateSelected();
+        }
+
+        return true;
+    },
+
+    _failedGrabAction: function () {
+        if (!["coverflow", "timeline"].includes(global.settings.get_string('alttab-switcher-style'))) {
+            this._keyReleaseEvent(null, null);
+        }
+    },
+
+    // allow navigating by mouse-wheel scrolling
+    _scrollEvent: function (actor, event) {
+        if (event.get_scroll_direction() == Clutter.ScrollDirection.SMOOTH)
+            return Clutter.EVENT_STOP;
+        if (this._checkSwitchTime()) {
+            actor.set_reactive(false);
+            if (event.get_scroll_direction() == Clutter.ScrollDirection.UP)
+                this._previous();
+            else if (event.get_scroll_direction() == Clutter.ScrollDirection.DOWN)
+                this._next();
+            actor.set_reactive(true);
+        }
+        return true;
+    },
+
+    _disableHover: function () {
+        this._mouseActive = false;
+
+        if (this._motionTimeoutId != 0)
+            Mainloop.source_remove(this._motionTimeoutId);
+
+        this._motionTimeoutId = Mainloop.timeout_add(DISABLE_HOVER_TIMEOUT, Lang.bind(this, this._mouseTimedOut));
+    },
+
+    _mouseTimedOut: function () {
+        this._motionTimeoutId = 0;
+        this._mouseActive = true;
+    },
+
+    _switchWorkspace: function (direction) {
+        if (global.workspace_manager.n_workspaces < 2)
+            return false;
+
+        let current = global.workspace_manager.get_active_workspace_index();
+
+        if (direction === Clutter.KEY_Left)
+            Main.wm.actionMoveWorkspaceLeft();
+        else if (direction === Clutter.KEY_Right)
+            Main.wm.actionMoveWorkspaceRight();
+        else
+            return false;
+
+        if (current === global.workspace_manager.get_active_workspace_index())
+            return false;
+
+        let workspace = global.workspace_manager.get_active_workspace();
+        this._onWorkspaceSelected(workspace);
+        return true;
+    },
+
+    _windowDestroyed: function (wm, actor) {
+        this._removeDestroyedWindow(actor.meta_window);
+    },
+
+    _removeDestroyedWindow: function (window) {
+        for (let i in this._windows) {
+            if (window == this._windows[i]) {
+                if (this._windows.length == 1)
+                    this.destroy();
+                else {
+                    this._windows.splice(i, 1);
+                    if (this._previews && this._previews[i]) {
+                        this._previews[i].destroy();
+                        this._previews.splice(i, 1);
+                    }
+                    if (i < this._currentIndex)
+                        this._currentIndex--;
+                    else
+                        this._currentIndex %= this._windows.length;
+
+                    this._updateList(0);
+                    this._setCurrentWindow(this._windows[this._currentIndex]);
+                }
+
+                return;
+            }
+        }
+    },
+
+    _activateSelected: function () {
+        const _window = this._windows[this._currentIndex]
+        const workspace_num = _window.get_workspace().index();
+        Main.activateWindow(_window, global.get_current_time(), workspace_num);
+        this._warpMouse = global.settings.get_boolean("alttab-switcher-warp-mouse-pointer");
+        if (this._warpMouse) {
+            const rect = _window.get_frame_rect();
+            const x = rect.x + rect.width / 2;
+            const y = rect.y + rect.height / 2;
+            this._pointer = Clutter.get_default_backend().get_default_seat().create_virtual_device(Clutter.InputDeviceType.POINTER_DEVICE);
+            this._pointer.notify_absolute_motion(global.get_current_time(), x, y);
+        }
+        if (!this._destroyed)
+            this.destroy();
+    },
+
+    _showDesktop: function () {
+        for (let i in this._windows) {
+            if (!this._windows[i].minimized)
+                this._windows[i].minimize();
+        }
+        this.destroy();
+    },
+
+    destroy: function () {
+        this._destroyed = true;
+        this._popModal();
+
+        if (this._initialDelayTimeoutId !== 0)
+            this._destroyActors();
+        else
+            this._hide();
+
+        if (this._initialDelayTimeoutId !== null && this._initialDelayTimeoutId > 0) {
+            Mainloop.source_remove(this._initialDelayTimeoutId);
+            this._initialDelayTimeoutId = 0;
+        }
+        this._onDestroy();
+
+        this._windows = null;
+        if (this._motionTimeoutId != 0) {
+            Mainloop.source_remove(this._motionTimeoutId);
+            this._motionTimeoutId = 0;
+        }
+
+        if (this._dcid > 0) {
+            this._windowManager.disconnect(this._dcid);
+            this._dcid = 0;
+        }
+
+        if (this._mcid > 0) {
+            this._windowManager.disconnect(this._mcid);
+            this._mcid = 0;
+        }
+    }
+};

+ 1010 - 0
usr/share/cinnamon/js/ui/appSwitcher/classicSwitcher.js

@@ -0,0 +1,1010 @@
+// -*- mode: js; js-indent-level: 4; indent-tabs-mode: nil -*-
+
+const Lang = imports.lang;
+
+const Clutter = imports.gi.Clutter;
+const St = imports.gi.St;
+const Meta = imports.gi.Meta;
+const Pango = imports.gi.Pango;
+const Cinnamon = imports.gi.Cinnamon;
+const Signals = imports.signals;
+const Mainloop = imports.mainloop;
+
+const AppSwitcher = imports.ui.appSwitcher.appSwitcher;
+const Main = imports.ui.main;
+const Tweener = imports.ui.tweener;
+
+const WindowUtils = imports.misc.windowUtils;
+
+const POPUP_SCROLL_TIME = 0.10; // seconds
+const POPUP_DELAY_TIMEOUT = 150; // milliseconds
+const POPUP_FADE_OUT_TIME = 0.1; // seconds
+
+const APP_ICON_HOVER_TIMEOUT = 200; // milliseconds
+
+const THUMBNAIL_DEFAULT_SIZE = 256;
+const THUMBNAIL_POPUP_TIME = 0; // milliseconds
+const THUMBNAIL_FADE_TIME = 0.1; // seconds
+
+const PREVIEW_DELAY_TIMEOUT = 0; // milliseconds
+var PREVIEW_SWITCHER_FADEOUT_TIME = 0.2; // seconds
+
+const iconSizes = [96, 64, 48];
+
+function mod(a, b) {
+    return (a + b) % b;
+}
+
+function ClassicSwitcher() {
+    this._init.apply(this, arguments);
+}
+
+ClassicSwitcher.prototype = {
+    __proto__: AppSwitcher.AppSwitcher.prototype,
+    
+    _init: function() {
+        AppSwitcher.AppSwitcher.prototype._init.apply(this, arguments);
+
+        this.actor = new Cinnamon.GenericContainer({ name: 'altTabPopup',
+                                                  reactive: true,
+                                                  visible: false });
+        
+        this._thumbnailTimeoutId = 0;
+        this.thumbnailsVisible = false;
+        this._displayPreviewTimeoutId = 0;
+
+        Main.uiGroup.add_actor(this.actor);
+
+        if (!this._setupModal())
+            return;
+            
+        let styleSettings = global.settings.get_string("alttab-switcher-style");
+        let features = styleSettings.split('+');
+        this._iconsEnabled = features.indexOf('icons') !== -1;
+        this._previewEnabled = features.indexOf('preview') !== -1;
+        this._thumbnailsEnabled = features.indexOf('thumbnails') !== -1;
+        if (!this._iconsEnabled && !this._previewEnabled && !this._thumbnailsEnabled)
+            this._iconsEnabled = true;
+
+        this._showThumbnails = this._thumbnailsEnabled && !this._iconsEnabled;
+        this._showArrows = this._thumbnailsEnabled && this._iconsEnabled;
+        
+        this._updateList(0);
+
+        this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
+        this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
+        this.actor.connect('allocate', Lang.bind(this, this._allocate));
+        
+        this._applist_act_id = 0;
+        this._applist_enter_id = 0;
+
+        // Need to force an allocation so we can figure out whether we
+        // need to scroll when selecting
+        this.actor.opacity = 0;
+        this.actor.show();
+        this.actor.get_allocation_box();
+    },
+
+    _getPreferredWidth: function (actor, forHeight, alloc) {
+        alloc.min_size = global.screen_width;
+        alloc.natural_size = global.screen_width;
+    },
+
+    _getPreferredHeight: function (actor, forWidth, alloc) {
+        alloc.min_size = global.screen_height;
+        alloc.natural_size = global.screen_height;
+    },
+
+    _allocate: function (actor, box, flags) {
+        let childBox = new Clutter.ActorBox();
+        let monitor = this._activeMonitor;
+
+        let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
+        let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
+        let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
+        let vPadding = this.actor.get_theme_node().get_vertical_padding();
+        let hPadding = leftPadding + rightPadding;
+
+        // Allocate the appSwitcher
+        // We select a size based on an icon size that does not overflow the screen
+        let [childMinHeight, childNaturalHeight] = this._appList.actor.get_preferred_height(monitor.width - hPadding);
+        let [childMinWidth, childNaturalWidth] = this._appList.actor.get_preferred_width(childNaturalHeight);
+        childBox.x1 = Math.max(monitor.x + leftPadding, monitor.x + Math.floor((monitor.width - childNaturalWidth) / 2));
+        childBox.x2 = Math.min(monitor.x + monitor.width - rightPadding, childBox.x1 + childNaturalWidth);
+        childBox.y1 = monitor.y + Math.floor((monitor.height - childNaturalHeight) / 2);
+        childBox.y2 = childBox.y1 + childNaturalHeight;
+        this._appList.actor.allocate(childBox, flags);
+
+        // Allocate the thumbnails
+        // We try to avoid overflowing the screen so we base the resulting size on
+        // those calculations
+        if (this._thumbnails && this._appIcons.length > 0) {
+            let icon = this._appIcons[this._currentIndex].actor;
+            let [posX, posY] = icon.get_transformed_position();
+            let thumbnailCenter = posX + icon.width / 2;
+            let [childMinWidth, childNaturalWidth] = this._thumbnails.actor.get_preferred_width(-1);
+            childBox.x1 = Math.max(monitor.x + leftPadding, Math.floor(thumbnailCenter - childNaturalWidth / 2));
+            if (childBox.x1 + childNaturalWidth > monitor.x + monitor.width - rightPadding) {
+                let offset = (childBox.x1 + childNaturalWidth) - (monitor.x + monitor.width - rightPadding);
+                childBox.x1 -= offset;
+            }
+
+            let spacing = this.actor.get_theme_node().get_length('spacing');
+
+            childBox.x2 = childBox.x1 +  childNaturalWidth;
+            childBox.y1 = this._appList.actor.allocation.y2 + spacing;
+            this._thumbnails.addClones(monitor.y + monitor.height - bottomPadding - childBox.y1);
+            let [childMinHeight, childNaturalHeight] = this._thumbnails.actor.get_preferred_height(-1);
+            childBox.y2 = childBox.y1 + childNaturalHeight;
+            this._thumbnails.actor.allocate(childBox, flags);
+        }
+    },
+
+    _show: function() {
+        Main.panelManager.panels.forEach(function(panel) { panel.actor.set_reactive(false); });
+        
+        this.actor.opacity = 255;
+        this._initialDelayTimeoutId = 0;
+        this._next();
+    },
+    
+    _hide: function() {
+        // window title and icon
+        if(this._windowTitle) {
+            this._windowTitle.hide();
+            this._applicationIconBox.hide();
+        }
+
+        // panels
+        Main.panelManager.panels.forEach(function(panel) { panel.actor.set_reactive(true); });
+
+        Tweener.addTween(this.actor, { opacity: 0,
+            time: POPUP_FADE_OUT_TIME,
+            transition: 'easeOutQuad',
+            onComplete: Lang.bind(this, this._destroyActors)
+        });
+    },
+
+    _destroyActors: function() {
+        Main.uiGroup.remove_actor(this.actor);
+        this.actor.destroy();
+    },
+
+    _updateList: function(direction) {
+        if(direction !== 0)
+            return;
+        
+        if (this._appList) {
+            if (this._applist_act_id !== 0) {
+                this._appList.disconnect(this._applist_act_id);
+                this._applist_act_id = 0;
+            }
+            if (this._applist_enter_id !== 0) {
+                this._appList.disconnect(this._applist_enter_id);
+                this._applist_enter_id = 0;
+            }
+            this._clearPreview();
+            this._destroyThumbnails();
+            this.actor.remove_actor(this._appList.actor);
+            this._appList.actor.destroy();
+        }
+        this._appList = new AppList(this._windows, this._showThumbnails, this._showArrows, this._activeMonitor);
+        this.actor.add_actor(this._appList.actor);
+        if (!this._iconsEnabled && !this._thumbnailsEnabled) {
+            this._appList.actor.hide();
+        }
+        this._applist_act_id = this._appList.connect('item-activated', Lang.bind(this, this._appActivated));
+        this._applist_enter_id = this._appList.connect('item-entered', Lang.bind(this, this._appEntered));
+        
+        this._appIcons = this._appList.icons;
+        this.actor.get_allocation_box();
+    },
+
+    _selectNext: function() {
+        if (this._currentIndex == this._windows.length - 1) {
+            this._currentIndex = 0;
+        } else {
+            this._currentIndex = this._currentIndex + 1;
+        }
+    },
+
+    _selectPrevious: function() {
+        if (this._currentIndex == 0) {
+            this._currentIndex = this._windows.length-1;
+        } else {
+            this._currentIndex = this._currentIndex - 1;
+        }
+    },
+
+    _onWorkspaceSelected: function() {
+        this._windows = AppSwitcher.getWindowsForBinding(this._binding);
+        this._currentIndex = 0;
+        this._updateList(0);
+        this._select(0);
+    },
+    
+    _setCurrentWindow: function(window) {
+        this._appList.highlight(this._currentIndex, false);
+        this._doWindowPreview();
+        this._destroyThumbnails();
+        
+        if (this._thumbnailTimeoutId != 0) {
+            Mainloop.source_remove(this._thumbnailTimeoutId);
+            this._thumbnailTimeoutId = 0;
+        }
+        
+        if (this._showArrows) {
+            this._thumbnailTimeoutId = Mainloop.timeout_add(
+                THUMBNAIL_POPUP_TIME, Lang.bind(this, function() {
+
+                    if (!this._thumbnails)
+                        this._createThumbnails();
+                    this._thumbnails.highlight(0, false);
+                    this._thumbnailTimeoutId = 0;
+            }));
+        }
+    },
+
+    _onDestroy: function() {
+        if (this._appList !== null) {
+            if (this._applist_act_id > 0) {
+                this._appList.disconnect(this._applist_act_id);
+                this._applist_act_id = 0;
+            }
+
+            if (this._applist_enter_id > 0) {
+                this._appList.disconnect(this._applist_enter_id);
+                this._applist_enter_id = 0;
+            }
+        }
+
+        if (this._thumbnailTimeoutId != 0) {
+            Mainloop.source_remove(this._thumbnailTimeoutId);
+            this._thumbnailTimeoutId = 0;
+        }
+        if (this._displayPreviewTimeoutId != 0) {
+            Mainloop.source_remove(this._displayPreviewTimeoutId);
+            this._displayPreviewTimeoutId = 0;
+        }
+    },
+
+    _appActivated : function(appSwitcher, n) {
+        this._activateSelected();
+    },
+
+    _appEntered : function(appSwitcher, n) {
+        if (!this._mouseActive)
+            return;
+
+        this._select(n);
+    },
+
+    _windowActivated : function(thumbnailList, n) {
+        this._activateSelected();
+    },
+    
+    _clearPreview: function() {
+        if (this._previewClones) {
+            for (let i = 0; i < this._previewClones.length; ++i) {
+                let clone = this._previewClones[i];
+                Tweener.addTween(clone, {
+                    opacity: 0,
+                    time: PREVIEW_SWITCHER_FADEOUT_TIME / 4,
+                    transition: 'linear',
+                    onCompleteScope: this,
+                    onComplete: function() {
+                        this.actor.remove_actor(clone);
+                        clone.destroy();
+                    }
+                });
+            }
+            this._previewClones = null;
+        }
+    },
+    
+    _doWindowPreview: function() {
+        if (!this._previewEnabled || this._windows.length < 1)
+        {
+            return;
+        }
+
+        // Use a cancellable timeout to avoid flickering effect when tabbing rapidly through the set.
+        if (this._displayPreviewTimeoutId) {
+            Mainloop.source_remove(this._displayPreviewTimeoutId);
+            this._displayPreviewTimeoutId = 0;
+        }
+        let delay = PREVIEW_DELAY_TIMEOUT;
+        this._displayPreviewTimeoutId = Mainloop.timeout_add(delay, Lang.bind(this, this._showWindowPreview));
+    },
+    
+    _showWindowPreview: function() {
+        this._displayPreviewTimeoutId = 0;
+
+        let childBox = new Clutter.ActorBox();
+
+        let lastClone = null;
+        let previewClones = [];
+        let window = this._windows[this._currentIndex];
+        let clones = WindowUtils.createWindowClone(window, 0, 0, true, false);
+        for (let i = 0; i < clones.length; i++) {
+            let clone = clones[i];
+            previewClones.push(clone.actor);
+            this.actor.add_actor(clone.actor);
+            let [width, height] = clone.actor.get_size();
+            childBox.x1 = clone.x;
+            childBox.x2 = clone.x + width;
+            childBox.y1 = clone.y;
+            childBox.y2 = clone.y + height;
+            clone.actor.allocate(childBox, 0);
+            clone.actor.lower(this._appList.actor);
+            if (lastClone) {
+                lastClone.lower(clone.actor);
+            }
+            lastClone = clone.actor;
+        }
+
+        this._clearPreview();
+        this._previewClones = previewClones;
+
+        if (!this._previewBackdrop) {
+            let backdrop = this._previewBackdrop = new St.Bin({style_class: 'switcher-preview-backdrop'});
+            this.actor.add_actor(backdrop);
+
+            // Make sure that the backdrop does not overlap the switcher.
+            backdrop.lower(this._appList.actor);
+            backdrop.lower(lastClone);
+            childBox.x1 = this.actor.x;
+            childBox.x2 = this.actor.x + this.actor.width;
+            childBox.y1 = this.actor.y;
+            childBox.y2 = this.actor.y + this.actor.height;
+            backdrop.allocate(childBox, 0);
+            backdrop.opacity = 0;
+            Tweener.addTween(backdrop,
+                            { opacity: 255,
+                            time: PREVIEW_SWITCHER_FADEOUT_TIME / 4,
+                            transition: 'linear'
+                            });
+        }
+    },
+
+    _destroyThumbnails : function() {
+        if (!this._thumbnails) {
+            return;
+        }
+        let thumbnailsActor = this._thumbnails.actor;
+        this._thumbnails = null;
+        this.actor.remove_actor(thumbnailsActor);
+        thumbnailsActor.destroy();
+        this.thumbnailsVisible = false;
+        
+    },
+
+    _createThumbnails : function() {
+        this._thumbnails = new ThumbnailList ([this._windows[this._currentIndex]], this._activeMonitor);
+        this._thumbnails.connect('item-activated', Lang.bind(this, this._windowActivated));
+
+        this.actor.add_actor(this._thumbnails.actor);
+
+        // Need to force an allocation so we can figure out whether we
+        // need to scroll when selecting
+        this._thumbnails.actor.get_allocation_box();
+
+        this._thumbnails.actor.opacity = 0;
+        Tweener.addTween(this._thumbnails.actor,
+                         { opacity: 255,
+                           time: THUMBNAIL_FADE_TIME,
+                           transition: 'easeOutQuad',
+                           onComplete: Lang.bind(this, function () { this.thumbnailsVisible = true; })
+                         });
+    }
+};
+
+
+
+function AppIcon(window, showThumbnail) {
+    this._init(window, showThumbnail);
+}
+
+AppIcon.prototype = {
+    _init: function(window, showThumbnail) {
+        this.window = window;
+        this.showThumbnail = showThumbnail;
+        let tracker = Cinnamon.WindowTracker.get_default();
+        this.app = tracker.get_window_app(window);
+        this.actor = new St.BoxLayout({ style_class: 'alt-tab-app',
+                                         vertical: true });
+        this.icon = null;
+        this._iconBin = new St.Bin();
+
+        this.actor.add(this._iconBin, { x_fill: false, y_fill: false } );
+        let title = window.get_title();
+        if (title) {
+            if (window.minimized) {
+                this.label = new St.Label({ text: "[" + title + "]"});               
+                let contrast_effect = new Clutter.BrightnessContrastEffect();                
+                contrast_effect.set_brightness_full(-0.5, -0.5, -0.5);
+                this._iconBin.add_effect(contrast_effect);                
+            }
+            else {
+                this.label = new St.Label({ text: title });    
+            }
+            
+            let bin = new St.Bin({ x_align: St.Align.MIDDLE });
+            bin.add_actor(this.label);
+            this.actor.add(bin);
+        }
+        else {
+            this.label = new St.Label({ text: this.app ? this.app.get_name() : window.title });
+            this.actor.add(this.label, { x_fill: false });
+        }
+    },
+
+    set_size: function(size) {
+        if (this.showThumbnail){
+            this.icon = new St.Widget();
+            let clones = WindowUtils.createWindowClone(this.window, size * global.ui_scale, size * global.ui_scale, true, true);
+            for (let i in clones) {
+                let clone = clones[i];
+                this.icon.add_actor(clone.actor);
+                // the following 2 lines are used when cloning without positions (param #4 = false)
+                //let [width, height] = clone.actor.get_size();
+                //clone.actor.set_position(Math.round((size - width) / 2), Math.round((size - height) / 2));
+                clone.actor.set_position(clone.x, clone.y);
+            }
+        } else {
+            this.icon = this.app ?
+                this.app.create_icon_texture_for_window(size, this.window) :
+                new St.Icon({ icon_name: 'application-default-icon',
+                              icon_type: St.IconType.FULLCOLOR,
+                              icon_size: size });
+        }
+        size *= global.ui_scale;
+        this._iconBin.set_size(size, size);
+        this._iconBin.child = this.icon;
+    }
+};
+
+function SwitcherList(squareItems, activeMonitor) {
+    this._init(squareItems, activeMonitor);
+}
+
+SwitcherList.prototype = {
+    _init : function(squareItems, activeMonitor) {
+        this.actor = new Cinnamon.GenericContainer({ style_class: 'switcher-list' });
+        this.actor.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
+        this.actor.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
+        this.actor.connect('allocate', Lang.bind(this, this._allocateTop));
+
+        // Here we use a GenericContainer so that we can force all the
+        // children except the separator to have the same width.
+        // TODO: Separator is gone, we could use an St.ScrollView now.
+        this._list = new Cinnamon.GenericContainer({ style_class: 'switcher-list-item-container' });
+        this._list.spacing = -1;
+        this._list.connect('style-changed', Lang.bind(this, function() {
+                                                        this._list.spacing = this._list.get_theme_node().get_length('spacing');
+                                                     }));
+
+        this._list.connect('get-preferred-width', Lang.bind(this, this._getPreferredWidth));
+        this._list.connect('get-preferred-height', Lang.bind(this, this._getPreferredHeight));
+        this._list.connect('allocate', Lang.bind(this, this._allocate));
+
+        this._clipBin = new St.Bin({style_class: 'cbin'});
+        this._clipBin.child = this._list;
+        this.actor.add_actor(this._clipBin);
+
+        this._leftGradient = new St.BoxLayout({style_class: 'thumbnail-scroll-gradient-left', vertical: true});
+        this._rightGradient = new St.BoxLayout({style_class: 'thumbnail-scroll-gradient-right', vertical: true});
+        this.actor.add_actor(this._leftGradient);
+        this.actor.add_actor(this._rightGradient);
+
+        // Those arrows indicate whether scrolling in one direction is possible
+        this._leftArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
+                                               pseudo_class: 'highlighted' });
+        this._leftArrow.connect('repaint', Lang.bind(this,
+            function() { _drawArrow(this._leftArrow, St.Side.LEFT); }));
+        this._rightArrow = new St.DrawingArea({ style_class: 'switcher-arrow',
+                                                pseudo_class: 'highlighted' });
+        this._rightArrow.connect('repaint', Lang.bind(this,
+            function() { _drawArrow(this._rightArrow, St.Side.RIGHT); }));
+
+        this.actor.add_actor(this._leftArrow);
+        this.actor.add_actor(this._rightArrow);
+
+        this._items = [];
+        this._highlighted = -1;
+        this._squareItems = squareItems;
+        this._minSize = 0;
+        this._scrollableRight = true;
+        this._scrollableLeft = false;
+        this._activeMonitor = activeMonitor;
+    },
+
+    _allocateTop: function(actor, box, flags) {
+        if (this._list.spacing === -1) {
+            this._list.spacing = this._list.get_theme_node().get_length('spacing');
+        }
+
+        let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
+        let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
+
+        let childBox = new Clutter.ActorBox();
+        let scrollable = this._minSize > box.x2 - box.x1;
+
+        this._clipBin.allocate(box, flags);
+
+        childBox.x1 = 0;
+        childBox.y1 = 0;
+        childBox.x2 = this._leftGradient.width;
+        childBox.y2 = this.actor.height;
+        this._leftGradient.allocate(childBox, flags);
+        this._leftGradient.opacity = (this._scrollableLeft && scrollable) ? 255 : 0;
+
+        childBox.x1 = (this.actor.allocation.x2 - this.actor.allocation.x1) - this._rightGradient.width;
+        childBox.y1 = 0;
+        childBox.x2 = childBox.x1 + this._rightGradient.width;
+        childBox.y2 = this.actor.height;
+        this._rightGradient.allocate(childBox, flags);
+        this._rightGradient.opacity = (this._scrollableRight && scrollable) ? 255 : 0;
+
+        let arrowWidth = Math.floor(leftPadding / 3);
+        let arrowHeight = arrowWidth * 2;
+        childBox.x1 = leftPadding / 2;
+        childBox.y1 = this.actor.height / 2 - arrowWidth;
+        childBox.x2 = childBox.x1 + arrowWidth;
+        childBox.y2 = childBox.y1 + arrowHeight;
+        this._leftArrow.allocate(childBox, flags);
+        this._leftArrow.opacity = this._leftGradient.opacity;
+
+        arrowWidth = Math.floor(rightPadding / 3);
+        arrowHeight = arrowWidth * 2;
+        childBox.x1 = this.actor.width - arrowWidth - rightPadding / 2;
+        childBox.y1 = this.actor.height / 2 - arrowWidth;
+        childBox.x2 = childBox.x1 + arrowWidth;
+        childBox.y2 = childBox.y1 + arrowHeight;
+        this._rightArrow.allocate(childBox, flags);
+        this._rightArrow.opacity = this._rightGradient.opacity;
+    },
+
+    addItem : function(item, label) {
+        let bbox = new St.Button({ style_class: 'item-box',
+                                   reactive: true });
+
+        bbox.set_child(item);
+        this._list.add_actor(bbox);
+
+        let n = this._items.length;
+        bbox.connect('clicked', Lang.bind(this, function() { this._onItemClicked(n); }));
+        bbox.connect('enter-event', Lang.bind(this, function() { this._onItemEnter(n); }));
+
+        bbox.label_actor = label;
+
+        this._items.push(bbox);
+    },
+
+    _onItemClicked: function (index) {
+        this._itemActivated(index);
+    },
+
+    _onItemEnter: function (index) {
+        this._itemEntered(index);
+    },
+
+    highlight: function(index, justOutline) {
+        if (this._highlighted != -1) {
+            this._items[this._highlighted].remove_style_pseudo_class('outlined');
+            this._items[this._highlighted].remove_style_pseudo_class('selected');
+        }
+
+        this._highlighted = index;
+
+        if (this._highlighted != -1) {
+            if (justOutline)
+                this._items[this._highlighted].add_style_pseudo_class('outlined');
+            else
+                this._items[this._highlighted].add_style_pseudo_class('selected');
+        }
+
+        let [absItemX, absItemY] = this._items[index].get_transformed_position();
+        let [result, posX, posY] = this.actor.transform_stage_point(absItemX, 0);
+        let [containerWidth, containerHeight] = this.actor.get_transformed_size();
+        if (posX + this._items[index].get_width() > containerWidth)
+            this._scrollToRight();
+        else if (posX < 0)
+            this._scrollToLeft();
+
+    },
+
+    _scrollToLeft : function() {
+        let x = this._items[this._highlighted].allocation.x1;
+        this._scrollableRight = true;
+        Tweener.addTween(this._list, { anchor_x: x,
+                                        time: POPUP_SCROLL_TIME,
+                                        transition: 'easeOutQuad',
+                                        onComplete: Lang.bind(this, function () {
+                                                                        if (this._highlighted == 0) {
+                                                                            this._scrollableLeft = false;
+                                                                            this.actor.queue_relayout();
+                                                                        }
+                                                             })
+                        });
+    },
+
+    _scrollToRight : function() {
+        this._scrollableLeft = true;
+        let monitor = this._activeMonitor;
+        let padding = this.actor.get_theme_node().get_horizontal_padding();
+        let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
+        let x = this._items[this._highlighted].allocation.x2 - monitor.width + padding + parentPadding;
+        Tweener.addTween(this._list, { anchor_x: x,
+                                        time: POPUP_SCROLL_TIME,
+                                        transition: 'easeOutQuad',
+                                        onComplete: Lang.bind(this, function () {
+                                                                        if (this._highlighted == this._items.length - 1) {
+                                                                            this._scrollableRight = false;
+                                                                            this.actor.queue_relayout();
+                                                                        }
+                                                             })
+                        });
+    },
+
+    _itemActivated: function(n) {
+        this.emit('item-activated', n);
+    },
+
+    _itemEntered: function(n) {
+        this.emit('item-entered', n);
+    },
+
+    _maxChildWidth: function () {
+        let maxChildMin = 0;
+        let maxChildNat = 0;
+
+        if (this._items.length > 0) {
+            return this._items[0].get_preferred_width(-1);
+        }
+
+        return [0, 0]
+    },
+
+    _getPreferredWidth: function (actor, forHeight, alloc) {
+        let [maxChildMin, maxChildNat] = this._maxChildWidth();
+
+        let totalSpacing = this._list.spacing * Math.max(1, (this._items.length - 1));
+        alloc.min_size = this._items.length * maxChildMin + totalSpacing;
+        alloc.natural_size = alloc.min_size;
+        this._minSize = alloc.min_size;
+    },
+
+    _getPreferredHeight: function (actor, forWidth, alloc) {
+        let maxChildMin = 0;
+        let maxChildNat = 0;
+
+        for (let i = 0; i < this._items.length; i++) {
+            let [childMin, childNat] = this._items[i].get_preferred_height(-1);
+            maxChildMin = Math.max(childMin, maxChildMin);
+            maxChildNat = Math.max(childNat, maxChildNat);
+        }
+
+        if (this._squareItems) {
+            let [childMin, childNat] = this._maxChildWidth();
+            maxChildMin = Math.max(childMin, maxChildMin);
+            maxChildNat = maxChildMin;
+        }
+
+        alloc.min_size = maxChildMin;
+        alloc.natural_size = maxChildNat;
+    },
+
+    _allocate: function (actor, box, flags) {
+        let childHeight = box.y2 - box.y1;
+
+        let [maxChildMin, maxChildNat] = this._maxChildWidth();
+        let totalSpacing = this._list.spacing * (this._items.length - 1);
+
+        let childWidth = Math.floor(Math.max(0, box.x2 - box.x1 - totalSpacing) / this._items.length);
+
+        let x = 0;
+        let children = this._list.get_children();
+        let childBox = new Clutter.ActorBox();
+
+        let monitor = this._activeMonitor;
+        let parentRightPadding = this.actor.get_parent().get_theme_node().get_padding(St.Side.RIGHT);
+        if (this.actor.allocation.x2 == monitor.x + monitor.width - parentRightPadding) {
+            if (this._squareItems)
+                childWidth = childHeight;
+            else {
+                let [childMin, childNat] = children[0].get_preferred_width(childHeight);
+                childWidth = childMin;
+            }
+        }
+
+        for (let i = 0; i < children.length; i++) {
+            if (this._items.indexOf(children[i]) != -1) {
+                let [childMin, childNat] = children[i].get_preferred_height(childWidth);
+                let vSpacing = (childHeight - childNat) / 2;
+                childBox.x1 = x;
+                childBox.y1 = vSpacing;
+                childBox.x2 = x + childWidth;
+                childBox.y2 = childBox.y1 + childNat;
+                children[i].allocate(childBox, flags);
+
+                x += this._list.spacing + childWidth;
+            } else {
+                // Something else, eg, AppList's arrows;
+                // we don't allocate it.
+            }
+        }
+
+        let leftPadding = this.actor.get_theme_node().get_padding(St.Side.LEFT);
+        let rightPadding = this.actor.get_theme_node().get_padding(St.Side.RIGHT);
+        let topPadding = this.actor.get_theme_node().get_padding(St.Side.TOP);
+        let bottomPadding = this.actor.get_theme_node().get_padding(St.Side.BOTTOM);
+
+        // Clip the area for scrolling
+        this._clipBin.set_clip(0, -topPadding, (this.actor.allocation.x2 - this.actor.allocation.x1) - leftPadding - rightPadding, this.actor.height + bottomPadding);
+    }
+};
+
+Signals.addSignalMethods(SwitcherList.prototype);
+
+
+function AppList() {
+    this._init.apply(this, arguments);
+}
+
+AppList.prototype = {
+    __proto__ : SwitcherList.prototype,
+
+    _init : function(windows, showThumbnails, showArrows, activeMonitor) {
+        SwitcherList.prototype._init.call(this, true, activeMonitor);
+
+        // Construct the AppIcons, add to the popup
+        let activeWorkspace = global.workspace_manager.get_active_workspace();
+        let workspaceIcons = [];
+        let otherIcons = [];
+        for (let i = 0; i < windows.length; i++) {
+            workspaceIcons.push(new AppIcon(windows[i], showThumbnails));
+        }
+
+        this.icons = [];
+        this._arrows = [];
+        for (let i = 0; i < workspaceIcons.length; i++)
+            this._addIcon(workspaceIcons[i]);
+        if (workspaceIcons.length > 0 && otherIcons.length > 0)
+            this.addSeparator();
+        for (let i = 0; i < otherIcons.length; i++)
+            this._addIcon(otherIcons[i]);
+
+        this._curApp = -1;
+        this._iconSize = 0;
+        this._showArrows = showArrows;
+        this._mouseTimeOutId = 0;
+        this._activeMonitor = activeMonitor;
+    },
+
+    _getPreferredHeight: function (actor, forWidth, alloc) {
+        if (this._items.length < 1) {
+            alloc.min_size = alloc.natural_size = 32;
+            return;
+        }
+        let j = 0;
+        while(this._items.length > 1 && this._items[j].style_class != 'item-box') {
+                j++;
+        }
+        let themeNode = this._items[j].get_theme_node();
+        let iconPadding = themeNode.get_horizontal_padding();
+        let iconBorder = themeNode.get_border_width(St.Side.LEFT) + themeNode.get_border_width(St.Side.RIGHT);
+        let [iconMinHeight, iconNaturalHeight] = this.icons[j].label.get_preferred_height(-1);
+        let iconSpacing = iconNaturalHeight + iconPadding + iconBorder;
+        let totalSpacing = this._list.spacing * (this._items.length - 1);
+
+        // We just assume the whole screen here due to weirdness happing with the passed width
+        let parentPadding = this.actor.get_parent().get_theme_node().get_horizontal_padding();
+        let availWidth = this._activeMonitor.width - parentPadding - this.actor.get_theme_node().get_horizontal_padding();
+        let height = 0;
+
+        for(let i =  0; i < iconSizes.length; i++) {
+                this._iconSize = iconSizes[i];
+                height = (iconSizes[i] * global.ui_scale) + iconSpacing;
+                let w = height * this._items.length + totalSpacing;
+                if (w <= availWidth)
+                        break;
+        }
+        if (this._items.length == 1) {
+            this._iconSize = iconSizes[0];
+            height = (iconSizes[0] * global.ui_scale) + iconSpacing;
+        }
+
+        for(let i = 0; i < this.icons.length; i++) {
+            if (this.icons[i].icon != null)
+                break;
+            this.icons[i].set_size(this._iconSize);
+        }
+
+        alloc.min_size = height;
+        alloc.natural_size = height;
+    },
+
+    _allocate: function (actor, box, flags) {
+        // Allocate the main list items
+        SwitcherList.prototype._allocate.call(this, actor, box, flags);
+
+        if (this._showArrows) {
+            let arrowHeight = Math.floor(this.actor.get_theme_node().get_padding(St.Side.BOTTOM) / 3);
+            let arrowWidth = arrowHeight * 2;
+
+            // Now allocate each arrow underneath its item
+            let childBox = new Clutter.ActorBox();
+            for (let i = 0; i < this._items.length; i++) {
+                let itemBox = this._items[i].allocation;
+                childBox.x1 = Math.floor(itemBox.x1 + (itemBox.x2 - itemBox.x1 - arrowWidth) / 2);
+                childBox.x2 = childBox.x1 + arrowWidth;
+                childBox.y1 = itemBox.y2 + arrowHeight;
+                childBox.y2 = childBox.y1 + arrowHeight;
+                this._arrows[i].allocate(childBox, flags);
+            }
+        }
+    },
+
+    // We override SwitcherList's _onItemEnter method to delay
+    // activation when the thumbnail list is open
+    _onItemEnter: function (index) {
+        if (this._mouseTimeOutId != 0)
+            Mainloop.source_remove(this._mouseTimeOutId);
+        this._itemEntered(index);
+    },
+
+    _enterItem: function(index) {
+        let [x, y, mask] = global.get_pointer();
+        let pickedActor = global.stage.get_actor_at_pos(Clutter.PickMode.ALL, x, y);
+        if (this._items[index].contains(pickedActor))
+            this._itemEntered(index);
+    },
+
+    // We override SwitcherList's highlight() method to also deal with
+    // the AppList->ThumbnailList arrows.
+    highlight : function(n, justOutline) {
+        if (this._curApp != -1) {
+            this._arrows[this._curApp].hide();
+        }
+        
+        SwitcherList.prototype.highlight.call(this, n, justOutline);
+        this._curApp = n;
+ 
+        if (n != -1 && this._showArrows) {
+            this._arrows[n].show();
+        }
+    },
+
+    _addIcon : function(appIcon) {
+        this.icons.push(appIcon);
+        this.addItem(appIcon.actor, appIcon.label);
+
+        let n = this._arrows.length;
+        let arrow = new St.DrawingArea({ style_class: 'switcher-arrow' });
+        arrow.connect('repaint', function() { _drawArrow(arrow, St.Side.BOTTOM); });
+        this._list.add_actor(arrow);
+        this._arrows.push(arrow);
+        arrow.hide();
+    }
+};
+
+function ThumbnailList(windows, activeMonitor) {
+    this._init(windows, activeMonitor);
+}
+
+ThumbnailList.prototype = {
+    __proto__ : SwitcherList.prototype,
+
+    _init : function(windows, activeMonitor) {
+        SwitcherList.prototype._init.call(this, false, activeMonitor);
+
+        let activeWorkspace = global.workspace_manager.get_active_workspace();
+
+        this._labels = new Array();
+        this._thumbnailBins = new Array();
+        this._clones = new Array();
+        this._windows = windows;
+
+        for (let i = 0; i < windows.length; i++) {
+            let box = new St.BoxLayout({ style_class: 'thumbnail-box',
+                                         vertical: true });
+
+            let bin = new St.Bin({ style_class: 'thumbnail' });
+
+            box.add_actor(bin);
+            this._thumbnailBins.push(bin);
+
+            let title = windows[i].get_title();
+            if (title) {
+                let name = new St.Label({ text: title });
+                // St.Label doesn't support text-align so use a Bin
+                let bin = new St.Bin({ x_align: St.Align.MIDDLE });
+                this._labels.push(bin);
+                bin.add_actor(name);
+                box.add_actor(bin);
+
+                this.addItem(box, name);
+            } else {
+                this.addItem(box, null);
+            }
+
+        }
+    },
+
+    addClones : function (availHeight) {
+        if (!this._thumbnailBins.length)
+            return;
+        let totalPadding = this._items[0].get_theme_node().get_horizontal_padding() + this._items[0].get_theme_node().get_vertical_padding();
+        totalPadding += this.actor.get_theme_node().get_horizontal_padding() + this.actor.get_theme_node().get_vertical_padding();
+        let [labelMinHeight, labelNaturalHeight] = this._labels.length > 0 ?
+            this._labels[0].get_preferred_height(-1) : [0, 0];
+        let spacing = this._items[0].child.get_theme_node().get_length('spacing');
+
+        availHeight = Math.min(availHeight - labelNaturalHeight - totalPadding - spacing, THUMBNAIL_DEFAULT_SIZE * global.ui_scale);
+        let binHeight = availHeight + this._items[0].get_theme_node().get_vertical_padding() + this.actor.get_theme_node().get_vertical_padding() - spacing;
+        binHeight = Math.min(THUMBNAIL_DEFAULT_SIZE * global.ui_scale, binHeight);
+
+        for (let i = 0; i < this._thumbnailBins.length; i++) {
+            let metaWindow = this._windows[i];
+            let container = new St.Widget();
+            let clones = WindowUtils.createWindowClone(metaWindow, availHeight, availHeight, true, true);
+            for (let j = 0; j < clones.length; j++) {
+              let clone = clones[j];
+              container.add_actor(clone.actor);
+              clone.actor.set_position(clone.x, clone.y);
+            }
+            this._thumbnailBins[i].set_height(binHeight);
+            this._thumbnailBins[i].add_actor(container);
+            this._clones.push(container);
+        }
+
+        // Make sure we only do this once
+        this._thumbnailBins = new Array();
+    }
+};
+
+function _drawArrow(area, side) {
+    let themeNode = area.get_theme_node();
+    let borderColor = themeNode.get_border_color(side);
+    let bodyColor = themeNode.get_foreground_color();
+
+    let [width, height] = area.get_surface_size ();
+    let cr = area.get_context();
+
+    cr.setLineWidth(1.0);
+    Clutter.cairo_set_source_color(cr, borderColor);
+
+    switch (side) {
+    case St.Side.TOP:
+        cr.moveTo(0, height);
+        cr.lineTo(Math.floor(width * 0.5), 0);
+        cr.lineTo(width, height);
+        break;
+
+    case St.Side.BOTTOM:
+        cr.moveTo(width, 0);
+        cr.lineTo(Math.floor(width * 0.5), height);
+        cr.lineTo(0, 0);
+        break;
+
+    case St.Side.LEFT:
+        cr.moveTo(width, height);
+        cr.lineTo(0, Math.floor(height * 0.5));
+        cr.lineTo(width, 0);
+        break;
+
+    case St.Side.RIGHT:
+        cr.moveTo(0, 0);
+        cr.lineTo(width, Math.floor(height * 0.5));
+        cr.lineTo(0, height);
+        break;
+    }
+
+    cr.strokePreserve();
+
+    Clutter.cairo_set_source_color(cr, bodyColor);
+    cr.fill();
+
+    cr.$dispose();
+}

+ 1 - 0
到 system-sleep 的链接

@@ -0,0 +1 @@
+/usr/lib/systemd/system-sleep