通过 wine 完美运行最新版网易云音乐

虽然网易云音乐已经推出了 linux 版,但是诸如本地歌曲扫描、心动模式等功能有所缺失,并且更新相对较慢,因此这里介绍一种使用 wine 完美运行网易云音乐的方法

目前兼容情况

  • 主界面正常打开,可以账号密码登录、扫码登录、第三方登录,保存登录信息
  • dock栏及托盘图标显示正常,托盘图标可以左右键点击
  • 中文字体显示正常,无乱码zai
  • 可以扫描磁盘上其他文件夹内的歌曲并识别,并且重启后无需重新识别
  • MV可以正常播放,mini模式和桌面歌词均显示正常

目前已知兼容问题

  • 由于上游软件 wine 的影响,wine 中运行的所有软件无法在非相同分辨率的第二屏中运行

具体效果图可以拖到最后,推荐在 deepin 系统中使用

安装 wine

开启 i386 支持

sudo dpkg --add-architecture i386

安装公钥

wget -nc https://dl.winehq.org/wine-builds/Release.key
wget -nc https://dl.winehq.org/wine-builds/winehq.key
sudo apt-key add Release.key
sudo apt-key add winehq.key

需要注意的是,wine 自 2018-12-19 更改了公钥,因此这里安装了两个

添加仓库

