• Home
Name Date Size #Lines LOC

..--

AppScope/12-May-2024-2423

common/12-May-2024-2,1491,383

entry/12-May-2024-10,1449,099

feature/ippPrint/12-May-2024-2,7291,763

figures/12-May-2024-

hvigor/12-May-2024-98

signature/12-May-2024-

.gitignoreD12-May-2024110 99

LICENSED12-May-20249.9 KiB177150

OAT.xmlD12-May-20245.1 KiB7727

README.en.mdD12-May-20241,018 3119

README.mdD12-May-202415.6 KiB426357

build-profile.json5D12-May-2024574 3635

gradlewD12-May-20241.1 KiB3733

hvigorfile.jsD12-May-2024782 171

hvigorwD12-May-20241.4 KiB4928

hvigorw.batD12-May-20241.5 KiB6547

oh-package-lock.json5D12-May-2024434 1313

oh-package.json5D12-May-2024210 1111

README.en.md

1# PrintSpooler<a name="EN-US_TOPIC_0000001103330836"></a>
2
3-   [Introduction](#section11660541593)
4    -   [Architecture](#section125101832114213)
5
6-   [Directory Structure](#section161941989596)
7-   [Repositories Involved](#section1371113476307)
8
9## Introduction<a name="section11660541593"></a>
10
11PrintSpooler is a system app preinstalled in OpenHarmony. It provides users with functions such as print preview, discovering and connecting printers, setting print parameters, issuing print tasks, and managing print tasks.
12
13### Architecture<a name="section125101832114213"></a>
14
15![](figures/spooler_01.png)
16
17## Directory Structure<a name="section161941989596"></a>
18
19```
20/applications/standard/print_spooler
21├── figures                     # Architecture figures
22├── entry                       # Main entry module code
23├── signature                   # Certificate files
24├── LICENSE                     # License files
25├── features
26    ├── ippPrint                # ipp print
27```
28
29
30
31

README.md

1# PrintSpooler<a name="ZH-CN_TOPIC_0000001103330836"></a>
2
3-   [简介](#section11660541593)
4    -   [架构图](#section125101832114213)
5-   [目录](#section161941989596)
6-   [使用说明](#section123459000)
7-   [相关仓](#section1371113476307)
8
9## 简介<a name="section11660541593"></a>
10
11PrintSpooler应用是OpenHarmony中预置的系统应用,为用户提供打印预览、发现和连接打印机、打印参数设置、下发打印任务以及打印任务状态的管理等功能。
12
13### 约束限制
14当前只支持对接支持ipp无驱动打印协议的打印机,需要单独安装驱动的打印机暂不支持对接。
15
16### 架构图<a name="section125101832114213"></a>
17
18![](figures/spooler_01.png)
19
20## 目录<a name="section161941989596"></a>
21
22```
23/applications/standard/print_spooler
24    ├── LICENSE                         # 许可文件
25    ├── common                          # 通用工具类目录
26    ├── entry                           # entry模块目录
27    ├── signature                       # 证书文件目录
28    ├── features                        # 子组件目录
29    │   ├── ippPrint                    # 局域网打印组件
30
31```
32###
33
34## 基础开发说明
35### 资源引用
36#### 定义资源文件
37- 在 `src/main/resources/`目录下,根据不同的资源类型,定义资源文件。
38
39  ```json
40      {
41        "name": "default_background_color",
42        "value": "#F1F3F5"
43      },
44  ```
45#### 引用资源
46- 在有对应page的ets文件中,可直接通过`$r()`引用。
47  ```` JavaScript
48  @Provide backgroundColor: Resource = $r('app.color.default_background_color');
49  ````
50## 典型接口的使用
51打印框架启动打印界面:
52
53   ```
54    std::string jobId = GetPrintJobId();
55    auto printJob = std::make_shared<PrintJob>();
56    if (printJob == nullptr) {
57        return E_PRINT_GENERIC_FAILURE;
58    }
59    printJob->SetFdList(fdList);
60    printJob->SetJobId(jobId);
61    printJob->SetJobState(PRINT_JOB_PREPARED);
62    AAFwk::Want want;
63    want.SetElementName(SPOOLER_BUNDLE_NAME, SPOOLER_ABILITY_NAME);
64    want.SetParam(LAUNCH_PARAMETER_JOB_ID, jobId);
65    want.SetParam(LAUNCH_PARAMETER_FILE_LIST, fileList);
66    BuildFDParam(fdList, want);
67    int32_t callerTokenId = static_cast<int32_t>(IPCSkeleton::GetCallingTokenID());
68    std::string callerPkg = DelayedSingleton<PrintBMSHelper>::GetInstance()->QueryCallerBundleName();
69    ingressPackage = callerPkg;
70    int32_t callerUid = IPCSkeleton::GetCallingUid();
71    int32_t callerPid = IPCSkeleton::GetCallingPid();
72    want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_TOKEN, callerTokenId);
73    want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_UID, callerUid);
74    want.SetParam(AAFwk::Want::PARAM_RESV_CALLER_PID, callerPid);
75    want.SetParam(CALLER_PKG_NAME, callerPkg);
76    if (!StartAbility(want)) {
77        PRINT_HILOGE("Failed to start spooler ability");
78        return E_PRINT_SERVER_FAILURE;
79    }
80   ```
81
82## 打印能力指南
83### 支持预览界面显示预览图并根据设置动态刷新
84- `entry/src/main/ets/Common/Utils/FileUtil.ts `
85- 打开传入的图片uri,获得fd并生成imageSource
86  ```
87  import Fileio from '@ohos.file.fs';
88  import image from '@ohos.multimedia.image';
89
90  file = Fileio.openSync(uri, Constants.READ_WRITE);
91  let imageSource = image.createImageSource(file.fd);
92  imageArray.push(new FileModel(<number> file.fd, <string> file.fd, <string> uri,
93  <number> imageInfo.size.width, <number> imageInfo.size.height, imageSource));
94  ```
95
96- `entry/src/main/ets/pages/component/PreviewComponent.ets`
97- 动态变量imageSorce数组更新时,触发handleImage,调用parseImageSize,
98- fdToPixelMap生成pixelMap更新this.currentPixelMap,Image组件显示图片
99- 彩色、无边距等设置项使用组件属性完成动态刷新;
100- 其他设置项变化事件中调用parseImageSize调整或重新加载图片。
101  ```
102  @Link @Watch('handleImage')  imageSources: Array<FileModel>
103  @State currentPixelMap: PixelMap = undefined;
104
105    Image(this.currentPixelMap).key('PreviewComponent_Image_currentPixelMap')
106    .width(this.canvasWidth).height(this.canvasHeight)
107    .backgroundColor($r('app.color.white'))
108    .objectFit(this.isBorderless?ImageFit.Cover:ImageFit.Contain)
109    .renderMode(this.colorMode === ColorMode.COLOR ? ImageRenderMode.Original : ImageRenderMode.Template)
110
111    handleImage(){
112    Log.info(TAG,'handleImage'+this.imageSources.length)
113    this.checkCanvasWidth()
114    this.parseImageSize(false);
115  }
116
117    parseImageSize(isRendered: boolean) {
118    this.originalIndex = this.printRange[this.currentIndex - 1]
119    this.currentImage = this.imageSources[this.originalIndex - 1];
120    if (CheckEmptyUtils.isEmpty(this.currentImage)){
121      return;
122    }
123    if (!isRendered) {
124      this.fdToPixelMap(this.currentImage.fd);
125    }
126    let width = this.currentImage.width
127    let height = this.currentImage.height
128    if(width > height) {
129      this.imageOrientation = PageDirection.LANDSCAPE  //图片横向
130    } else {
131      this.imageOrientation = PageDirection.VERTICAL //图片竖向
132    }
133    this.updateCanvasSize()
134  }
135  ```
136### 支持打印任务管理,显示打印任务状态及错误信息
137-`entry/src/main/ets/pages/JobManagerPage.ets`
138-任务管理界面加载时,根据任务id创建本地任务,用@StorageLink动态监控打印任务队列变化,任务有更新后刷新界面显示;
139
140  ```
141    @StorageLink('JobQueue') jobQueue: Array<PrintJob> = new Array();
142
143    List() {
144      ForEach(this.jobQueue, (printJob:PrintJob)=>{
145        ListItem(){
146          printJobComponent({ mPrintJob: printJob});
147        }.key(`JobManagerPage_ListItem_${printJob.jobId}`)
148      }, printJob=>printJob.jobId)
149    }
150
151    aboutToAppear() {
152        this.abilityContext = GlobalThisHelper.getValue<common.UIAbilityContext>(GlobalThisStorageKey.KEY_JOB_MANAGER_ABILITY_CONTEXT)
153        let data = {
154          wantJobId : Constants.STRING_NONE
155        }
156        this.abilityContext.eventHub.emit(Constants.EVENT_GET_ABILITY_DATA, data);
157        this.jobId = data.wantJobId;
158        this.adapter = PrintAdapter.getInstance();
159        this.adapter.getPrintJobCtl().createPrintJob(this.jobId)
160  }
161  ```
162
163- `entry/src/main/ets/Controller/PrintJobController.ets`
164- 初始化时将本地任务队列存入AppStorage,建立和界面@StorageLink的关联;
165- 通过print.on接口,传入回调监听任务状态更新事件;
166- 状态更新时通过更新本地任务队列,@StorageLink动态刷新界面
167  ```
168  public init(): void {
169    AppStorageHelper.createValue<Array<PrintJob>>(this.getModel().mPrintJobs, AppStorageKeyName.JOB_QUEUE_NAME);
170    this.registerPrintJobCallback();
171  }
172
173  private registerPrintJobCallback(): void {
174    print.on('jobStateChange', this.onJobStateChanged);
175  }
176
177  private onJobStateChanged = (state: print.PrintJobState, job: print.PrintJob): void => {
178    if (state === null || job === null) {
179      Log.error(TAG, 'device state changed null data');
180      return;
181    }
182    this.deleteLocalSource(<number>state, <string>job.jobId);
183    switch (state) {
184      case PrintJobState.PRINT_JOB_PREPARED:
185      case PrintJobState.PRINT_JOB_QUEUED:
186      case PrintJobState.PRINT_JOB_RUNNING:
187      case PrintJobState.PRINT_JOB_BLOCKED:
188      case PrintJobState.PRINT_JOB_COMPLETED:
189        this.onPrintJobStateChange(job);
190        break;
191      default:
192        break;
193    }
194  };
195
196  private onPrintJobStateChange(job: print.PrintJob): void {
197    if (job === null) {
198      return;
199    }
200    this.getModel().printJobStateChange(job.jobId, job.jobState, job.jobSubState);
201  }
202  ```
203### 打印需支持mopria协议,支持p2p连接
204- `feature/ippPrint/src/main/ets/common/discovery/P2pDiscoveryChannel.ts`
205- 调用Wifi-P2p的接口发现周边的p2p打印机
206  ```
207  import wifi from '@ohos.wifi';
208
209  startDiscovery(callback: (found: boolean, peer: wifi.WifiP2pDevice) => void): void {
210    this.discoveryCallback = callback;
211    wifi.on('p2pPeerDeviceChange', this.updatePeerDevices);
212    this.startP2pDiscovery();
213    this.registerWifiCommonEvent();
214
215
216    this.discoverySleepTimer = setInterval(()=> {
217      Log.debug(TAG, 'native p2p service is sleep, start discovery');
218      this.startP2pDiscovery();
219    }, DISCOVERY_SLEEP);
220  }
221
222  private startP2pDiscovery(): void {
223    wifi.startDiscoverDevices();
224    wifi.getP2pPeerDevices().then((peers: wifi.WifiP2pDevice[]) => {
225      this.updatePeerDevices(peers);
226    });
227  }
228  ```
229- `feature/ippPrint/src/main/ets/common/connect/P2pPrinterConnection.ts`
230- 发现p2p打印机之后,执行连接,获取对端打印机的ip信息
231  ```
232  import wifi from '@ohos.wifi';
233
234  private startConnect(printer: DiscoveredPrinter): void {
235    Log.debug(TAG, 'connect to ' + CommonUtils.getSecurityMac(printer.getDeviceAddress()));
236    let config: wifi.WifiP2PConfig = this.configForPeer(printer);
237    this.mWifiModel.registerWifiP2pEvent(WifiModel.p2pConnectionChange, this.p2pConnectionChangeReceive);
238    this.mWifiModel.registerWifiP2pEvent(WifiModel.p2pPeerDeviceChange, this.p2pPeersChangeReceive);
239    let connectionOperation: boolean = this.mWifiModel.connectToPrinter(config);
240    if (!connectionOperation) {
241      Log.error(TAG, 'connection operation failed');
242      if (this.delayTimer !== undefined) {
243        clearTimeout(this.delayTimer);
244      }
245      this.mWifiModel.unregisterWifiP2pEvent(WifiModel.p2pConnectionChange, this.p2pConnectionChangeReceive);
246      this.mWifiModel.unregisterWifiP2pEvent(WifiModel.p2pPeerDeviceChange, this.p2pPeersChangeReceive);
247      this.mListener.onConnectionDelayed();
248      return;
249    }
250    Log.error(TAG, 'connection operation success');
251  }
252  ```
253- `feature/ippPrint/src/main/ets/common/napi/NativeApi.ts`
254- p2p打印机连接成功之后调用print_print_fwk的接口获取打印机支持的ipp协议能力,并调用print_print_fwk接口向cupsd服务配置一台无驱动打印机(支持Mopria协议)
255  ```
256  import print from '@ohos.print';
257
258  public getCapabilities(uri: string, printerName: string, getCapsCallback: (result) => void): void {
259    Log.debug(TAG, 'getCapabilities enter');
260    if (print === undefined) {
261      Log.error(TAG, 'print is undefined');
262      getCapsCallback(ERROR);
263      return;
264    }
265    Log.debug(TAG, 'getCapabilities start');
266    // 获取打印机的ipp打印能力
267    print.queryPrinterCapabilityByUri(uri).then((result) => {
268      Log.debug(TAG, 'nativeGetCapabilities result: ' + JSON.stringify(result));
269      this.setCupsPrinter(uri, this.removeSpaces(printerName));
270      getCapsCallback(result);
271    }).catch((error) => {
272      Log.error(TAG, 'nativeGetCapabilities error: ' + JSON.stringify(error));
273      getCapsCallback(ERROR);
274    });
275    Log.debug(TAG, 'getCapabilities end');
276  }
277
278  public setCupsPrinter(uri: string, name: string): void {
279    Log.debug(TAG, 'setCupsPrinter enter');
280    if (print === undefined) {
281      Log.error(TAG, 'print is undefined');
282      return;
283    }
284    // 向cupsd服务配置无驱动打印机
285    print.addPrinterToCups(uri, name).then((result) => {
286      Log.debug(TAG, 'nativeSetCupsPrinter result: ' + JSON.stringify(result));
287    }).catch((error) => {
288      Log.error(TAG, 'nativeSetCupsPrinter error: ' + JSON.stringify(error));
289    });
290  }
291  ```
292- 打印框架代码详见:[print_print_fwk](https://gitee.com/openharmony/print_print_fwk)
293
294## 签名打包
295### 签名
296#### 签名文件的获取
2971. 拷贝OpenHarmony标准版 工程的 OpenHarmony\signcenter_tool 目录到操作目录
2982. 标准版的签名文件下载路径:https://gitee.com/openharmony/signcenter_tool?_from=gitee_search2993. PrintSpooler 工程的 signature\spooler.p7b 到该目录下
300#### 签名文件的配置
301打开项目工程,选择 File → Project Structure
302
303![](figures/signature_1.png)
304
305选择Project → Signing Configs,将对应的签名文件配置如下,完成后点击Apply,再点击OK。
306密码为生成签名文件时的密码,如果使用默认的签名文件,则使用默认密码123456。
307
308![](figures/signature_2.png)
309
310## 安装、运行、调试
311## 应用安装
312配置 hdc:
313进入SDK目录中的toolchains文件夹下,获取文件路径:
314
315![](figures/screenshot-20210521-105407.png)
316
317> 注意,此处的hdc.exe如果版本较老,可能不能正常使用,需要获取新的hdc.exe文件
318> hdc命令介绍与下载详见:[hdc仓库地址](https://gitee.com/openharmony/developtools_hdc_standard)
319
320
321并将此路径配置到环境变量中:
322
323![](figures/screenshot-20210521-111223.png)
324
325重启电脑使环境变量生效
326
327连接开发板,打开cmd命令窗口,执行hdc list targets,弹出窗口如下:
328
329![](figures/cmd1.png)
330
331等待一段时间后,窗口出现如下打印,可回到输入 hdc list targets 的命令窗口继续操作:
332
333![](figures/cmd2.png)
334
335再次输入hdc list targets,出现如下结果,说明hdc连接成功
336
337![](figures/cmd3.png)
338
339获取读写权限:
340
341```
342hdc target mount
343```
344将签名好的 hap 包放入设备的 `/system/app/com.ohos.spooler` 目录下,并修改hap包的权限
345
346```
347hdc file send 本地路径 /system/app/com.ohos.spooler/hap包名称
348例如:hdc file send Spooler.hap /system/app/com.ohos.spooler/Spooler.hap
349```
350## 应用运行
351Spooler属于系统应用,在将签名的 hap 包放入 `/system/app/com.ohos.spooler` 目录后,重启系统,应用会自动拉起。
352```
353hdc shell
354reboot
355(不可以直接执行hdc reboot,命令是无效的)
356```
357> 注意,如果设备之前安装过系统应用,则需要执行如下两条命令清除设备中存储的应用信息才能够在设备重启的时候将我们装入设备的新 hap 包正常拉起。
358> ```
359> hdc  shell rm -rf  /data/misc_de/0/mdds/0/default/bundle_manager_service
360> hdc  shell rm -rf  /data/accounts
361> ```
362## 应用调试
363### log打印
364- 在程序中添加 log
365```JS
366import hilog from '@ohos.hilog';
367hilog.info(0x0001, "Spooler", "%{public}s World %{private}d", "hello", 3);
368```
369### log获取及过滤
370- log获取
371
372
373将log输出至文件
374```
375hdc shell hilog > 输出文件名称
376```
377
378例:
379在真实环境查看log,将全log输出到当前目录的hilog.log文件中
380```
381hdc shell hilog > hilog.log
382```
383
384- log过滤
385
386在命令行窗口中过滤log
387```
388hilog │ grep 过滤信息
389```
390
391例:过滤包含信息 Label 的 hilog
392```
393hilog │ grep Label
394```
395## 贡献代码
396### Fork 代码仓库
3971. 在码云上打开 PrintSpooler 代码仓库([仓库地址](https://gitee.com/openharmony/applications_print_spooler))。
398
3992. 点击仓库右上角的 Forked 按钮,在弹出的画面中,选择将仓库 fork 到哪里,点击确认。
400
4013. Fork 成功之后,会在自己的账号下看见 fork 的代码仓库。
402
403### 提交代码
4041. 访问我们自己在码云账号上 fork 的代码仓库,点击“克隆/下载”按钮,选择 SSH/HTTPS,点击“复制”按钮。
405
4062. 在本地新建 PrintSpooler 目录,在 PrintSpooler 目录中执行如下命令
407   ```
408   git clone 步骤1中复制的地址
409   ```
410
4113. 修改代码。
412
413   > 将代码引入工程,以及编译工程等相关内容请参见 **3. 代码使用** 部分的相关内容。
4144. 提交代码到 fork 仓库。
415   > 修改后的代码,首先执行 `git add` 命令,然后执行 `git commit` 命令与 `git push` 命令,将代码 push 到我们自己的 fork 仓中。
416   > 关于代码提交的这部分内容涉及 git 的使用,可以参照 [git官网](https://git-scm.com/) 的内容,在此不再赘述。
417
418### 发起 Pull Request (PR)
419在将代码提交到 fork 仓之后,我们可以通过发起 Pull Request(PR)的方式来为 OpenHarmony 的相关项目贡献代码。
420
4211. 打开 fork 仓库。选择 `Pull Requests` → `新建 Pull Request`
422
4232. 在 `新建 Pull Request` 画面填入标题与说明,点击 `创建` 按钮。
424
4253. 创建 Pull Request 完成。 PR 创建完成后,会有专门的代码审查人员对代码进行评审,评审通过之后会合入相应的代码库。
426