版本号 命令
Ubuntu 18.10 sudo apt-add-repository ‘deb [https://dl.winehq.org/wine-builds/ubuntu/](https://dl.winehq.org/wine-builds/ubuntu/) cosmic main’
Ubuntu 18.04 / Linux Mint 19.x sudo apt-add-repository ‘deb [https://dl.winehq.org/wine-builds/ubuntu/](https://dl.winehq.org/wine-builds/ubuntu/) bionic main’
Ubuntu 16.04 / Linux Mint 18.x sudo apt-add-repository ‘deb [https://dl.winehq.org/wine-builds/ubuntu/](https://dl.winehq.org/wine-builds/ubuntu/) xenial main’
Ubuntu 14.04 / Linux Mint 17.x sudo apt-add-repository ‘deb [https://dl.winehq.org/wine-builds/ubuntu/](https://dl.winehq.org/wine-builds/ubuntu/) trusty main’
Debain 8 echo “deb [https://dl.winehq.org/wine-builds/debian/](https://dl.winehq.org/wine-builds/debian/) jessie main” | sudo tee -a /etc/apt/sources.list.d/winehq.conf
Debain 9 echo “deb [https://dl.winehq.org/wine-builds/debian/](https://dl.winehq.org/wine-builds/debian/) stretch main” | sudo tee -a /etc/apt/sources.list.d/winehq.conf
Debain 10 echo “deb [https://dl.winehq.org/wine-builds/debian/](https://dl.winehq.org/wine-builds/debian/) buster main” | sudo tee -a /etc/apt/sources.list.d/winehq.conf

更新仓库

sudo apt-get update

安装软件包

sudo apt install --install-recommends winehq-devel

当然也可以选择安装稳定版

sudo apt install --install-recommends winehq-stable

个人比较喜欢开发版

配置 wine

首次启动

winecfg

首次启动会创建容器,提示要求安装扩展包,按照提示安装即可

但是由于总所周知的原因,安装非常缓慢甚至会失败,但是这并不影响正常使用

修改 windows 版本

网易云音乐只能在 Windows XP 容器中运行

e9be862e7ada445dc771a8d822b44db9.png

安装中文字体

你可以从 windows 文件夹中拷贝字体目录到 wine 目录中,也可以下载我从 windows 10 中 Fonts 文件夹内打好的字体包

sudo apt-get install tar
wget https://uploads.limstash.com/fonts.tar.gz
tar -xvzf fonts.tar.gz
rm -rf ~/.wine/Windows/Fonts
mv Fonts ~/.wine/Windows/Fonts

再次运行 winecfg 在 [显示] 选项卡中可以看到中文字体显示已经正常

a9f87b6fb4227de1304f9af54cdc19e8.png

关闭允许桌面管理器装饰窗口

运行 winecfg 在 [显示] 选项卡中取消勾选 [允许窗口管理器装饰窗口] 以修复网易云音乐最大化后超出屏幕边界的问题

调整屏幕分辨率

将屏幕分辨率调整至 105 以上,以修复托盘图标无法点击的问题(在屏幕分辨率大于一定值后即可点击,但不确定具体为多少,可以自行尝试)

f924343e37b560379910bc8894ff0b79.png

设置语言编码

sudo cd /usr/bin
sudo mkdir wine-run
sudo mv wine wine-run
sudo vi wine

按下 i 写入

#!/bin/bash

export LANG=zh_CN.UTF-8
export LANGUAGE=zh_CN:zh
export LC_CTYPE="zh_CN.UTF-8"
export LC_NUMERIC="zh_CN.UTF-8"
export LC_TIME="zh_CN.UTF-8"
export LC_COLLATE="zh_CN.UTF-8"
export LC_MONETARY="zh_CN.UTF-8"
export LC_MESSAGES="zh_CN.UTF-8"
export LC_PAPER="zh_CN.UTF-8"
export LC_NAME="zh_CN.UTF-8"
export LC_ADDRESS="zh_CN.UTF-8"
export LC_TELEPHONE="zh_CN.UTF-8"
export LC_MEASUREMENT="zh_CN.UTF-8"
export LC_IDENTIFICATION="zh_CN.UTF-8"
export LC_ALL="zh_CN.UTF-8"

/usr/bin/wine-run/wine "$@"

然后按 Esc 退出编辑模式,按 ZZ 保存

如果在 Ubuntu 中使用 Fcitx 输入法可能会遇到中文输入法问题,请在文件末尾附加一段

export GTK_IM_MODULE=fcitx
export QT_IM_MODULE=fcitx
export XMODIFIERS=@im=fcitx

/usr/bin/wine-run/wine "$@"

赋予可执行权限

sudo chmod +x ./wine

字体平滑显示

安装 winetricks

wget https://raw.githubusercontent.com/Winetricks/winetricks/master/src/winetricks
sudo chmod +x ./winetricks

配置 fontsmooth=rgb

./winetricks settings fontsmooth=rgb

安装网易云音乐

下载安装包

wget https://d1.music.126.net/dmusic/cloudmusicsetup_2.5.2.197409.exe
wine cloudmusicsetup_2.5.2.197409.exe

当然也可以自己到官网下载最新的安装包,在这篇文章发出的时候安装包最新为 2.5.2,使用

wine 安装包文件名

即可安装

禁用缩放

之前设置了屏幕分辨率以解决托盘图标问题,在 设置 > 常规 > 高清屏适配 中勾选 “禁用系统缩放比率”

722faa20f9940f4ed92c18ef8672286a.png

更改字体

在 设置 > 字体 > 常规 中将字体更改为微软雅黑

a6a6085edf0dcbf781566316c52f9a86.md.png

黑边框及边框阴影

由于上游软件原因,会出现一个无法跟随移动的黑边框,在 deepin 开启窗口特效后体现为边框阴影

在启动软件时通过劫持边框窗口的方式可以去掉主程序的边框,但后期弹出的登录界面无法去除

当然登录一般情况下你只需要登录一次

sudo apt-get install g++ git libx11-dev
git clone https://gitee.com/limstash/wine_cloudmusic.git
cd wine_cloudmusic
g++ disable_cloudmusic_shadows.cpp -o disable_cloudmusic_shadows -lX11
cp disable_cloudmusic_shadows ~/.wine

那么每次运行网易云音乐后只需要再运行 “disable_cloudmusic_shadows` 即可关闭黑边框

vi ~/.local/share/applications/wine/Programs/网易云音乐.desktop

将其中的 Exec 项目改为

Exec=/bin/bash /home/你的用户名/.wine/start.sh

可以通过添加一行使启动项出现在音乐栏中

Categories=Music

新建一个启动脚本

vi ~/.wine/start.sh

写入

#!/bin/bash
env WINEPREFIX="/home/你的用户名/.wine" wine "C:\\Windows\\Command\\Start.exe" "C:\\Program Files (x86)\\Netease\\Cloudmusic\\cloudmusic.exe"
sleep 10
/home/你的用户名/.wine/disable_cloudmusic_shadows

赋予可执行权限

sudo chmod +x ~/.wine/start.sh

效果图

全屏显示

df5bd1c4aee1864d520d7b1c8076d292.md.png

播放器与桌面歌词

9e9a367a6f62bedce41c96c55b893285.md.png

识别已下载的歌曲

f9298769bd01afcbf99195ddcdd9c5cb.md.png

Mini 模式

e48976fdba6d457b501cf248f6837a2f.md.png

登录

0c7c2171fbd5e8e8c2e4a09ad7f9e03a.md.png

MV 播放

1d83981208614cbc0b9176904a908213.md.png

本文同步发布于 deepin

源码

/**
 * https://www.cnblogs.com/Seiyagoo/p/3496945.html
 * https://blog.csdn.net/ice__snow/article/details/79979298
 * https://blog.csdn.net/lvbian/article/details/23031635
 * 
 * This code is collected and generalized by hytzongxuan (hytzongxuan@gmail.com)
 */

#include <X11/Xlib.h>
#include <X11/Xatom.h>
#include <sys/types.h>
#include <dirent.h>
#include <iostream>
#include <cstring>
#include <cstdio>
#include <list>
#include <map>

#define BUF_SIZE 1024
using namespace std;

string PID[50];
int tot;

class WindowsMatchingPid
{
  public:
    WindowsMatchingPid(Display *display, Window wRoot, unsigned long pid)
        : _display(display), _pid(pid)
    {
        _atomPID = XInternAtom(display, "_NET_WM_PID", True);
        if (_atomPID == None)
        {
            cout << "No such atom" << endl;
            return;
        }

        search(wRoot);
    }

    const list<Window> &result() const { return _result; }

  private:
    unsigned long _pid;
    Atom _atomPID;
    Display *_display;
    list<Window> _result;

    void search(Window w)
    {
        Atom type;
        int format;
        unsigned long nItems;
        unsigned long bytesAfter;
        unsigned char *propPID = 0;
        if (Success == XGetWindowProperty(_display, w, _atomPID, 0, 1, False, XA_CARDINAL,
                                          &type, &format, &nItems, &bytesAfter, &propPID))
        {
            if (propPID != 0)
            {
                if (_pid == *((unsigned long *)propPID))
                    _result.push_back(w);

                XFree(propPID);
            }
        }

        Window wRoot;
        Window wParent;
        Window *wChild;
        unsigned nChildren;
        if (0 != XQueryTree(_display, w, &wRoot, &wParent, &wChild, &nChildren))
        {
            for (unsigned i = 0; i < nChildren; i++)
                search(wChild[i]);
        }
    }
};

void getPidByName(char *task_name)
{
    DIR *dir;
    struct dirent *ptr;
    FILE *fp;
    char filepath[50];
    char cur_task_name[50];
    char buf[BUF_SIZE];

    dir = opendir("/proc");

    if (NULL != dir)
    {
        while ((ptr = readdir(dir)) != NULL)
        {

            if ((strcmp(ptr->d_name, ".") == 0) || (strcmp(ptr->d_name, "..") == 0))
                continue;
            if (DT_DIR != ptr->d_type)
                continue;

            sprintf(filepath, "/proc/%s/status", ptr->d_name);
            fp = fopen(filepath, "r");

            if (NULL != fp)
            {
                if (fgets(buf, BUF_SIZE - 1, fp) == NULL)
                {
                    fclose(fp);
                    continue;
                }
                sscanf(buf, "%*s %s", cur_task_name);

                if (!strcmp(task_name, cur_task_name))
                {
                    PID[++tot] = ptr->d_name;
                }

                fclose(fp);
            }
        }
        closedir(dir);
    }
}

struct CloudMusicWindow
{
    Window window;
    int height, width;

    CloudMusicWindow() {}
    CloudMusicWindow(Window _win, int _h, int _w)
    {
        window = _win;
        height = _h;
        width = _w;
    }
};

CloudMusicWindow cloudmusicwindow[50];
map<pair<int, int>, int> Map;
int window_cnt;

void findWindow(string x)
{
    int pid = atoi(x.c_str());

    Display *display = XOpenDisplay(0);

    WindowsMatchingPid match(display, XDefaultRootWindow(display), pid);

    const list<Window> &result = match.result();
    for (list<Window>::const_iterator it = result.begin(); it != result.end(); it++)
    {
        XWindowAttributes attrs;
        XGetWindowAttributes(display, (*it), &attrs);

        Map[make_pair(attrs.height, attrs.width)]++;
        cloudmusicwindow[++window_cnt] = CloudMusicWindow((*it), attrs.height, attrs.width);
    }
}

int main(int argc, char **argv)
{
    Display *display;
    Window window;
    XEvent event;
    int s;

    display = XOpenDisplay(NULL);
    if (display == NULL)
    {
        fprintf(stderr, "Cannot open display\n");
        exit(1);
    }

    getPidByName("cloudmusic.exe");

    for (register int i = 1; i <= tot; i++)
    {
        findWindow(PID[i]);
    }

    s = DefaultScreen(display);

    window = XCreateSimpleWindow(display, RootWindow(display, s), 10, 10, 1, 1, 1,
                                 BlackPixel(display, s), WhitePixel(display, s));

    XSelectInput(display, window, ExposureMask | KeyPressMask);

    XMapWindow(display, window);

    for (register int i = 1; i <= window_cnt; i++)
    {
        if (Map[make_pair(cloudmusicwindow[i].height, cloudmusicwindow[i].width)] == 2)
        {
            XReparentWindow(display, cloudmusicwindow[i].window, window, 1, 1);
        }
    }

    XCloseDisplay(display);
    return 0;
}